expo-gaode-map-navigation 2.0.2 → 2.0.4-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/android/build.gradle +3 -0
  2. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +32 -50
  3. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +5 -15
  4. package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +41 -14
  5. package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
  6. package/build/ExpoGaodeMapNaviView.js +3 -2
  7. package/build/ExpoGaodeMapNaviView.js.map +1 -1
  8. package/build/ExpoGaodeMapNavigationModule.d.ts +2 -7
  9. package/build/ExpoGaodeMapNavigationModule.d.ts.map +1 -1
  10. package/build/ExpoGaodeMapNavigationModule.js +12 -2
  11. package/build/ExpoGaodeMapNavigationModule.js.map +1 -1
  12. package/build/map/ExpoGaodeMapModule.d.ts.map +1 -1
  13. package/build/map/ExpoGaodeMapModule.js +154 -16
  14. package/build/map/ExpoGaodeMapModule.js.map +1 -1
  15. package/build/map/ExpoGaodeMapOfflineModule.d.ts.map +1 -1
  16. package/build/map/ExpoGaodeMapOfflineModule.js +12 -2
  17. package/build/map/ExpoGaodeMapOfflineModule.js.map +1 -1
  18. package/build/map/ExpoGaodeMapView.d.ts.map +1 -1
  19. package/build/map/ExpoGaodeMapView.js +8 -2
  20. package/build/map/ExpoGaodeMapView.js.map +1 -1
  21. package/build/map/components/overlays/Circle.d.ts.map +1 -1
  22. package/build/map/components/overlays/Circle.js +3 -2
  23. package/build/map/components/overlays/Circle.js.map +1 -1
  24. package/build/map/components/overlays/Cluster.d.ts.map +1 -1
  25. package/build/map/components/overlays/Cluster.js +3 -2
  26. package/build/map/components/overlays/Cluster.js.map +1 -1
  27. package/build/map/components/overlays/HeatMap.d.ts.map +1 -1
  28. package/build/map/components/overlays/HeatMap.js +3 -2
  29. package/build/map/components/overlays/HeatMap.js.map +1 -1
  30. package/build/map/components/overlays/Marker.d.ts.map +1 -1
  31. package/build/map/components/overlays/Marker.js +3 -2
  32. package/build/map/components/overlays/Marker.js.map +1 -1
  33. package/build/map/components/overlays/MultiPoint.d.ts.map +1 -1
  34. package/build/map/components/overlays/MultiPoint.js +3 -2
  35. package/build/map/components/overlays/MultiPoint.js.map +1 -1
  36. package/build/map/components/overlays/Polygon.d.ts.map +1 -1
  37. package/build/map/components/overlays/Polygon.js +3 -2
  38. package/build/map/components/overlays/Polygon.js.map +1 -1
  39. package/build/map/components/overlays/Polyline.d.ts.map +1 -1
  40. package/build/map/components/overlays/Polyline.js +3 -2
  41. package/build/map/components/overlays/Polyline.js.map +1 -1
  42. package/build/map/types/common.types.d.ts +6 -0
  43. package/build/map/types/common.types.d.ts.map +1 -1
  44. package/build/map/types/common.types.js.map +1 -1
  45. package/build/map/types/native-module.types.d.ts +4 -1
  46. package/build/map/types/native-module.types.d.ts.map +1 -1
  47. package/build/map/types/native-module.types.js.map +1 -1
  48. package/build/map/utils/ErrorHandler.d.ts +6 -0
  49. package/build/map/utils/ErrorHandler.d.ts.map +1 -1
  50. package/build/map/utils/ErrorHandler.js +31 -0
  51. package/build/map/utils/ErrorHandler.js.map +1 -1
  52. package/build/map/utils/lazyNativeViewManager.d.ts +3 -0
  53. package/build/map/utils/lazyNativeViewManager.d.ts.map +1 -0
  54. package/build/map/utils/lazyNativeViewManager.js +11 -0
  55. package/build/map/utils/lazyNativeViewManager.js.map +1 -0
  56. package/ios/ExpoGaodeMapNavigation.podspec +5 -0
  57. package/ios/map/ExpoGaodeMapModule.swift +20 -42
  58. package/ios/map/ExpoGaodeMapView.swift +160 -77
  59. package/ios/map/GaodeMapPrivacyManager.swift +65 -0
  60. package/ios/map/modules/LocationManager.swift +43 -24
  61. package/package.json +1 -1
  62. package/android/src/main/java/expo/modules/gaodemap/map/MapPreloadManager.kt +0 -494
  63. package/ios/map/MapPreloadManager.swift +0 -348
@@ -28,18 +28,17 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
28
28
 
29
29
  override init() {
30
30
  super.init()
31
- initLocationManager()
32
31
  }
33
32
 
34
33
  // MARK: - 连续定位控制
35
34
 
36
35
  func start() {
37
- locationManager?.startUpdatingLocation()
36
+ ensureLocationManager()?.startUpdatingLocation()
38
37
  isLocationStarted = true
39
38
  }
40
39
 
41
40
  func stop() {
42
- locationManager?.stopUpdatingLocation()
41
+ ensureLocationManager()?.stopUpdatingLocation()
43
42
  isLocationStarted = false
44
43
  }
45
44
 
@@ -50,19 +49,19 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
50
49
  // MARK: - 高德定位配置 API
51
50
 
52
51
  func setLocatingWithReGeocode(_ isReGeocode: Bool) {
53
- locationManager?.locatingWithReGeocode = isReGeocode
52
+ ensureLocationManager()?.locatingWithReGeocode = isReGeocode
54
53
  }
55
54
 
56
55
  func setDistanceFilter(_ distance: Double) {
57
- locationManager?.distanceFilter = distance
56
+ ensureLocationManager()?.distanceFilter = distance
58
57
  }
59
58
 
60
59
  func setLocationTimeout(_ timeout: Int) {
61
- locationManager?.locationTimeout = timeout
60
+ ensureLocationManager()?.locationTimeout = timeout
62
61
  }
63
62
 
64
63
  func setReGeocodeTimeout(_ timeout: Int) {
65
- locationManager?.reGeocodeTimeout = timeout
64
+ ensureLocationManager()?.reGeocodeTimeout = timeout
66
65
  }
67
66
 
68
67
  func setDesiredAccuracy(_ accuracy: Int) {
@@ -76,11 +75,11 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
76
75
  case 5: value = kCLLocationAccuracyThreeKilometers
77
76
  default: value = kCLLocationAccuracyBest
78
77
  }
79
- locationManager?.desiredAccuracy = value
78
+ ensureLocationManager()?.desiredAccuracy = value
80
79
  }
81
80
 
82
81
  func setPausesLocationUpdatesAutomatically(_ pauses: Bool) {
83
- locationManager?.pausesLocationUpdatesAutomatically = pauses
82
+ ensureLocationManager()?.pausesLocationUpdatesAutomatically = pauses
84
83
  }
85
84
 
86
85
  func setAllowsBackgroundLocationUpdates(_ allows: Bool) {
@@ -91,14 +90,14 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
91
90
  return
92
91
  }
93
92
  }
94
- locationManager?.allowsBackgroundLocationUpdates = allows
93
+ ensureLocationManager()?.allowsBackgroundLocationUpdates = allows
95
94
  }
96
95
 
97
96
  func setGeoLanguage(_ language: Int) {
98
97
  switch language {
99
- case 0: locationManager?.reGeocodeLanguage = .default
100
- case 1: locationManager?.reGeocodeLanguage = .chinse
101
- case 2: locationManager?.reGeocodeLanguage = .english
98
+ case 0: ensureLocationManager()?.reGeocodeLanguage = .default
99
+ case 1: ensureLocationManager()?.reGeocodeLanguage = .chinse
100
+ case 2: ensureLocationManager()?.reGeocodeLanguage = .english
102
101
  default: break
103
102
  }
104
103
  }
@@ -106,26 +105,41 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
106
105
  // MARK: - 方向
107
106
 
108
107
  func startUpdatingHeading() {
109
- locationManager?.startUpdatingHeading()
108
+ ensureLocationManager()?.startUpdatingHeading()
110
109
  }
111
110
 
112
111
  func stopUpdatingHeading() {
113
- locationManager?.stopUpdatingHeading()
112
+ ensureLocationManager()?.stopUpdatingHeading()
114
113
  }
115
114
 
116
115
  // MARK: - 初始化
117
116
 
118
- private func initLocationManager() {
119
- locationManager = AMapLocationManager()
120
- locationManager?.delegate = self
117
+ @discardableResult
118
+ private func ensureLocationManager() -> AMapLocationManager? {
119
+ if let locationManager {
120
+ return locationManager
121
+ }
122
+
123
+ guard GaodeMapPrivacyManager.isReady else {
124
+ log.warn("⚠️ [ExpoGaodeMap] iOS 定位模块在隐私同意前不会初始化 AMapLocationManager")
125
+ return nil
126
+ }
127
+
128
+ GaodeMapPrivacyManager.applyPrivacyState()
129
+
130
+ let manager = AMapLocationManager()
131
+ manager.delegate = self
121
132
 
122
133
  // 默认配置
123
- locationManager?.desiredAccuracy = kCLLocationAccuracyHundredMeters
124
- locationManager?.distanceFilter = 10
125
- locationManager?.locationTimeout = 10
126
- locationManager?.reGeocodeTimeout = 5
127
- locationManager?.locatingWithReGeocode = true
128
- locationManager?.pausesLocationUpdatesAutomatically = false
134
+ manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
135
+ manager.distanceFilter = 10
136
+ manager.locationTimeout = 10
137
+ manager.reGeocodeTimeout = 5
138
+ manager.locatingWithReGeocode = true
139
+ manager.pausesLocationUpdatesAutomatically = false
140
+
141
+ locationManager = manager
142
+ return manager
129
143
  }
130
144
 
131
145
  // MARK: - Delegate(连续定位回调)
@@ -184,6 +198,11 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
184
198
  * @param promise Promise
185
199
  */
186
200
  func coordinateConvert(_ coordinate: [String: Double], type: Int, promise: Promise) {
201
+ guard GaodeMapPrivacyManager.isReady else {
202
+ promise.reject("PRIVACY_NOT_AGREED", "隐私协议未完成确认,请先调用 setPrivacyShow/setPrivacyAgree")
203
+ return
204
+ }
205
+
187
206
  guard let lat = coordinate["latitude"],
188
207
  let lon = coordinate["longitude"] else {
189
208
  promise.reject("INVALID_ARGUMENT", "Invalid coordinate")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-gaode-map-navigation",
3
- "version": "2.0.2",
3
+ "version": "2.0.4-next.0",
4
4
  "description": "高德地图导航功能模块 - 路径规划、导航引导,独立版本包含完整地图功能",
5
5
  "author": "<TomWq> (https://github.com/TomWq)",
6
6
  "repository": "https://github.com/TomWq/expo-gaode-map",
@@ -1,494 +0,0 @@
1
- /**
2
- * 地图预加载管理器 (Android)
3
- * 在后台预先初始化地图实例,提升首次显示速度
4
- */
5
-
6
- package expo.modules.gaodemap.map
7
-
8
- import android.app.ActivityManager
9
- import android.content.ComponentCallbacks2
10
- import android.content.Context
11
- import android.content.res.Configuration
12
- import android.util.Log
13
- import com.amap.api.maps.AMap
14
- import com.amap.api.maps.CameraUpdateFactory
15
- import com.amap.api.maps.MapView
16
- import com.amap.api.maps.model.CameraPosition
17
- import com.amap.api.maps.model.LatLng
18
- import kotlinx.coroutines.*
19
-
20
- import java.util.concurrent.ConcurrentLinkedQueue
21
- import java.util.concurrent.atomic.AtomicBoolean
22
- import java.util.concurrent.atomic.AtomicInteger
23
-
24
- /**
25
- * 地图预加载实例数据
26
- */
27
- data class PreloadedMapInstance(
28
- val mapView: MapView,
29
- val timestamp: Long = System.currentTimeMillis()
30
- )
31
-
32
- /**
33
- * 地图预加载管理器单例
34
- */
35
- object MapPreloadManager : ComponentCallbacks2 {
36
- private const val TAG = "MapPreloadManager"
37
-
38
- // 动态池大小配置(根据内存自适应)
39
- private const val MAX_POOL_SIZE_HIGH_MEMORY = 3 // 高内存设备:>= 500MB
40
- private const val MAX_POOL_SIZE_MEDIUM_MEMORY = 2 // 中等内存:300-500MB
41
- private const val MAX_POOL_SIZE_LOW_MEMORY = 1 // 低内存设备:150-300MB
42
- private const val MIN_MEMORY_THRESHOLD_MB = 150 // 最低内存要求
43
-
44
- // 动态 TTL 配置(根据内存压力自适应)
45
- private const val TTL_NORMAL = 5 * 60 * 1000L // 正常:5分钟
46
- private const val TTL_MEMORY_PRESSURE = 2 * 60 * 1000L // 内存压力:2分钟
47
- private const val TTL_LOW_MEMORY = 1 * 60 * 1000L // 低内存:1分钟
48
-
49
- private val preloadedMapViews = ConcurrentLinkedQueue<PreloadedMapInstance>()
50
- private val isPreloading = AtomicBoolean(false)
51
- private val preloadScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
52
-
53
- // 动态配置
54
- @Volatile private var currentMaxPoolSize = MAX_POOL_SIZE_MEDIUM_MEMORY
55
- @Volatile private var currentTTL = TTL_NORMAL
56
- @Volatile private var memoryPressureLevel = 0 // 0=正常, 1=压力, 2=严重
57
-
58
- // 性能统计
59
- private val totalPreloads = AtomicInteger(0)
60
- private val successfulPreloads = AtomicInteger(0)
61
- private val failedPreloads = AtomicInteger(0)
62
- private val instancesUsed = AtomicInteger(0)
63
- private val instancesExpired = AtomicInteger(0)
64
- private val totalDuration = AtomicInteger(0)
65
-
66
- private var appContext: Context? = null
67
- private var cleanupJob: Job? = null
68
-
69
- init {
70
- Log.d(TAG, "🔧 初始化预加载管理器")
71
- }
72
-
73
- /**
74
- * 初始化管理器(注册内存监听)
75
- */
76
- fun initialize(context: Context) {
77
- appContext = context.applicationContext
78
- appContext?.registerComponentCallbacks(this)
79
-
80
- // 启动定期清理任务
81
- startPeriodicCleanup()
82
-
83
- Log.d(TAG, "✅ 预加载管理器已初始化,已注册内存监听")
84
- }
85
-
86
- /**
87
- * 启动定期清理过期实例的任务
88
- */
89
- private fun startPeriodicCleanup() {
90
- cleanupJob?.cancel()
91
- cleanupJob = preloadScope.launch {
92
- while (isActive) {
93
- delay(60_000) // 每分钟检查一次
94
- cleanupExpiredInstances()
95
- }
96
- }
97
- }
98
-
99
- /**
100
- * 清理过期的预加载实例(使用动态 TTL)
101
- */
102
- private fun cleanupExpiredInstances() {
103
- val now = System.currentTimeMillis()
104
- var expiredCount = 0
105
-
106
- val iterator = preloadedMapViews.iterator()
107
- while (iterator.hasNext()) {
108
- val instance = iterator.next()
109
- // 使用动态 TTL
110
- if (now - instance.timestamp > currentTTL) {
111
- try {
112
- instance.mapView.onDestroy()
113
- iterator.remove()
114
- expiredCount++
115
- } catch (e: Exception) {
116
- Log.e(TAG, "清理过期实例失败: ${e.message}", e)
117
- }
118
- }
119
- }
120
-
121
- if (expiredCount > 0) {
122
- instancesExpired.addAndGet(expiredCount)
123
- Log.i(TAG, "🧹 清理了 $expiredCount 个过期实例(总计: ${instancesExpired.get()},当前TTL: ${currentTTL/1000}秒)")
124
- }
125
- }
126
-
127
- /**
128
- * 内存警告回调
129
- */
130
- @Deprecated("Deprecated in Java")
131
- override fun onLowMemory() {
132
- Log.w(TAG, "⚠️ 收到低内存警告,清理预加载池")
133
- clearPool()
134
- }
135
-
136
- /**
137
- * 内存trim回调
138
- */
139
- override fun onTrimMemory(level: Int) {
140
- when (level) {
141
- ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL,
142
- ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
143
- Log.w(TAG, "⚠️ 内存严重不足 (level: $level),清理预加载池")
144
- clearPool()
145
- }
146
- ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
147
- ComponentCallbacks2.TRIM_MEMORY_MODERATE -> {
148
- Log.w(TAG, "⚠️ 内存不足 (level: $level),清理部分实例")
149
- // 只清理一半
150
- val halfSize = preloadedMapViews.size / 2
151
- repeat(halfSize) {
152
- preloadedMapViews.poll()?.mapView?.onDestroy()
153
- }
154
- }
155
- }
156
- }
157
-
158
- override fun onConfigurationChanged(newConfig: Configuration) {
159
- // 不需要处理
160
- }
161
-
162
- /**
163
- * 开始预加载地图实例(自适应版本)
164
- * @param context Android 上下文
165
- * @param poolSize 预加载的地图实例数量(会根据内存自适应调整)
166
- */
167
- fun startPreload(context: Context, poolSize: Int) {
168
- if (isPreloading.get()) {
169
- Log.w(TAG, "⚠️ 预加载已在进行中")
170
- return
171
- }
172
-
173
- // 动态计算最优池大小
174
- val adaptiveMaxPoolSize = calculateAdaptivePoolSize(context)
175
- currentMaxPoolSize = adaptiveMaxPoolSize
176
-
177
- // 动态调整 TTL
178
- currentTTL = calculateAdaptiveTTL(context)
179
-
180
- // 检查内存是否充足
181
- if (!hasEnoughMemory(context)) {
182
- Log.w(TAG, "⚠️ 内存不足,跳过预加载")
183
- return
184
- }
185
-
186
- isPreloading.set(true)
187
- val targetSize = minOf(poolSize, adaptiveMaxPoolSize)
188
- Log.i(TAG, "🚀 开始预加载 $targetSize 个地图实例 (自适应池大小: $adaptiveMaxPoolSize, TTL: ${currentTTL/1000}秒)")
189
-
190
- val startTime = System.currentTimeMillis()
191
- totalPreloads.incrementAndGet()
192
-
193
- preloadScope.launch {
194
- var successCount = 0
195
-
196
- repeat(targetSize) { index ->
197
- try {
198
- val mapView = createPreloadedMapView(context)
199
-
200
- withContext(Dispatchers.Main) {
201
- val instance = PreloadedMapInstance(mapView)
202
- preloadedMapViews.offer(instance)
203
- successCount++
204
- Log.i(TAG, "✅ 预加载实例 ${index + 1}/$targetSize 完成")
205
-
206
- if (index == targetSize - 1) {
207
- val duration = (System.currentTimeMillis() - startTime).toInt()
208
- isPreloading.set(false)
209
-
210
- if (successCount > 0) {
211
- successfulPreloads.incrementAndGet()
212
- totalDuration.addAndGet(duration)
213
- } else {
214
- failedPreloads.incrementAndGet()
215
- }
216
-
217
- Log.i(TAG, "🎉 所有实例预加载完成(耗时: ${duration}ms)")
218
- }
219
- }
220
- } catch (e: Exception) {
221
- Log.e(TAG, "❌ 预加载实例 ${index + 1} 失败: ${e.message}", e)
222
- if (index == targetSize - 1) {
223
- isPreloading.set(false)
224
- if (successCount == 0) {
225
- failedPreloads.incrementAndGet()
226
- }
227
- }
228
- }
229
- }
230
- }
231
- }
232
-
233
- /**
234
- * 计算自适应池大小
235
- * 根据可用内存动态调整池大小
236
- */
237
- private fun calculateAdaptivePoolSize(context: Context): Int {
238
- val availableMB = getAvailableMemoryMB(context)
239
-
240
- return when {
241
- availableMB >= 500 -> {
242
- Log.i(TAG, "📊 高内存设备 (${availableMB}MB),池大小: $MAX_POOL_SIZE_HIGH_MEMORY")
243
- MAX_POOL_SIZE_HIGH_MEMORY
244
- }
245
- availableMB >= 300 -> {
246
- Log.i(TAG, "📊 中等内存设备 (${availableMB}MB),池大小: $MAX_POOL_SIZE_MEDIUM_MEMORY")
247
- MAX_POOL_SIZE_MEDIUM_MEMORY
248
- }
249
- availableMB >= 150 -> {
250
- Log.i(TAG, "📊 低内存设备 (${availableMB}MB),池大小: $MAX_POOL_SIZE_LOW_MEMORY")
251
- MAX_POOL_SIZE_LOW_MEMORY
252
- }
253
- else -> {
254
- Log.w(TAG, "⚠️ 内存极低 (${availableMB}MB),禁用预加载")
255
- 0
256
- }
257
- }
258
- }
259
-
260
- /**
261
- * 计算自适应 TTL
262
- * 根据内存压力动态调整过期时间
263
- */
264
- private fun calculateAdaptiveTTL(context: Context): Long {
265
- val availableMB = getAvailableMemoryMB(context)
266
- val totalMB = getTotalMemoryMB(context)
267
- val usagePercent = ((totalMB - availableMB).toFloat() / totalMB * 100).toInt()
268
-
269
- return when {
270
- usagePercent < 60 -> {
271
- memoryPressureLevel = 0
272
- Log.i(TAG, "📊 内存充足 (使用率: $usagePercent%),TTL: ${TTL_NORMAL/1000}秒")
273
- TTL_NORMAL
274
- }
275
- usagePercent < 80 -> {
276
- memoryPressureLevel = 1
277
- Log.i(TAG, "📊 内存压力 (使用率: $usagePercent%),TTL: ${TTL_MEMORY_PRESSURE/1000}秒")
278
- TTL_MEMORY_PRESSURE
279
- }
280
- else -> {
281
- memoryPressureLevel = 2
282
- Log.w(TAG, "⚠️ 内存严重不足 (使用率: $usagePercent%),TTL: ${TTL_LOW_MEMORY/1000}秒")
283
- TTL_LOW_MEMORY
284
- }
285
- }
286
- }
287
-
288
- /**
289
- * 获取可用内存(MB)
290
- */
291
- private fun getAvailableMemoryMB(context: Context): Long {
292
- val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
293
- val memoryInfo = ActivityManager.MemoryInfo()
294
- activityManager?.getMemoryInfo(memoryInfo)
295
- return memoryInfo.availMem / (1024 * 1024)
296
- }
297
-
298
- /**
299
- * 获取总内存(MB)
300
- */
301
- private fun getTotalMemoryMB(context: Context): Long {
302
- val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
303
- val memoryInfo = ActivityManager.MemoryInfo()
304
- activityManager?.getMemoryInfo(memoryInfo)
305
- return memoryInfo.totalMem / (1024 * 1024)
306
- }
307
-
308
- /**
309
- * 检查是否有足够的内存
310
- */
311
- private fun hasEnoughMemory(context: Context): Boolean {
312
- val availableMB = getAvailableMemoryMB(context)
313
- return availableMB > MIN_MEMORY_THRESHOLD_MB
314
- }
315
-
316
- /**
317
- * 创建预加载的地图视图
318
- * @param context Android 上下文
319
- * @return 预加载的地图视图实例
320
- */
321
- private suspend fun createPreloadedMapView(context: Context): MapView {
322
- return withContext(Dispatchers.Main) {
323
- val mapView = MapView(context)
324
- val aMap = mapView.map
325
-
326
- // 基础配置
327
- aMap.mapType = AMap.MAP_TYPE_NORMAL
328
- aMap.isMyLocationEnabled = false
329
- aMap.uiSettings.isCompassEnabled = false
330
- aMap.uiSettings.isScaleControlsEnabled = false
331
- aMap.uiSettings.isZoomControlsEnabled = false
332
- aMap.uiSettings.isRotateGesturesEnabled = true
333
- aMap.uiSettings.isScrollGesturesEnabled = true
334
- aMap.uiSettings.isZoomGesturesEnabled = true
335
-
336
- // 预加载中心区域(北京天安门)
337
- val cameraPosition = CameraPosition(
338
- LatLng(39.9042, 116.4074),
339
- 12f, // zoom
340
- 0f, // tilt
341
- 0f // bearing
342
- )
343
- aMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
344
-
345
- // 触发地图初始化
346
- mapView.onCreate(null)
347
-
348
- mapView
349
- }
350
- }
351
-
352
- /**
353
- * 获取一个预加载的地图实例(使用动态 TTL)
354
- * @return 预加载的地图视图,如果池为空则返回 null
355
- */
356
- fun getPreloadedMapView(): MapView? {
357
- val now = System.currentTimeMillis()
358
-
359
- // 检查并移除过期实例(使用动态 TTL)
360
- while (true) {
361
- val instance = preloadedMapViews.peek() ?: break
362
-
363
- if (now - instance.timestamp > currentTTL) {
364
- preloadedMapViews.poll()
365
- try {
366
- instance.mapView.onDestroy()
367
- instancesExpired.incrementAndGet()
368
- Log.i(TAG, "🗑️ 预加载实例已过期(TTL: ${currentTTL/1000}秒),已删除")
369
- } catch (e: Exception) {
370
- Log.e(TAG, "清理过期实例失败: ${e.message}", e)
371
- }
372
- } else {
373
- break
374
- }
375
- }
376
-
377
- val instance = preloadedMapViews.poll()
378
-
379
- if (instance != null) {
380
- instancesUsed.incrementAndGet()
381
- Log.i(TAG, "📤 使用预加载实例,剩余: ${preloadedMapViews.size},总使用: ${instancesUsed.get()}")
382
-
383
- // 触发自动补充(延迟执行,避免影响当前页面渲染)
384
- triggerRefill()
385
-
386
- return instance.mapView
387
- } else {
388
- Log.w(TAG, "⚠️ 预加载池为空,返回 null")
389
- // 尝试触发补充,以便下次使用
390
- triggerRefill()
391
- return null
392
- }
393
- }
394
-
395
- /**
396
- * 触发自动补充机制
397
- */
398
- private fun triggerRefill() {
399
- if (isPreloading.get()) return
400
-
401
- // 延迟 5 秒后尝试补充,避免抢占当前 UI 资源
402
- preloadScope.launch {
403
- delay(5000)
404
- if (!isPreloading.get() && preloadedMapViews.size < currentMaxPoolSize) {
405
- appContext?.let { context ->
406
- Log.i(TAG, "🔄 触发自动补充预加载池")
407
- withContext(Dispatchers.Main) {
408
- startPreload(context, currentMaxPoolSize)
409
- }
410
- }
411
- }
412
- }
413
- }
414
-
415
- /**
416
- * 清空预加载池
417
- */
418
- fun clearPool() {
419
- val count = preloadedMapViews.size
420
- preloadedMapViews.forEach { instance ->
421
- try {
422
- instance.mapView.onDestroy()
423
- } catch (e: Exception) {
424
- Log.e(TAG, "清理地图实例失败: ${e.message}", e)
425
- }
426
- }
427
- preloadedMapViews.clear()
428
- Log.i(TAG, "🗑️ 预加载池已清空,清理了 $count 个实例")
429
- }
430
-
431
- /**
432
- * 获取预加载状态(包含动态配置信息)
433
- * @return 预加载状态信息
434
- */
435
- fun getStatus(): Map<String, Any> {
436
- return mapOf(
437
- "poolSize" to preloadedMapViews.size,
438
- "isPreloading" to isPreloading.get(),
439
- "maxPoolSize" to currentMaxPoolSize,
440
- "currentTTL" to currentTTL,
441
- "memoryPressureLevel" to memoryPressureLevel,
442
- "isAdaptive" to true
443
- )
444
- }
445
-
446
- /**
447
- * 检查是否有可用的预加载实例
448
- * @return 是否有可用实例
449
- */
450
- fun hasPreloadedMapView(): Boolean {
451
- return preloadedMapViews.isNotEmpty()
452
- }
453
-
454
- /**
455
- * 获取性能统计信息(包含内存信息)
456
- */
457
- fun getPerformanceMetrics(): Map<String, Any> {
458
- val total = totalPreloads.get()
459
- val successful = successfulPreloads.get()
460
- val avgDuration = if (successful > 0) totalDuration.get() / successful else 0
461
- val successRate = if (total > 0) (successful.toFloat() / total * 100) else 0f
462
-
463
- val availableMB = appContext?.let { getAvailableMemoryMB(it) } ?: 0
464
- val totalMB = appContext?.let { getTotalMemoryMB(it) } ?: 0
465
- val usagePercent = if (totalMB > 0) ((totalMB - availableMB).toFloat() / totalMB * 100).toInt() else 0
466
-
467
- return mapOf(
468
- "totalPreloads" to total,
469
- "successfulPreloads" to successful,
470
- "failedPreloads" to failedPreloads.get(),
471
- "averageDuration" to avgDuration,
472
- "instancesUsed" to instancesUsed.get(),
473
- "instancesExpired" to instancesExpired.get(),
474
- "successRate" to successRate,
475
- "currentMaxPoolSize" to currentMaxPoolSize,
476
- "currentTTL" to currentTTL,
477
- "memoryPressureLevel" to memoryPressureLevel,
478
- "availableMemoryMB" to availableMB,
479
- "totalMemoryMB" to totalMB,
480
- "memoryUsagePercent" to usagePercent
481
- )
482
- }
483
-
484
- /**
485
- * 清理资源
486
- */
487
- fun cleanup() {
488
- clearPool()
489
- cleanupJob?.cancel()
490
- preloadScope.cancel()
491
- appContext?.unregisterComponentCallbacks(this)
492
- Log.i(TAG, "🧹 预加载管理器已清理")
493
- }
494
- }