一、定位权限的核心变化与版本适配
Android 版本 | 定位权限关键变更 |
---|---|
≤ 8.1 (API 27) | 仅需声明 ACCESS_COARSE_LOCATION 或 ACCESS_FINE_LOCATION 即可访问位置。 |
9.0 (API 28) | 前台服务必须请求 FOREGROUND_SERVICE 权限(与定位无关,但影响后台定位服务)。 |
10.0 (API 29) | 后台定位需显式声明 ACCESS_BACKGROUND_LOCATION ,用户需手动授权。 |
11.0 (API 30) | 动态请求后台定位时,用户可选择仅授予"使用时允许"(前台定位)。 |
12.0 (API 31) | 引入 近似位置(ACCESS_COARSE_LOCATION) 与 精确位置(ACCESS_FINE_LOCATION) 分离。 |
二、前台定位与后台定位的权限区别
权限类型 | 权限名称 | 作用场景 |
---|---|---|
前台定位 | ACCESS_COARSE_LOCATION |
获取大致位置(基站/WiFi,精度约100米)。 |
ACCESS_FINE_LOCATION |
获取精确位置(GPS,精度约10米)。 | |
后台定位 | ACCESS_BACKGROUND_LOCATION |
应用在后台(不可见或未运行)时访问位置(需 Android 10+ 显式声明和用户授权)。 |
三、适配策略与代码示例
1. 权限声明(AndroidManifest.xml)
xml
<!-- 始终声明前台定位权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Android 10+ 后台定位需显式声明 -->
<uses-permission
android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
android:maxSdkVersion="30" /> <!-- Android 11+ 后台权限通过动态请求 -->
2. 动态权限请求(适配不同版本)
kotlin
// 检查并请求权限
fun requestLocationPermissions(activity: Activity) {
val permissions = mutableListOf<String>()
// Android 12+ 需要明确请求近似或精确位置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION)
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
} else {
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
// Android 10+ 需额外请求后台定位权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
permissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
ActivityCompat.requestPermissions(
activity,
permissions.toTypedArray(),
REQUEST_CODE_LOCATION
)
}
3. 处理权限结果
kotlin
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_LOCATION) {
val isFineLocationGranted = grantResults.getOrNull(
permissions.indexOfFirst { it == Manifest.permission.ACCESS_FINE_LOCATION }
) == PackageManager.PERMISSION_GRANTED
val isBackgroundGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
grantResults.getOrNull(
permissions.indexOfFirst { it == Manifest.permission.ACCESS_BACKGROUND_LOCATION }
) == PackageManager.PERMISSION_GRANTED
} else {
true // Android 9 及以下无需后台权限
}
if (isFineLocationGranted) {
if (isBackgroundGranted) {
// 前台和后台定位均授权
} else {
// 仅前台定位授权
}
} else {
// 定位权限被拒绝
}
}
}
四、兼容性问题与解决方案
1. Android 12+ 的近似位置权限
-
用户可能仅授予近似位置:需在代码中处理低精度场景。
-
适配建议 :
kotlinval hasCoarse = ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED val hasFine = ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED val locationPermissionLevel = when { hasFine -> "精确位置" hasCoarse -> "近似位置" else -> "无权限" }
2. 后台定位权限的显式引导(Android 10+)
-
用户拒绝后台权限时 :引导用户到设置页手动开启。
kotlinif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !Environment.isBackgroundLocationPermissionGranted()) { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { data = Uri.fromParts("package", packageName, null) } startActivity(intent) }
3. Android 11 的"仅在使用时允许"选项
-
用户选择"仅在使用时允许":应用在后台无法获取位置。
-
检测权限状态 :
kotlinif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val locationPermissionStatus = ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_BACKGROUND_LOCATION ) val isBackgroundAccessAllowed = locationPermissionStatus == PackageManager.PERMISSION_GRANTED }
五、最佳实践与注意事项
-
最小化权限请求
- 仅请求必要的精度(如导航应用需
ACCESS_FINE_LOCATION
,天气应用可用ACCESS_COARSE_LOCATION
)。
- 仅请求必要的精度(如导航应用需
-
用户引导与解释
- 在请求权限前通过弹窗说明用途(使用
shouldShowRequestPermissionRationale
判断是否需要解释)。
- 在请求权限前通过弹窗说明用途(使用
-
后台定位的合理使用
- 遵循 Google Play 政策,避免滥用后台定位(如仅用于导航、健身跟踪等核心功能)。
-
测试覆盖
- 在不同 Android 版本(尤其 10、11、12)上测试权限逻辑。
六、示例代码(使用 AndroidX Activity Result API)
kotlin
// 定义权限请求合约
val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val fineGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: false
val coarseGranted = permissions[Manifest.permission.ACCESS_COARSE_LOCATION] ?: false
val backgroundGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
permissions[Manifest.permission.ACCESS_BACKGROUND_LOCATION] ?: false
} else {
true
}
when {
fineGranted -> { /* 精确位置可用 */ }
coarseGranted -> { /* 近似位置可用 */ }
else -> { /* 无权限 */ }
}
}
// 触发权限请求
fun requestPermissions() {
val permissions = mutableListOf<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION)
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
} else {
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
permissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
locationPermissionRequest.launch(permissions.toTypedArray())
}
七、总结
- 前台定位 :需
ACCESS_COARSE_LOCATION
或ACCESS_FINE_LOCATION
,适配 Android 12+ 的精度分离。 - 后台定位 :Android 10+ 需显式请求
ACCESS_BACKGROUND_LOCATION
,用户可能手动拒绝。 - 兼容性关键:动态检查权限、处理不同精度场景、引导用户设置。
- 用户隐私:遵循最小化原则,仅在必要时请求权限并提供清晰解释。
更多分享
- Android跨进程通信中的关键字详解:in、out、inout、oneway
- 一文带你吃透Kotlin协程的launch()和async()的区别
- Kotlin 委托与扩展函数------新手入门
- Kotlin 作用域函数(let、run、with、apply、also)的使用指南
- 一文带你吃透Kotlin中 lateinit 和 by lazy 的区别和用法
- Kotlin 扩展方法(Extension Functions)使用详解
- Kotlin 中 == 和 === 的区别
- Kotlin 操作符与集合/数组方法详解------新手指南
- Kotlin 中 reified 配合 inline 不再被类型擦除蒙蔽双眼
- Kotlin Result 类型扩展详解 ------ 新手使用指南