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.
- package/android/build.gradle +3 -0
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +32 -50
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +5 -15
- package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +41 -14
- package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
- package/build/ExpoGaodeMapNaviView.js +3 -2
- package/build/ExpoGaodeMapNaviView.js.map +1 -1
- package/build/ExpoGaodeMapNavigationModule.d.ts +2 -7
- package/build/ExpoGaodeMapNavigationModule.d.ts.map +1 -1
- package/build/ExpoGaodeMapNavigationModule.js +12 -2
- package/build/ExpoGaodeMapNavigationModule.js.map +1 -1
- package/build/map/ExpoGaodeMapModule.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapModule.js +154 -16
- package/build/map/ExpoGaodeMapModule.js.map +1 -1
- package/build/map/ExpoGaodeMapOfflineModule.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapOfflineModule.js +12 -2
- package/build/map/ExpoGaodeMapOfflineModule.js.map +1 -1
- package/build/map/ExpoGaodeMapView.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapView.js +8 -2
- package/build/map/ExpoGaodeMapView.js.map +1 -1
- package/build/map/components/overlays/Circle.d.ts.map +1 -1
- package/build/map/components/overlays/Circle.js +3 -2
- package/build/map/components/overlays/Circle.js.map +1 -1
- package/build/map/components/overlays/Cluster.d.ts.map +1 -1
- package/build/map/components/overlays/Cluster.js +3 -2
- package/build/map/components/overlays/Cluster.js.map +1 -1
- package/build/map/components/overlays/HeatMap.d.ts.map +1 -1
- package/build/map/components/overlays/HeatMap.js +3 -2
- package/build/map/components/overlays/HeatMap.js.map +1 -1
- package/build/map/components/overlays/Marker.d.ts.map +1 -1
- package/build/map/components/overlays/Marker.js +3 -2
- package/build/map/components/overlays/Marker.js.map +1 -1
- package/build/map/components/overlays/MultiPoint.d.ts.map +1 -1
- package/build/map/components/overlays/MultiPoint.js +3 -2
- package/build/map/components/overlays/MultiPoint.js.map +1 -1
- package/build/map/components/overlays/Polygon.d.ts.map +1 -1
- package/build/map/components/overlays/Polygon.js +3 -2
- package/build/map/components/overlays/Polygon.js.map +1 -1
- package/build/map/components/overlays/Polyline.d.ts.map +1 -1
- package/build/map/components/overlays/Polyline.js +3 -2
- package/build/map/components/overlays/Polyline.js.map +1 -1
- package/build/map/types/common.types.d.ts +6 -0
- package/build/map/types/common.types.d.ts.map +1 -1
- package/build/map/types/common.types.js.map +1 -1
- package/build/map/types/native-module.types.d.ts +4 -1
- package/build/map/types/native-module.types.d.ts.map +1 -1
- package/build/map/types/native-module.types.js.map +1 -1
- package/build/map/utils/ErrorHandler.d.ts +6 -0
- package/build/map/utils/ErrorHandler.d.ts.map +1 -1
- package/build/map/utils/ErrorHandler.js +31 -0
- package/build/map/utils/ErrorHandler.js.map +1 -1
- package/build/map/utils/lazyNativeViewManager.d.ts +3 -0
- package/build/map/utils/lazyNativeViewManager.d.ts.map +1 -0
- package/build/map/utils/lazyNativeViewManager.js +11 -0
- package/build/map/utils/lazyNativeViewManager.js.map +1 -0
- package/ios/ExpoGaodeMapNavigation.podspec +5 -0
- package/ios/map/ExpoGaodeMapModule.swift +20 -42
- package/ios/map/ExpoGaodeMapView.swift +160 -77
- package/ios/map/GaodeMapPrivacyManager.swift +65 -0
- package/ios/map/modules/LocationManager.swift +43 -24
- package/package.json +1 -1
- package/android/src/main/java/expo/modules/gaodemap/map/MapPreloadManager.kt +0 -494
- 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
|
-
|
|
36
|
+
ensureLocationManager()?.startUpdatingLocation()
|
|
38
37
|
isLocationStarted = true
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
func stop() {
|
|
42
|
-
|
|
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
|
-
|
|
52
|
+
ensureLocationManager()?.locatingWithReGeocode = isReGeocode
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
func setDistanceFilter(_ distance: Double) {
|
|
57
|
-
|
|
56
|
+
ensureLocationManager()?.distanceFilter = distance
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
func setLocationTimeout(_ timeout: Int) {
|
|
61
|
-
|
|
60
|
+
ensureLocationManager()?.locationTimeout = timeout
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
func setReGeocodeTimeout(_ timeout: Int) {
|
|
65
|
-
|
|
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
|
-
|
|
78
|
+
ensureLocationManager()?.desiredAccuracy = value
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
func setPausesLocationUpdatesAutomatically(_ pauses: Bool) {
|
|
83
|
-
|
|
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
|
-
|
|
93
|
+
ensureLocationManager()?.allowsBackgroundLocationUpdates = allows
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
func setGeoLanguage(_ language: Int) {
|
|
98
97
|
switch language {
|
|
99
|
-
case 0:
|
|
100
|
-
case 1:
|
|
101
|
-
case 2:
|
|
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
|
-
|
|
108
|
+
ensureLocationManager()?.startUpdatingHeading()
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
func stopUpdatingHeading() {
|
|
113
|
-
|
|
112
|
+
ensureLocationManager()?.stopUpdatingHeading()
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
// MARK: - 初始化
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
locationManager
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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,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
|
-
}
|