mapbox-android-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMapbox Android Integration Patterns
Mapbox Android集成模式
Official integration patterns for Mapbox Maps SDK on Android. Covers Kotlin, Jetpack Compose, View system, proper lifecycle management, token handling, offline maps, and mobile-specific optimizations.
Use this skill when:
- Setting up Mapbox Maps SDK for Android in a new or existing project
- Integrating maps with Jetpack Compose or View system
- Implementing proper lifecycle management and cleanup
- Managing tokens securely in Android apps
- Working with offline maps and caching
- Integrating Navigation SDK
- Optimizing for battery life and memory usage
- Debugging crashes, memory leaks, or performance issues
这是适用于Android平台的Mapbox Maps SDK官方集成模式,涵盖Kotlin、Jetpack Compose、View系统、规范的生命周期管理、令牌处理、离线地图及移动端专属优化方案。
以下场景适用本技能:
- 在新项目或现有项目中搭建Mapbox Maps SDK for Android环境
- 将地图与Jetpack Compose或View系统集成
- 实现规范的生命周期管理与资源清理
- 在Android应用中安全管理令牌
- 处理离线地图与缓存相关需求
- 集成Navigation SDK
- 优化电池续航与内存占用
- 调试崩溃、内存泄漏或性能问题
Core Integration Patterns
核心集成模式
Jetpack Compose Pattern (Modern)
Jetpack Compose模式(现代化方案)
Modern approach using Jetpack Compose and Kotlin
kotlin
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import com.mapbox.maps.MapView
import com.mapbox.maps.Style
import com.mapbox.maps.plugin.animation.camera
import com.mapbox.geojson.Point
@Composable
fun MapboxMap(
modifier: Modifier = Modifier,
center: Point,
zoom: Double,
onMapReady: (MapView) -> Unit = {}
) {
val mapView = rememberMapViewWithLifecycle()
AndroidView(
modifier = modifier,
factory = { mapView },
update = { view ->
// Update camera when state changes
view.getMapboxMap().apply {
setCamera(
CameraOptions.Builder()
.center(center)
.zoom(zoom)
.build()
)
}
}
)
LaunchedEffect(mapView) {
mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS) {
onMapReady(mapView)
}
}
}
@Composable
fun rememberMapViewWithLifecycle(): MapView {
val context = LocalContext.current
val mapView = remember {
MapView(context).apply {
id = View.generateViewId()
}
}
// Lifecycle-aware cleanup
DisposableEffect(mapView) {
onDispose {
mapView.onDestroy()
}
}
return mapView
}
// Usage in Composable
@Composable
fun MapScreen() {
var center by remember { mutableStateOf(Point.fromLngLat(-122.4194, 37.7749)) }
var zoom by remember { mutableStateOf(12.0) }
MapboxMap(
modifier = Modifier.fillMaxSize(),
center = center,
zoom = zoom,
onMapReady = { mapView ->
// Add sources and layers
}
)
}Key points:
- Use to integrate MapView in Compose
AndroidView - Use to preserve MapView across recompositions
remember - Use for proper lifecycle cleanup
DisposableEffect - Handle state updates in block
update
基于Jetpack Compose与Kotlin的现代化实现方式
kotlin
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import com.mapbox.maps.MapView
import com.mapbox.maps.Style
import com.mapbox.maps.plugin.animation.camera
import com.mapbox.geojson.Point
@Composable
fun MapboxMap(
modifier: Modifier = Modifier,
center: Point,
zoom: Double,
onMapReady: (MapView) -> Unit = {}
) {
val mapView = rememberMapViewWithLifecycle()
AndroidView(
modifier = modifier,
factory = { mapView },
update = { view ->
// 状态变更时更新相机
view.getMapboxMap().apply {
setCamera(
CameraOptions.Builder()
.center(center)
.zoom(zoom)
.build()
)
}
}
)
LaunchedEffect(mapView) {
mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS) {
onMapReady(mapView)
}
}
}
@Composable
fun rememberMapViewWithLifecycle(): MapView {
val context = LocalContext.current
val mapView = remember {
MapView(context).apply {
id = View.generateViewId()
}
}
// 感知生命周期的资源清理
DisposableEffect(mapView) {
onDispose {
mapView.onDestroy()
}
}
return mapView
}
// 在Composable中的使用示例
@Composable
fun MapScreen() {
var center by remember { mutableStateOf(Point.fromLngLat(-122.4194, 37.7749)) }
var zoom by remember { mutableStateOf(12.0) }
MapboxMap(
modifier = Modifier.fillMaxSize(),
center = center,
zoom = zoom,
onMapReady = { mapView ->
// 添加数据源与图层
}
)
}核心要点:
- 使用在Compose中集成MapView
AndroidView - 使用确保MapView在重组时保留
remember - 使用实现规范的生命周期清理
DisposableEffect - 在代码块中处理状态更新
update
View System Pattern (Classic)
View系统模式(传统方案)
Traditional Android View system with proper lifecycle
kotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.mapbox.maps.MapView
import com.mapbox.maps.Style
import com.mapbox.maps.plugin.gestures.addOnMapClickListener
import com.mapbox.geojson.Point
class MapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
mapView = findViewById(R.id.mapView)
mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS) { style ->
// Map loaded, add sources and layers
setupMap(style)
}
// Add click listener
mapView.getMapboxMap().addOnMapClickListener { point ->
handleMapClick(point)
true
}
}
private fun setupMap(style: Style) {
// Add your custom sources and layers
}
private fun handleMapClick(point: Point) {
// Handle map clicks
}
// CRITICAL: Lifecycle methods for proper cleanup
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onStop() {
super.onStop()
mapView.onStop()
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
}XML layout (activity_map.xml):
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mapbox.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>Key points:
- Call ,
mapView.onStart(),onStop(),onDestroy()in corresponding Activity methodsonLowMemory() - Wait for style to load before adding layers
- Store MapView reference as lateinit var (will be initialized in onCreate)
遵循生命周期规范的传统Android View系统实现
kotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.mapbox.maps.MapView
import com.mapbox.maps.Style
import com.mapbox.maps.plugin.gestures.addOnMapClickListener
import com.mapbox.geojson.Point
class MapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
mapView = findViewById(R.id.mapView)
mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS) { style ->
// 地图加载完成,添加数据源与图层
setupMap(style)
}
// 添加点击监听器
mapView.getMapboxMap().addOnMapClickListener { point ->
handleMapClick(point)
true
}
}
private fun setupMap(style: Style) {
// 添加自定义数据源与图层
}
private fun handleMapClick(point: Point) {
// 处理地图点击事件
}
// 关键:调用对应生命周期方法以实现规范清理
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onStop() {
super.onStop()
mapView.onStop()
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
}XML布局文件(activity_map.xml):
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mapbox.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>核心要点:
- 在Activity对应生命周期方法中调用、
mapView.onStart()、onStop()、onDestroy()onLowMemory() - 等待样式加载完成后再添加图层
- 使用lateinit var存储MapView引用(将在onCreate中初始化)
Fragment Pattern
Fragment模式
kotlin
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.mapbox.maps.MapView
import com.mapbox.maps.Style
class MapFragment : Fragment() {
private var mapView: MapView? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = inflater.inflate(R.layout.fragment_map, container, false)
mapView = view.findViewById(R.id.mapView)
mapView?.getMapboxMap()?.loadStyleUri(Style.MAPBOX_STREETS) { style ->
setupMap(style)
}
return view
}
private fun setupMap(style: Style) {
// Add sources and layers
}
override fun onStart() {
super.onStart()
mapView?.onStart()
}
override fun onStop() {
super.onStop()
mapView?.onStop()
}
override fun onDestroyView() {
super.onDestroyView()
mapView?.onDestroy()
mapView = null
}
override fun onLowMemory() {
super.onLowMemory()
mapView?.onLowMemory()
}
}Key points:
- Set to null in
mapViewto prevent leaksonDestroyView() - Use nullable for safety
mapView? - Call lifecycle methods appropriately
kotlin
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.mapbox.maps.MapView
import com.mapbox.maps.Style
class MapFragment : Fragment() {
private var mapView: MapView? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = inflater.inflate(R.layout.fragment_map, container, false)
mapView = view.findViewById(R.id.mapView)
mapView?.getMapboxMap()?.loadStyleUri(Style.MAPBOX_STREETS) { style ->
setupMap(style)
}
return view
}
private fun setupMap(style: Style) {
// 添加数据源与图层
}
override fun onStart() {
super.onStart()
mapView?.onStart()
}
override fun onStop() {
super.onStop()
mapView?.onStop()
}
override fun onDestroyView() {
super.onDestroyView()
mapView?.onDestroy()
mapView = null
}
override fun onLowMemory() {
super.onLowMemory()
mapView?.onLowMemory()
}
}核心要点:
- 在中将
onDestroyView()置为null以避免内存泄漏mapView - 使用可空类型保证安全性
mapView? - 合理调用生命周期方法
Token Management
令牌管理
✅ Recommended: String Resources with BuildConfig
✅ 推荐方案:结合BuildConfig的字符串资源
1. Add to (DO NOT commit):
local.propertiesproperties
undefined1. 添加至(请勿提交至版本控制):
local.propertiesproperties
undefinedlocal.properties (add to .gitignore)
local.properties(需添加至.gitignore)
MAPBOX_ACCESS_TOKEN=pk.your_token_here
**2. Configure in `build.gradle.kts` (Module):**
```kotlin
android {
defaultConfig {
// Read from local.properties
val properties = Properties()
properties.load(project.rootProject.file("local.properties").inputStream())
buildConfigField(
"String",
"MAPBOX_ACCESS_TOKEN",
"\"${properties.getProperty("MAPBOX_ACCESS_TOKEN", "")}\""
)
// Also add to resources for SDK
resValue(
"string",
"mapbox_access_token",
properties.getProperty("MAPBOX_ACCESS_TOKEN", "")
)
}
buildFeatures {
buildConfig = true
}
}3. Add to :
.gitignoregitignore
local.properties4. Usage in code:
kotlin
import com.yourapp.BuildConfig
// Access token automatically picked up from resources
// No need to set manually if in string resources
// Or access programmatically:
val token = BuildConfig.MAPBOX_ACCESS_TOKENWhy this pattern:
- Token not in source code or version control
- Works in local development and CI/CD (via environment variables)
- Automatically injected at build time
- No hardcoded secrets
MAPBOX_ACCESS_TOKEN=pk.your_token_here
**2. 在`build.gradle.kts`(Module层级)中配置:**
```kotlin
android {
defaultConfig {
// 从local.properties读取配置
val properties = Properties()
properties.load(project.rootProject.file("local.properties").inputStream())
buildConfigField(
"String",
"MAPBOX_ACCESS_TOKEN",
"\"${properties.getProperty("MAPBOX_ACCESS_TOKEN", "")}\""
)
// 同时添加至资源文件供SDK使用
resValue(
"string",
"mapbox_access_token",
properties.getProperty("MAPBOX_ACCESS_TOKEN", "")
)
}
buildFeatures {
buildConfig = true
}
}3. 添加至:
.gitignoregitignore
local.properties4. 代码中的使用方式:
kotlin
import com.yourapp.BuildConfig
// SDK会自动从资源文件中读取令牌
// 若已配置在字符串资源中,无需手动设置
// 或者通过代码直接获取:
val token = BuildConfig.MAPBOX_ACCESS_TOKEN该模式优势:
- 令牌不会出现在源代码或版本控制中
- 适用于本地开发与CI/CD流程(通过环境变量注入)
- 构建时自动注入
- 无硬编码密钥风险
❌ Anti-Pattern: Hardcoded Tokens
❌ 反模式:硬编码令牌
kotlin
// ❌ NEVER DO THIS - Token in source code
MapboxOptions.accessToken = "pk.YOUR_MAPBOX_TOKEN_HERE"kotlin
// ❌ 绝对禁止此操作 - 令牌直接写入源代码
MapboxOptions.accessToken = "pk.YOUR_MAPBOX_TOKEN_HERE"Memory Management and Lifecycle
内存管理与生命周期
✅ Proper Lifecycle Management
✅ 规范的生命周期管理
kotlin
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.mapbox.maps.MapView
class MapLifecycleObserver(
private val mapView: MapView
) : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
mapView.onStart()
}
override fun onStop(owner: LifecycleOwner) {
mapView.onStop()
}
override fun onDestroy(owner: LifecycleOwner) {
mapView.onDestroy()
}
fun onLowMemory() {
mapView.onLowMemory()
}
}
// Usage in Activity/Fragment
class MapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var lifecycleObserver: MapLifecycleObserver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
mapView = findViewById(R.id.mapView)
lifecycleObserver = MapLifecycleObserver(mapView)
// Automatically handle lifecycle
lifecycle.addObserver(lifecycleObserver)
}
override fun onLowMemory() {
super.onLowMemory()
lifecycleObserver.onLowMemory()
}
}kotlin
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.mapbox.maps.MapView
class MapLifecycleObserver(
private val mapView: MapView
) : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
mapView.onStart()
}
override fun onStop(owner: LifecycleOwner) {
mapView.onStop()
}
override fun onDestroy(owner: LifecycleOwner) {
mapView.onDestroy()
}
fun onLowMemory() {
mapView.onLowMemory()
}
}
// 在Activity/Fragment中的使用方式
class MapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var lifecycleObserver: MapLifecycleObserver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
mapView = findViewById(R.id.mapView)
lifecycleObserver = MapLifecycleObserver(mapView)
// 自动处理生命周期
lifecycle.addObserver(lifecycleObserver)
}
override fun onLowMemory() {
super.onLowMemory()
lifecycleObserver.onLowMemory()
}
}✅ ViewModel Pattern
✅ ViewModel模式
kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mapbox.geojson.Point
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
data class MapState(
val center: Point = Point.fromLngLat(-122.4194, 37.7749),
val zoom: Double = 12.0,
val markers: List<Point> = emptyList()
)
class MapViewModel : ViewModel() {
private val _mapState = MutableStateFlow(MapState())
val mapState: StateFlow<MapState> = _mapState
fun updateCenter(point: Point) {
_mapState.value = _mapState.value.copy(center = point)
}
fun addMarker(point: Point) {
val currentMarkers = _mapState.value.markers
_mapState.value = _mapState.value.copy(
markers = currentMarkers + point
)
}
fun loadData() {
viewModelScope.launch {
// Load data from repository
// Update state when ready
}
}
}Benefits:
- State survives configuration changes
- Separates business logic from UI
- Lifecycle-aware
- Easy to test
kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mapbox.geojson.Point
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
data class MapState(
val center: Point = Point.fromLngLat(-122.4194, 37.7749),
val zoom: Double = 12.0,
val markers: List<Point> = emptyList()
)
class MapViewModel : ViewModel() {
private val _mapState = MutableStateFlow(MapState())
val mapState: StateFlow<MapState> = _mapState
fun updateCenter(point: Point) {
_mapState.value = _mapState.value.copy(center = point)
}
fun addMarker(point: Point) {
val currentMarkers = _mapState.value.markers
_mapState.value = _mapState.value.copy(
markers = currentMarkers + point
)
}
fun loadData() {
viewModelScope.launch {
// 从仓库加载数据
// 数据就绪后更新状态
}
}
}优势:
- 配置变更时状态得以保留
- 业务逻辑与UI解耦
- 感知生命周期
- 易于测试
Offline Maps
离线地图
Download Region for Offline Use
下载指定区域供离线使用
kotlin
import com.mapbox.maps.TileStore
import com.mapbox.maps.TileRegionLoadOptions
import com.mapbox.common.TileRegion
import com.mapbox.geojson.Point
import com.mapbox.bindgen.Expected
class OfflineManager(private val context: Context) {
private val tileStore = TileStore.create()
fun downloadRegion(
regionId: String,
bounds: CoordinateBounds,
minZoom: Int = 0,
maxZoom: Int = 16,
onProgress: (Float) -> Unit,
onComplete: (Result<Unit>) -> Unit
) {
val tilesetDescriptor = tileStore.createDescriptor(
TilesetDescriptorOptions.Builder()
.styleURI(Style.MAPBOX_STREETS)
.minZoom(minZoom.toByte())
.maxZoom(maxZoom.toByte())
.build()
)
val loadOptions = TileRegionLoadOptions.Builder()
.geometry(bounds.toGeometry())
.descriptors(listOf(tilesetDescriptor))
.acceptExpired(false)
.build()
val cancelable = tileStore.loadTileRegion(
regionId,
loadOptions,
{ progress ->
val percent = (progress.completedResourceCount.toFloat() /
progress.requiredResourceCount.toFloat()) * 100
onProgress(percent)
}
) { expected ->
if (expected.isValue) {
onComplete(Result.success(Unit))
} else {
onComplete(Result.failure(Exception(expected.error?.message)))
}
}
}
fun getTileRegions(callback: (List<TileRegion>) -> Unit) {
tileStore.getAllTileRegions { expected ->
if (expected.isValue) {
callback(expected.value ?: emptyList())
} else {
callback(emptyList())
}
}
}
fun removeTileRegion(regionId: String, callback: (Boolean) -> Unit) {
tileStore.removeTileRegion(regionId)
callback(true)
}
fun estimateStorageSize(
bounds: CoordinateBounds,
minZoom: Int,
maxZoom: Int
): Long {
// Rough estimate: 50 KB per tile average
val tileCount = estimateTileCount(bounds, minZoom, maxZoom)
return tileCount * 50_000L // bytes
}
private fun estimateTileCount(
bounds: CoordinateBounds,
minZoom: Int,
maxZoom: Int
): Long {
// Simplified tile count estimation
var count = 0L
for (zoom in minZoom..maxZoom) {
val tilesAtZoom = Math.pow(4.0, zoom.toDouble()).toLong()
count += tilesAtZoom
}
return count
}
}Key considerations:
- Battery impact: Downloading uses significant battery
- Storage limits: Monitor available disk space
- Zoom levels: Higher zoom = more tiles = more storage
- Network type: WiFi vs cellular
kotlin
import com.mapbox.maps.TileStore
import com.mapbox.maps.TileRegionLoadOptions
import com.mapbox.common.TileRegion
import com.mapbox.geojson.Point
import com.mapbox.bindgen.Expected
class OfflineManager(private val context: Context) {
private val tileStore = TileStore.create()
fun downloadRegion(
regionId: String,
bounds: CoordinateBounds,
minZoom: Int = 0,
maxZoom: Int = 16,
onProgress: (Float) -> Unit,
onComplete: (Result<Unit>) -> Unit
) {
val tilesetDescriptor = tileStore.createDescriptor(
TilesetDescriptorOptions.Builder()
.styleURI(Style.MAPBOX_STREETS)
.minZoom(minZoom.toByte())
.maxZoom(maxZoom.toByte())
.build()
)
val loadOptions = TileRegionLoadOptions.Builder()
.geometry(bounds.toGeometry())
.descriptors(listOf(tilesetDescriptor))
.acceptExpired(false)
.build()
val cancelable = tileStore.loadTileRegion(
regionId,
loadOptions,
{ progress ->
val percent = (progress.completedResourceCount.toFloat() /
progress.requiredResourceCount.toFloat()) * 100
onProgress(percent)
}
) { expected ->
if (expected.isValue) {
onComplete(Result.success(Unit))
} else {
onComplete(Result.failure(Exception(expected.error?.message)))
}
}
}
fun getTileRegions(callback: (List<TileRegion>) -> Unit) {
tileStore.getAllTileRegions { expected ->
if (expected.isValue) {
callback(expected.value ?: emptyList())
} else {
callback(emptyList())
}
}
}
fun removeTileRegion(regionId: String, callback: (Boolean) -> Unit) {
tileStore.removeTileRegion(regionId)
callback(true)
}
fun estimateStorageSize(
bounds: CoordinateBounds,
minZoom: Int,
maxZoom: Int
): Long {
// 粗略估算:平均每个瓦片约50 KB
val tileCount = estimateTileCount(bounds, minZoom, maxZoom)
return tileCount * 50_000L // 字节
}
private fun estimateTileCount(
bounds: CoordinateBounds,
minZoom: Int,
maxZoom: Int
): Long {
// 简化的瓦片数量估算逻辑
var count = 0L
for (zoom in minZoom..maxZoom) {
val tilesAtZoom = Math.pow(4.0, zoom.toDouble()).toLong()
count += tilesAtZoom
}
return count
}
}核心注意事项:
- 电池影响:下载过程会消耗大量电池电量
- 存储限制:需监控可用磁盘空间
- 缩放级别:缩放级别越高,瓦片数量越多,占用存储越大
- 网络类型:区分WiFi与蜂窝网络
Check Available Storage
检查可用存储空间
kotlin
import android.os.StatFs
import android.os.Environment
fun getAvailableStorageBytes(): Long {
val stat = StatFs(Environment.getDataDirectory().path)
return stat.availableBlocksLong * stat.blockSizeLong
}
fun hasEnoughStorage(requiredBytes: Long): Boolean {
val available = getAvailableStorageBytes()
return available > requiredBytes * 2 // 2x buffer
}kotlin
import android.os.StatFs
import android.os.Environment
fun getAvailableStorageBytes(): Long {
val stat = StatFs(Environment.getDataDirectory().path)
return stat.availableBlocksLong * stat.blockSizeLong
}
fun hasEnoughStorage(requiredBytes: Long): Boolean {
val available = getAvailableStorageBytes()
return available > requiredBytes * 2 // 预留2倍缓冲空间
}Navigation SDK Integration
Navigation SDK集成
Basic Navigation Setup
基础导航配置
kotlin
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.MapboxNavigationProvider
import com.mapbox.navigation.core.directions.session.RoutesObserver
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
import com.mapbox.navigation.core.trip.session.TripSessionState
import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.geojson.Point
class NavigationActivity : AppCompatActivity() {
private lateinit var mapboxNavigation: MapboxNavigation
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_navigation)
mapView = findViewById(R.id.mapView)
// Initialize Navigation SDK
mapboxNavigation = MapboxNavigationProvider.create(
NavigationOptions.Builder(this)
.accessToken(getString(R.string.mapbox_access_token))
.build()
)
setupObservers()
}
private fun setupObservers() {
// Observe route updates
mapboxNavigation.registerRoutesObserver(object : RoutesObserver {
override fun onRoutesChanged(result: RoutesUpdatedResult) {
val routes = result.navigationRoutes
if (routes.isNotEmpty()) {
// Show route on map
showRouteOnMap(routes.first())
}
}
})
// Observe navigation progress
mapboxNavigation.registerRouteProgressObserver(object : RouteProgressObserver {
override fun onRouteProgressChanged(routeProgress: RouteProgress) {
// Update UI with progress
val distanceRemaining = routeProgress.distanceRemaining
val durationRemaining = routeProgress.durationRemaining
}
})
}
fun startNavigation(destination: Point) {
// Request route
val origin = mapboxNavigation.navigationOptions.locationEngine
.getLastLocation { location ->
location?.let {
val originPoint = Point.fromLngLat(it.longitude, it.latitude)
requestRoute(originPoint, destination)
}
}
}
private fun requestRoute(origin: Point, destination: Point) {
val routeOptions = RouteOptions.builder()
.applyDefaultNavigationOptions()
.coordinates(listOf(origin, destination))
.build()
mapboxNavigation.requestRoutes(
routeOptions,
object : NavigationRouterCallback {
override fun onRoutesReady(
routes: List<NavigationRoute>,
routerOrigin: RouterOrigin
) {
mapboxNavigation.setNavigationRoutes(routes)
mapboxNavigation.startTripSession()
}
override fun onFailure(
reasons: List<RouterFailure>,
routeOptions: RouteOptions
) {
// Handle error
}
override fun onCanceled(
routeOptions: RouteOptions,
routerOrigin: RouterOrigin
) {
// Handle cancellation
}
}
)
}
private fun showRouteOnMap(route: NavigationRoute) {
// Draw route on map
}
override fun onDestroy() {
super.onDestroy()
mapboxNavigation.onDestroy()
}
}Navigation SDK features:
- Turn-by-turn guidance
- Voice instructions
- Route progress tracking
- Rerouting
- Traffic-aware routing
- Offline navigation (with offline regions)
kotlin
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.MapboxNavigationProvider
import com.mapbox.navigation.core.directions.session.RoutesObserver
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
import com.mapbox.navigation.core.trip.session.TripSessionState
import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.geojson.Point
class NavigationActivity : AppCompatActivity() {
private lateinit var mapboxNavigation: MapboxNavigation
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_navigation)
mapView = findViewById(R.id.mapView)
// 初始化Navigation SDK
mapboxNavigation = MapboxNavigationProvider.create(
NavigationOptions.Builder(this)
.accessToken(getString(R.string.mapbox_access_token))
.build()
)
setupObservers()
}
private fun setupObservers() {
// 监听路线更新
mapboxNavigation.registerRoutesObserver(object : RoutesObserver {
override fun onRoutesChanged(result: RoutesUpdatedResult) {
val routes = result.navigationRoutes
if (routes.isNotEmpty()) {
// 在地图上显示路线
showRouteOnMap(routes.first())
}
}
})
// 监听导航进度
mapboxNavigation.registerRouteProgressObserver(object : RouteProgressObserver {
override fun onRouteProgressChanged(routeProgress: RouteProgress) {
// 更新UI展示进度
val distanceRemaining = routeProgress.distanceRemaining
val durationRemaining = routeProgress.durationRemaining
}
})
}
fun startNavigation(destination: Point) {
// 请求路线
val origin = mapboxNavigation.navigationOptions.locationEngine
.getLastLocation { location ->
location?.let {
val originPoint = Point.fromLngLat(it.longitude, it.latitude)
requestRoute(originPoint, destination)
}
}
}
private fun requestRoute(origin: Point, destination: Point) {
val routeOptions = RouteOptions.builder()
.applyDefaultNavigationOptions()
.coordinates(listOf(origin, destination))
.build()
mapboxNavigation.requestRoutes(
routeOptions,
object : NavigationRouterCallback {
override fun onRoutesReady(
routes: List<NavigationRoute>,
routerOrigin: RouterOrigin
) {
mapboxNavigation.setNavigationRoutes(routes)
mapboxNavigation.startTripSession()
}
override fun onFailure(
reasons: List<RouterFailure>,
routeOptions: RouteOptions
) {
// 处理错误
}
override fun onCanceled(
routeOptions: RouteOptions,
routerOrigin: RouterOrigin
) {
// 处理取消请求
}
}
)
}
private fun showRouteOnMap(route: NavigationRoute) {
// 在地图上绘制路线
}
override fun onDestroy() {
super.onDestroy()
mapboxNavigation.onDestroy()
}
}Navigation SDK功能:
- 逐向导航指引
- 语音播报指令
- 路线进度追踪
- 重新规划路线
- 实时路况导航
- 离线导航(需配合离线区域使用)
Mobile Performance Optimization
移动端性能优化
Battery Optimization
电池优化
kotlin
import android.content.Context
import android.os.PowerManager
class BatteryAwareMapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var powerManager: PowerManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
mapView = findViewById(R.id.mapView)
powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
observeBatteryState()
}
private fun observeBatteryState() {
if (powerManager.isPowerSaveMode) {
enableLowPowerMode()
}
// Register broadcast receiver for power save mode changes
registerReceiver(
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (powerManager.isPowerSaveMode) {
enableLowPowerMode()
} else {
enableNormalMode()
}
}
},
IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
)
}
private fun enableLowPowerMode() {
// Reduce frame rate
mapView.getMapboxMap().setMaximumFps(30)
// Disable 3D features
// Reduce tile quality
}
private fun enableNormalMode() {
mapView.getMapboxMap().setMaximumFps(60)
}
}kotlin
import android.content.Context
import android.os.PowerManager
class BatteryAwareMapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var powerManager: PowerManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
mapView = findViewById(R.id.mapView)
powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
observeBatteryState()
}
private fun observeBatteryState() {
if (powerManager.isPowerSaveMode) {
enableLowPowerMode()
}
// 注册广播接收器监听省电模式变更
registerReceiver(
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (powerManager.isPowerSaveMode) {
enableLowPowerMode()
} else {
enableNormalMode()
}
}
},
IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
)
}
private fun enableLowPowerMode() {
// 降低帧率
mapView.getMapboxMap().setMaximumFps(30)
// 禁用3D功能
// 降低瓦片质量
}
private fun enableNormalMode() {
mapView.getMapboxMap().setMaximumFps(60)
}
}Memory Optimization
内存优化
kotlin
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
// Clear map cache
mapView.getMapboxMap().clearData { result ->
if (result.isValue) {
Log.d("Map", "Cache cleared")
}
}
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
when (level) {
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
// Clear non-essential data
mapView.getMapboxMap().clearData { }
}
}
}kotlin
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
// 清理地图缓存
mapView.getMapboxMap().clearData { result ->
if (result.isValue) {
Log.d("Map", "缓存已清理")
}
}
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
when (level) {
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
// 清理非必要数据
mapView.getMapboxMap().clearData { }
}
}
}Network Optimization
网络优化
kotlin
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
class NetworkAwareMapActivity : AppCompatActivity() {
private lateinit var connectivityManager: ConnectivityManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
observeNetworkState()
}
private fun observeNetworkState() {
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onCapabilitiesChanged(
network: Network,
capabilities: NetworkCapabilities
) {
when {
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> {
// WiFi - use full quality
enableHighQuality()
}
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {
// Cellular - reduce data usage
enableLowDataMode()
}
}
}
}
connectivityManager.registerDefaultNetworkCallback(networkCallback)
}
private fun enableHighQuality() {
// Use full resolution tiles
}
private fun enableLowDataMode() {
// Reduce tile resolution
// Limit prefetching
}
}kotlin
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
class NetworkAwareMapActivity : AppCompatActivity() {
private lateinit var connectivityManager: ConnectivityManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
observeNetworkState()
}
private fun observeNetworkState() {
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onCapabilitiesChanged(
network: Network,
capabilities: NetworkCapabilities
) {
when {
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> {
// WiFi环境下使用全质量配置
enableHighQuality()
}
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {
// 蜂窝网络下降低数据消耗
enableLowDataMode()
}
}
}
}
connectivityManager.registerDefaultNetworkCallback(networkCallback)
}
private fun enableHighQuality() {
// 使用全分辨率瓦片
}
private fun enableLowDataMode() {
// 降低瓦片分辨率
// 限制预加载
}
}Common Mistakes and Solutions
常见错误与解决方案
❌ Mistake 1: Not Calling Lifecycle Methods
❌ 错误1:未调用生命周期方法
kotlin
// ❌ BAD: MapView lifecycle not managed
class MapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapView = findViewById(R.id.mapView)
// No lifecycle methods called!
}
}
// ✅ GOOD: Proper lifecycle management
class MapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapView = findViewById(R.id.mapView)
}
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onStop() {
super.onStop()
mapView.onStop()
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
}kotlin
// ❌ 错误示例:未管理MapView生命周期
class MapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapView = findViewById(R.id.mapView)
// 未调用任何生命周期方法!
}
}
// ✅ 正确示例:规范的生命周期管理
class MapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapView = findViewById(R.id.mapView)
}
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onStop() {
super.onStop()
mapView.onStop()
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
}❌ Mistake 2: Memory Leaks in Fragments
❌ 错误2:Fragment中的内存泄漏
kotlin
// ❌ BAD: MapView not cleaned up in Fragment
class MapFragment : Fragment() {
private lateinit var mapView: MapView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = inflater.inflate(R.layout.fragment_map, container, false)
mapView = view.findViewById(R.id.mapView)
return view
}
// No cleanup!
}
// ✅ GOOD: Proper cleanup
class MapFragment : Fragment() {
private var mapView: MapView? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = inflater.inflate(R.layout.fragment_map, container, false)
mapView = view.findViewById(R.id.mapView)
return view
}
override fun onDestroyView() {
super.onDestroyView()
mapView?.onDestroy()
mapView = null // Prevent leaks
}
}kotlin
// ❌ 错误示例:Fragment中未清理MapView
class MapFragment : Fragment() {
private lateinit var mapView: MapView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = inflater.inflate(R.layout.fragment_map, container, false)
mapView = view.findViewById(R.id.mapView)
return view
}
// 未做任何清理!
}
// ✅ 正确示例:规范的清理操作
class MapFragment : Fragment() {
private var mapView: MapView? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = inflater.inflate(R.layout.fragment_map, container, false)
mapView = view.findViewById(R.id.mapView)
return view
}
override fun onDestroyView() {
super.onDestroyView()
mapView?.onDestroy()
mapView = null // 避免内存泄漏
}
}❌ Mistake 3: Ignoring Location Permissions
❌ 错误3:忽略位置权限
kotlin
// ❌ BAD: Enabling location without checking permissions
mapView.location.enabled = true
// ✅ GOOD: Request and check permissions
import androidx.activity.result.contract.ActivityResultContracts
class MapActivity : AppCompatActivity() {
private val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true -> {
enableLocationTracking()
}
permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true -> {
enableLocationTracking()
}
else -> {
// Handle denied
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestLocationPermissions()
}
private fun requestLocationPermissions() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED -> {
enableLocationTracking()
}
else -> {
locationPermissionRequest.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}
}
}
private fun enableLocationTracking() {
mapView.location.enabled = true
}
}Add to AndroidManifest.xml:
xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />kotlin
// ❌ 错误示例:未检查权限就启用定位
mapView.location.enabled = true
// ✅ 正确示例:申请并检查权限
import androidx.activity.result.contract.ActivityResultContracts
class MapActivity : AppCompatActivity() {
private val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true -> {
enableLocationTracking()
}
permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true -> {
enableLocationTracking()
}
else -> {
// 处理权限被拒绝的情况
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestLocationPermissions()
}
private fun requestLocationPermissions() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED -> {
enableLocationTracking()
}
else -> {
locationPermissionRequest.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}
}
}
private fun enableLocationTracking() {
mapView.location.enabled = true
}
}需添加至AndroidManifest.xml:
xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />❌ Mistake 4: Adding Layers Before Map Loads
❌ 错误4:地图加载完成前添加图层
kotlin
// ❌ BAD: Adding layers immediately
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapView = findViewById(R.id.mapView)
addCustomLayers() // Map not loaded yet!
}
// ✅ GOOD: Wait for style to load
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapView = findViewById(R.id.mapView)
mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS) { style ->
addCustomLayers(style)
}
}kotlin
// ❌ 错误示例:立即添加图层
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapView = findViewById(R.id.mapView)
addCustomLayers() // 地图尚未加载完成!
}
// ✅ 正确示例:等待样式加载完成后再操作
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapView = findViewById(R.id.mapView)
mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS) { style ->
addCustomLayers(style)
}
}Testing Patterns
测试模式
Unit Testing Map Logic
地图逻辑单元测试
kotlin
import org.junit.Test
import org.junit.Assert.*
import com.mapbox.geojson.Point
class MapLogicTest {
@Test
fun testCoordinateConversion() {
val point = Point.fromLngLat(-122.4194, 37.7749)
// Test your map logic without creating actual MapView
val converted = MapLogic.convert(point)
assertEquals(-122.4194, converted.longitude(), 0.001)
assertEquals(37.7749, converted.latitude(), 0.001)
}
}kotlin
import org.junit.Test
import org.junit.Assert.*
import com.mapbox.geojson.Point
class MapLogicTest {
@Test
fun testCoordinateConversion() {
val point = Point.fromLngLat(-122.4194, 37.7749)
// 无需创建实际MapView即可测试地图逻辑
val converted = MapLogic.convert(point)
assertEquals(-122.4194, converted.longitude(), 0.001)
assertEquals(37.7749, converted.latitude(), 0.001)
}
}Instrumented Testing with Maps
基于地图的仪器化测试
kotlin
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MapActivityTest {
@get:Rule
val activityRule = ActivityScenarioRule(MapActivity::class.java)
@Test
fun testMapLoads() {
activityRule.scenario.onActivity { activity ->
val mapView = activity.findViewById<MapView>(R.id.mapView)
assertNotNull(mapView)
}
}
}kotlin
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MapActivityTest {
@get:Rule
val activityRule = ActivityScenarioRule(MapActivity::class.java)
@Test
fun testMapLoads() {
activityRule.scenario.onActivity { activity ->
val mapView = activity.findViewById<MapView>(R.id.mapView)
assertNotNull(mapView)
}
}
}Troubleshooting
故障排查
Map Not Displaying
地图无法显示
Checklist:
- ✅ Token configured in string resources?
- ✅ Correct package name in token restrictions?
- ✅ MapboxMaps dependency added to build.gradle?
- ✅ MapView lifecycle methods called?
- ✅ Internet permission in AndroidManifest.xml?
xml
<uses-permission android:name="android.permission.INTERNET" />检查清单:
- ✅ 令牌是否已配置在字符串资源中?
- ✅ 令牌限制中的包名是否正确?
- ✅ 项目build.gradle中是否已添加MapboxMaps依赖?
- ✅ 是否调用了MapView的生命周期方法?
- ✅ AndroidManifest.xml中是否添加了网络权限?
xml
<uses-permission android:name="android.permission.INTERNET" />Memory Leaks
内存泄漏
Use Android Studio Profiler:
- Run → Profile 'app' → Memory
- Look for MapView instances not being garbage collected
- Ensure is called
mapView.onDestroy() - Set in Fragments after destroy
mapView = null
使用Android Studio Profiler排查:
- 运行 → Profile 'app' → Memory
- 查找未被垃圾回收的MapView实例
- 确保已调用
mapView.onDestroy() - 在Fragment销毁后将
mapView = null
Slow Performance
性能缓慢
Common causes:
- Too many markers (use clustering or symbols)
- Large GeoJSON sources (use vector tiles)
- Not handling lifecycle properly
- Not calling
onLowMemory() - Running on emulator (use device for accurate testing)
常见原因:
- 标记点过多(使用聚类或符号图层优化)
- GeoJSON数据源过大(使用矢量瓦片替代)
- 未正确处理生命周期
- 未调用
onLowMemory() - 在模拟器上运行(使用真实设备进行精准测试)
Platform-Specific Considerations
平台专属注意事项
Android Version Support
Android版本支持
- Android 6.0+ (API 23+): Minimum supported version
- Android 12+ (API 31+): New permission handling
- Android 13+ (API 33+): Runtime notification permissions
- Android 6.0+(API 23+):最低支持版本
- Android 12+(API 31+):全新权限处理逻辑
- Android 13+(API 33+):运行时通知权限
Device Optimization
设备优化
kotlin
import android.app.ActivityManager
import android.content.Context
fun isLowRamDevice(): Boolean {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return activityManager.isLowRamDevice
}
// Adjust map quality based on device
if (isLowRamDevice()) {
// Reduce detail, limit features
}kotlin
import android.app.ActivityManager
import android.content.Context
fun isLowRamDevice(): Boolean {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return activityManager.isLowRamDevice
}
// 根据设备配置调整地图质量
if (isLowRamDevice()) {
// 降低细节展示,限制功能
}Screen Density
屏幕密度适配
kotlin
val density = resources.displayMetrics.density
when {
density >= 4.0 -> {
// xxxhdpi displays
// Use highest quality
}
density >= 3.0 -> {
// xxhdpi displays
// High quality
}
density >= 2.0 -> {
// xhdpi displays
// Standard quality
}
}kotlin
val density = resources.displayMetrics.density
when {
density >= 4.0 -> {
// xxxhdpi屏幕
// 使用最高质量配置
}
density >= 3.0 -> {
// xxhdpi屏幕
// 高质量配置
}
density >= 2.0 -> {
// xhdpi屏幕
// 标准质量配置
}
}