expo-gaode-map-navigation 1.1.5 → 1.1.6

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 (146) hide show
  1. package/README.md +213 -73
  2. package/android/build.gradle +10 -0
  3. package/android/src/main/cpp/CMakeLists.txt +24 -0
  4. package/android/src/main/cpp/cluster_jni.cpp +848 -0
  5. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +616 -92
  6. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +493 -0
  7. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +230 -14
  8. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapViewModule.kt +37 -27
  9. package/android/src/main/java/expo/modules/gaodemap/map/MapPreloadManager.kt +494 -0
  10. package/android/src/main/java/expo/modules/gaodemap/map/companion/BitmapDescriptorCache.kt +30 -0
  11. package/android/src/main/java/expo/modules/gaodemap/map/companion/IconBitmapCache.kt +37 -0
  12. package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +76 -0
  13. package/android/src/main/java/expo/modules/gaodemap/map/modules/LocationManager.kt +15 -3
  14. package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +4 -59
  15. package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleView.kt +9 -12
  16. package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleViewModule.kt +5 -6
  17. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +539 -66
  18. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterViewModule.kt +17 -1
  19. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +165 -33
  20. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +15 -3
  21. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +1249 -672
  22. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +40 -17
  23. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointView.kt +177 -22
  24. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointViewModule.kt +11 -3
  25. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonView.kt +57 -14
  26. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonViewModule.kt +9 -5
  27. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineView.kt +90 -63
  28. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineViewModule.kt +7 -3
  29. package/android/src/main/java/expo/modules/gaodemap/map/services/LocationForegroundService.kt +3 -2
  30. package/android/src/main/java/expo/modules/gaodemap/map/utils/BitmapDescriptorCache.kt +20 -0
  31. package/android/src/main/java/expo/modules/gaodemap/map/utils/ClusterNative.kt +13 -0
  32. package/android/src/main/java/expo/modules/gaodemap/map/utils/ColorParser.kt +20 -0
  33. package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +515 -0
  34. package/android/src/main/java/expo/modules/gaodemap/map/utils/LatLngParser.kt +91 -0
  35. package/android/src/main/java/expo/modules/gaodemap/map/utils/PermissionHelper.kt +248 -0
  36. package/build/ExpoGaodeMapNaviView.d.ts +7 -7
  37. package/build/ExpoGaodeMapNaviView.js +10 -11
  38. package/build/ExpoGaodeMapNavigationModule.d.ts +2 -1
  39. package/build/index.d.ts +35 -33
  40. package/build/index.js +70 -106
  41. package/build/map/ExpoGaodeMapModule.d.ts +2 -201
  42. package/build/map/ExpoGaodeMapModule.js +586 -18
  43. package/build/map/ExpoGaodeMapOfflineModule.d.ts +139 -0
  44. package/build/map/ExpoGaodeMapOfflineModule.js +8 -0
  45. package/build/map/ExpoGaodeMapView.js +66 -58
  46. package/build/map/components/FoldableMapView.d.ts +38 -0
  47. package/build/map/components/FoldableMapView.js +209 -0
  48. package/build/map/components/MapContext.d.ts +12 -0
  49. package/build/map/components/MapContext.js +54 -0
  50. package/build/map/components/MapUI.d.ts +18 -0
  51. package/build/map/components/MapUI.js +29 -0
  52. package/build/map/components/overlays/Circle.js +34 -3
  53. package/build/map/components/overlays/Cluster.d.ts +3 -1
  54. package/build/map/components/overlays/Cluster.js +31 -2
  55. package/build/map/components/overlays/HeatMap.d.ts +3 -1
  56. package/build/map/components/overlays/HeatMap.js +33 -3
  57. package/build/map/components/overlays/Marker.d.ts +1 -1
  58. package/build/map/components/overlays/Marker.js +37 -32
  59. package/build/map/components/overlays/MultiPoint.js +1 -1
  60. package/build/map/components/overlays/Polygon.js +30 -3
  61. package/build/map/components/overlays/Polyline.js +36 -3
  62. package/build/map/index.d.ts +25 -5
  63. package/build/map/index.js +59 -18
  64. package/build/map/types/common.types.d.ts +40 -0
  65. package/build/map/types/common.types.js +0 -4
  66. package/build/map/types/index.d.ts +3 -2
  67. package/build/map/types/map-view.types.d.ts +108 -3
  68. package/build/map/types/native-module.types.d.ts +363 -0
  69. package/build/map/types/native-module.types.js +5 -0
  70. package/build/map/types/offline.types.d.ts +132 -0
  71. package/build/map/types/offline.types.js +5 -0
  72. package/build/map/types/overlays.types.d.ts +137 -24
  73. package/build/map/utils/ErrorHandler.d.ts +110 -0
  74. package/build/map/utils/ErrorHandler.js +421 -0
  75. package/build/map/utils/GeoUtils.d.ts +20 -0
  76. package/build/map/utils/GeoUtils.js +76 -0
  77. package/build/map/utils/OfflineMapManager.d.ts +148 -0
  78. package/build/map/utils/OfflineMapManager.js +217 -0
  79. package/build/map/utils/PermissionUtils.d.ts +91 -0
  80. package/build/map/utils/PermissionUtils.js +255 -0
  81. package/build/map/utils/PlatformDetector.d.ts +102 -0
  82. package/build/map/utils/PlatformDetector.js +186 -0
  83. package/build/types/index.d.ts +1 -0
  84. package/build/types/index.js +1 -0
  85. package/build/types/native-module.types.d.ts +69 -0
  86. package/build/types/native-module.types.js +2 -0
  87. package/build/types/naviview.types.d.ts +1 -1
  88. package/expo-module.config.json +12 -10
  89. package/ios/ExpoGaodeMapNavigation.podspec +9 -0
  90. package/ios/map/ExpoGaodeMapModule.swift +485 -75
  91. package/ios/map/ExpoGaodeMapOfflineModule.swift +479 -0
  92. package/ios/map/ExpoGaodeMapView.swift +611 -62
  93. package/ios/map/ExpoGaodeMapViewModule.swift +48 -26
  94. package/ios/map/MapPreloadManager.swift +348 -0
  95. package/ios/map/cpp/ClusterEngine.cpp +110 -0
  96. package/ios/map/cpp/ClusterEngine.hpp +20 -0
  97. package/ios/map/cpp/ColorParser.cpp +135 -0
  98. package/ios/map/cpp/ColorParser.hpp +14 -0
  99. package/ios/map/cpp/GeometryEngine.cpp +574 -0
  100. package/ios/map/cpp/GeometryEngine.hpp +159 -0
  101. package/ios/map/cpp/QuadTree.cpp +92 -0
  102. package/ios/map/cpp/QuadTree.hpp +42 -0
  103. package/ios/map/cpp/README.md +55 -0
  104. package/ios/map/managers/UIManager.swift +72 -1
  105. package/ios/map/modules/LocationManager.swift +123 -166
  106. package/ios/map/overlays/CircleView.swift +16 -32
  107. package/ios/map/overlays/CircleViewModule.swift +12 -12
  108. package/ios/map/overlays/ClusterAnnotation.swift +32 -0
  109. package/ios/map/overlays/ClusterView.swift +331 -45
  110. package/ios/map/overlays/ClusterViewModule.swift +20 -6
  111. package/ios/map/overlays/HeatMapView.swift +135 -32
  112. package/ios/map/overlays/HeatMapViewModule.swift +20 -8
  113. package/ios/map/overlays/MarkerView.swift +613 -130
  114. package/ios/map/overlays/MarkerViewModule.swift +38 -18
  115. package/ios/map/overlays/MultiPointView.swift +168 -10
  116. package/ios/map/overlays/MultiPointViewModule.swift +27 -5
  117. package/ios/map/overlays/PolygonView.swift +62 -23
  118. package/ios/map/overlays/PolygonViewModule.swift +18 -12
  119. package/ios/map/overlays/PolylineView.swift +21 -13
  120. package/ios/map/overlays/PolylineViewModule.swift +18 -12
  121. package/ios/map/utils/ClusterNative.h +96 -0
  122. package/ios/map/utils/ClusterNative.mm +377 -0
  123. package/ios/map/utils/ColorParser.swift +12 -1
  124. package/ios/map/utils/CppBridging.mm +13 -0
  125. package/ios/map/utils/GeometryUtils.swift +34 -0
  126. package/ios/map/utils/LatLngParser.swift +87 -0
  127. package/ios/map/utils/PermissionManager.swift +135 -6
  128. package/package.json +3 -2
  129. package/shared/cpp/ClusterEngine.cpp +110 -0
  130. package/shared/cpp/ClusterEngine.hpp +20 -0
  131. package/shared/cpp/ColorParser.cpp +135 -0
  132. package/shared/cpp/ColorParser.hpp +14 -0
  133. package/shared/cpp/GeometryEngine.cpp +574 -0
  134. package/shared/cpp/GeometryEngine.hpp +159 -0
  135. package/shared/cpp/QuadTree.cpp +92 -0
  136. package/shared/cpp/QuadTree.hpp +42 -0
  137. package/shared/cpp/README.md +55 -0
  138. package/shared/cpp/tests/benchmark_js.js +41 -0
  139. package/shared/cpp/tests/run.sh +17 -0
  140. package/shared/cpp/tests/test_main.cpp +276 -0
  141. package/build/map/ExpoGaodeMap.types.d.ts +0 -41
  142. package/build/map/ExpoGaodeMap.types.js +0 -24
  143. package/build/map/utils/EventManager.d.ts +0 -10
  144. package/build/map/utils/EventManager.js +0 -26
  145. package/build/map/utils/ModuleLoader.d.ts +0 -73
  146. package/build/map/utils/ModuleLoader.js +0 -112
@@ -0,0 +1,494 @@
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
+ }
@@ -0,0 +1,30 @@
1
+ package expo.modules.gaodemap.map.companion
2
+
3
+ import com.amap.api.maps.model.BitmapDescriptor
4
+ import com.amap.api.maps.model.BitmapDescriptorFactory
5
+ import android.graphics.Bitmap
6
+ import java.util.concurrent.ConcurrentHashMap
7
+
8
+ object BitmapDescriptorCache {
9
+
10
+ private val cache = ConcurrentHashMap<String, BitmapDescriptor>()
11
+
12
+ fun get(key: String): BitmapDescriptor? = cache[key]
13
+
14
+ fun put(key: String, bitmap: Bitmap) {
15
+ val descriptor = BitmapDescriptorFactory.fromBitmap(bitmap)
16
+ cache[key] = descriptor
17
+ }
18
+
19
+ fun putDescriptor(key: String, descriptor: BitmapDescriptor) {
20
+ cache[key] = descriptor
21
+ }
22
+
23
+ fun clear() {
24
+ cache.clear()
25
+ }
26
+
27
+ fun remove(key: String) {
28
+ cache.remove(key)
29
+ }
30
+ }
@@ -0,0 +1,37 @@
1
+ package expo.modules.gaodemap.map.companion
2
+
3
+ import android.graphics.Bitmap
4
+ import android.util.LruCache
5
+
6
+ /**
7
+ * 简单的 Bitmap LRU 缓存。
8
+ * key 的格式使用: "<fingerprint>|<width>x<height>"
9
+ */
10
+ object IconBitmapCache {
11
+ private val maxMemoryKb = (Runtime.getRuntime().maxMemory() / 1024).toInt()
12
+ // 使用 1/8 可用内存作为缓存容积(可按需调整)
13
+ private val cacheSizeKb = maxMemoryKb / 8
14
+
15
+ private val cache = object : LruCache<String, Bitmap>(cacheSizeKb) {
16
+ override fun sizeOf(key: String, value: Bitmap): Int {
17
+ // 以 KB 为单位
18
+ return value.byteCount / 1024
19
+ }
20
+ }
21
+
22
+ fun put(key: String, bitmap: Bitmap) {
23
+ if (get(key) == null) {
24
+ cache.put(key, bitmap)
25
+ }
26
+ }
27
+
28
+ fun get(key: String): Bitmap? = cache.get(key)
29
+
30
+ fun remove(key: String) {
31
+ cache.remove(key)
32
+ }
33
+
34
+ fun clear() {
35
+ cache.evictAll()
36
+ }
37
+ }
@@ -1,5 +1,6 @@
1
1
  package expo.modules.gaodemap.map.managers
2
2
 
3
+ import android.annotation.SuppressLint
3
4
  import android.content.Context
4
5
  import android.graphics.BitmapFactory
5
6
  import android.location.Location
@@ -161,6 +162,7 @@ class UIManager(private val aMap: AMap, private val context: Context) : Location
161
162
  * 设置用户位置样式
162
163
  * 统一 iOS 和 Android 的 API
163
164
  */
165
+ @SuppressLint("DiscouragedApi")
164
166
  fun setUserLocationRepresentation(config: Map<String, Any>) {
165
167
  if (currentLocationStyle == null) {
166
168
  currentLocationStyle = MyLocationStyle().apply {
@@ -172,6 +174,30 @@ class UIManager(private val aMap: AMap, private val context: Context) : Location
172
174
 
173
175
  val style = currentLocationStyle!!
174
176
 
177
+ // 定位蓝点展现模式 (locationType) - Android 支持8种模式
178
+ val locationType = config["locationType"] as? String
179
+ if (locationType != null) {
180
+ val locationTypeValue = when (locationType) {
181
+ "SHOW" -> MyLocationStyle.LOCATION_TYPE_SHOW
182
+ "LOCATE" -> MyLocationStyle.LOCATION_TYPE_LOCATE
183
+ "FOLLOW" -> MyLocationStyle.LOCATION_TYPE_FOLLOW
184
+ "MAP_ROTATE" -> MyLocationStyle.LOCATION_TYPE_MAP_ROTATE
185
+ "LOCATION_ROTATE" -> MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE
186
+ "LOCATION_ROTATE_NO_CENTER" -> MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER
187
+ "FOLLOW_NO_CENTER" -> MyLocationStyle.LOCATION_TYPE_FOLLOW_NO_CENTER
188
+ "MAP_ROTATE_NO_CENTER" -> MyLocationStyle.LOCATION_TYPE_MAP_ROTATE_NO_CENTER
189
+ else -> MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE // 默认值
190
+ }
191
+ style.myLocationType(locationTypeValue)
192
+ }
193
+
194
+ // 是否显示定位蓝点 (showMyLocation) - Android 5.1.0+ 支持
195
+ // 注意:这个属性在 iOS 中没有对应项,是 Android 特有的
196
+ val showMyLocation = config["showMyLocation"] as? Boolean
197
+ if (showMyLocation != null) {
198
+ style.showMyLocation(showMyLocation)
199
+ }
200
+
175
201
  // 是否显示精度圈 (showsAccuracyRing) - 先处理这个,设置默认值
176
202
  val showsAccuracyRing = config["showsAccuracyRing"] as? Boolean ?: true
177
203
  if (!showsAccuracyRing) {
@@ -198,6 +224,13 @@ class UIManager(private val aMap: AMap, private val context: Context) : Location
198
224
  }
199
225
  }
200
226
 
227
+ // 定位图标锚点 (anchor) - Android 支持
228
+ val anchorU = config["anchorU"] as? Number
229
+ val anchorV = config["anchorV"] as? Number
230
+ if (anchorU != null && anchorV != null) {
231
+ style.anchor(anchorU.toFloat(), anchorV.toFloat())
232
+ }
233
+
201
234
  // 自定义图标 (image)
202
235
  val imagePath = config["image"] as? String
203
236
  if (imagePath != null && imagePath.isNotEmpty()) {
@@ -316,4 +349,47 @@ class UIManager(private val aMap: AMap, private val context: Context) : Location
316
349
  else -> AMap.MAP_TYPE_NORMAL // 标准地图
317
350
  }
318
351
  }
352
+
353
+ // ==================== 自定义地图样式 ====================
354
+
355
+ /**
356
+ * 设置自定义地图样式
357
+ * @param styleData 样式数据,支持以下格式:
358
+ * - styleId: String - 在线样式ID(从高德开放平台获取)
359
+ * - styleDataPath: String - 本地样式文件路径
360
+ * - extraStyleDataPath: String - 额外样式文件路径(可选)
361
+ */
362
+ fun setCustomMapStyle(styleData: Map<String, Any>) {
363
+ // 在线样式 ID
364
+ val styleId = styleData["styleId"] as? String
365
+ if (!styleId.isNullOrEmpty()) {
366
+ val options = com.amap.api.maps.model.CustomMapStyleOptions()
367
+ options.isEnable = true
368
+ options.styleId = styleId
369
+ aMap.setCustomMapStyle(options)
370
+ return
371
+ }
372
+
373
+ // 本地样式文件
374
+ val styleDataPath = styleData["styleDataPath"] as? String
375
+ if (!styleDataPath.isNullOrEmpty()) {
376
+ val options = com.amap.api.maps.model.CustomMapStyleOptions()
377
+ options.isEnable = true
378
+ options.styleDataPath = styleDataPath
379
+
380
+ // 额外样式文件(可选)
381
+ val extraPath = styleData["extraStyleDataPath"] as? String
382
+ if (!extraPath.isNullOrEmpty()) {
383
+ options.styleExtraPath = extraPath
384
+ }
385
+
386
+ aMap.setCustomMapStyle(options)
387
+ return
388
+ }
389
+
390
+ // 如果没有提供任何样式,禁用自定义样式
391
+ val options = com.amap.api.maps.model.CustomMapStyleOptions()
392
+ options.isEnable = false
393
+ aMap.setCustomMapStyle(options)
394
+ }
319
395
  }
@@ -4,6 +4,7 @@ import android.content.Context
4
4
  import com.amap.api.location.AMapLocation
5
5
  import com.amap.api.location.AMapLocationClient
6
6
  import com.amap.api.location.AMapLocationClientOption
7
+ import com.amap.api.maps.CoordinateConverter
7
8
  import com.amap.api.maps.model.LatLng
8
9
  import expo.modules.kotlin.Promise
9
10
  import expo.modules.gaodemap.map.services.LocationForegroundService
@@ -135,11 +136,22 @@ class LocationManager(context: Context) {
135
136
 
136
137
  try {
137
138
  val sourceLatLng = LatLng(lat, lng)
138
- // 高德 SDK 会自动处理坐标转换
139
+ val converter = CoordinateConverter(appContext)
140
+ // 0: GPS/Google, 1: MapBar, 2: Baidu, 3: MapABC/SoSo
141
+ converter.from(when(type) {
142
+ 0 -> CoordinateConverter.CoordType.GPS
143
+ 1 -> CoordinateConverter.CoordType.MAPBAR
144
+ 2 -> CoordinateConverter.CoordType.BAIDU
145
+ 3 -> CoordinateConverter.CoordType.MAPABC
146
+ else -> CoordinateConverter.CoordType.GPS
147
+ })
148
+ converter.coord(sourceLatLng)
149
+ val desLatLng = converter.convert()
150
+
139
151
  promise.resolve(
140
152
  mapOf(
141
- "latitude" to sourceLatLng.latitude,
142
- "longitude" to sourceLatLng.longitude
153
+ "latitude" to desLatLng.latitude,
154
+ "longitude" to desLatLng.longitude
143
155
  )
144
156
  )
145
157
  } catch (e: Exception) {