Skip to content

最小实现

本文给出仅包含 主 SDKNavigationServiceMapView 三者的最小集成示例,全部在 一个 Activity 中完成,便于快速验证工程是否可用。更完整的选项说明见 初始化

1. 模块与职责

模块 所在类 初始化 反初始化
主 SDK MapActivity SDK.getInstance().initialize(...),后台子线程,Region = EU SDK.getInstance().dispose(true)
NavigationService MapActivity NavigationService.Factory.createInstance(),主 SDK 成功后;主线程或后台线程均可 navigationService.dispose()
MapView MapActivity mapView.initialize(config)主 SDK 初始化成功后(主线程) mapView.onDestroy()

2. 权限说明(必读)

在调用 SDK.initialize 之前,必须先完成运行时权限申请。

主 SDK 需要读写缓存目录;Hybrid / Onboard 还需访问本地地图数据路径(常为外部存储)。若在未授权时初始化,可能导致缓存写入失败、地图数据无法读取或初始化异常。

集成时应:

  1. Activity.onCreate调用 requestPermissions(),一次性申请定位、网络、存储等权限(参考示例 MainActivity.checkUserPermission())。
  2. Android 11(API 30)及以上,若使用 sdcard 等外部目录,另引导用户开启「所有文件访问」。
  3. 仅在权限流程结束后再执行 SDK.initialize(建议后台线程)。
  4. 仅在主 SDK 初始化成功后再创建 NavigationService(主线程或后台线程均可)。
  5. 仅在主 SDK 初始化成功后再调用 MapView.initialize(主线程)。
1
权限就绪  →  SDK.initialize  →  NavigationService.createInstance  →  MapView.initialize

3. 流程总览

3.1 初始化

1
2
3
4
5
6
7
8
MapActivity.onCreate
  ├─ setContentView(布局含 TnMapView)
  └─ requestPermissions()                         ← 必须先于 SDK
        └─(已授权 / onRequestPermissionsResult)
              └─ 子线程:SDK.initialize(EU)
                    └─(可选:同线程或主线程)NavigationService.createInstance()
                          └─ runOnUiThread → initMapView()   ← SDK 成功后再初始化地图
                          └─ onResume / onPause 转发

3.2 反初始化

1
2
3
4
5
MapActivity.onDestroy
  ├─ mapView.onDestroy()
  └─ 子线程
        ├─ navigationService.dispose()(若已创建)
        └─ SDK.getInstance().dispose(true)

4. 布局

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.telenav.map.views.TnMapView
        android:id="@+id/mapView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

5. 初始化示例(MapActivity)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class MapActivity : AppCompatActivity() {

    private val permissionRequestCode = 12335
    private lateinit var binding: ActivityMapBinding

    lateinit var navigationService: NavigationService
        private set

    @Volatile
    private var isSdkReady = false

    private var isMapViewInitialized = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMapBinding.inflate(layoutInflater)
        setContentView(binding.root)
        requestPermissions()   // ① 先申请权限
    }

    // ---------- ① 权限(先于 SDK) ----------

    private fun requestPermissions() {
        val permissionsRequired = mutableListOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_NETWORK_STATE,
            Manifest.permission.INTERNET,
        )
        permissionsRequired.add(
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                Manifest.permission.READ_MEDIA_IMAGES
            } else {
                Manifest.permission.READ_EXTERNAL_STORAGE
            }
        )

        permissionsRequired.forEach { permission ->
            if (ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_DENIED) {
                ActivityCompat.requestPermissions(this, permissionsRequired.toTypedArray(), permissionRequestCode)
                return
            }
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
            startActivity(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION).apply {
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            })
        }

        initSdkAndNavigationServiceAsync()
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == permissionRequestCode) {
            initSdkAndNavigationServiceAsync()   // ② 权限回调后再初始化 SDK
        }
    }

    // ---------- ② 主 SDK(后台线程);NavigationService 可在本线程或主线程 ----------

    private fun initSdkAndNavigationServiceAsync() {
        if (isSdkReady) return

        thread(name = "sdk-init") {
            val cacheDir = File(cacheDir, "sdk_cache").apply { mkdirs() }

            val sdkOptions = SDKOptions.builder()
                .setApiKey("<YOUR_EU_API_KEY>")
                .setApiSecret("<YOUR_EU_API_SECRET>")
                .setCloudEndPoint("<YOUR_EU_CLOUD_ENDPOINT>")
                .setSdkCacheDataDir(cacheDir.absolutePath)
                .setLocale(Locale.GERMAN)
                .setRegion(Region.EU.name)
                .setUserId("<YOUR_USER_ID>")
                .setDeviceGuid("<YOUR_DEVICE_GUID>")
                // .setSdkDataDir("<YOUR_EU_MAP_DATA_DIR>")   // Hybrid / Onboard
                .build()

            val navSDKOptions = NavSDKOptions.builder(
                sdkOptions,
                VehicleInfoConfig.Builder().build()
            )
                .setTrafficRefreshTime(120)
                .setTrafficExpireTime(120)
                .build()

            if (SDK.getInstance().initialize(this@MapActivity, navSDKOptions) != 0) return@thread

            isSdkReady = true

            runOnUiThread {
                navigationService = NavigationService.Factory.createInstance()   // 主线程创建亦可
                initMapView()   // ③ SDK 成功后再初始化 MapView
            }
        }
    }

    // ---------- ③ MapView(主 SDK 成功后,主线程) ----------

    private fun initMapView() {
        if (isMapViewInitialized) return

        binding.mapView.initialize(
            MapViewInitConfig(
                context = applicationContext,
                lifecycleOwner = this,
                readyListener = MapViewReadyListener { /* 地图就绪后再操作相机、车标 */ }
            )
        )
        isMapViewInitialized = true
    }

    override fun onResume() {
        super.onResume()
        if (isMapViewInitialized) binding.mapView.onResume()
    }

    override fun onPause() {
        if (isMapViewInitialized) binding.mapView.onPause()
        super.onPause()
    }
}

6. 反初始化示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
override fun onDestroy() {
    if (isMapViewInitialized) {
        binding.mapView.onDestroy()
    }

    thread(name = "sdk-dispose") {
        if (::navigationService.isInitialized) {
            navigationService.stopNavigation()
            navigationService.dispose()
        }
        SDK.getInstance().dispose(true)
        isSdkReady = false
    }
    super.onDestroy()
}
模块 时机 API
MapView onDestroy mapView.onDestroy()
NavigationService onDestroy(子线程,若已创建) navigationService.dispose()
主 SDK onDestroy(子线程) SDK.getInstance().dispose(true)

mapView.onPause() / onResume() 须与 Activity 生命周期成对调用。