Android 定位权限兼容问题详解 —— 新手指南

一、定位权限的核心变化与版本适配

Android 版本 定位权限关键变更
≤ 8.1 (API 27) 仅需声明 ACCESS_COARSE_LOCATIONACCESS_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+ 的近似位置权限

  • 用户可能仅授予近似位置:需在代码中处理低精度场景。

  • 适配建议

    kotlin 复制代码
    val 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+)

  • 用户拒绝后台权限时 :引导用户到设置页手动开启。

    kotlin 复制代码
    if (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 的"仅在使用时允许"选项

  • 用户选择"仅在使用时允许":应用在后台无法获取位置。

  • 检测权限状态

    kotlin 复制代码
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        val locationPermissionStatus = ActivityCompat.checkSelfPermission(
            this, Manifest.permission.ACCESS_BACKGROUND_LOCATION
        )
        val isBackgroundAccessAllowed = locationPermissionStatus == PackageManager.PERMISSION_GRANTED
    }

五、最佳实践与注意事项

  1. 最小化权限请求

    • 仅请求必要的精度(如导航应用需 ACCESS_FINE_LOCATION,天气应用可用 ACCESS_COARSE_LOCATION)。
  2. 用户引导与解释

    • 在请求权限前通过弹窗说明用途(使用 shouldShowRequestPermissionRationale 判断是否需要解释)。
  3. 后台定位的合理使用

    • 遵循 Google Play 政策,避免滥用后台定位(如仅用于导航、健身跟踪等核心功能)。
  4. 测试覆盖

    • 在不同 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_LOCATIONACCESS_FINE_LOCATION,适配 Android 12+ 的精度分离。
  • 后台定位 :Android 10+ 需显式请求 ACCESS_BACKGROUND_LOCATION,用户可能手动拒绝。
  • 兼容性关键:动态检查权限、处理不同精度场景、引导用户设置。
  • 用户隐私:遵循最小化原则,仅在必要时请求权限并提供清晰解释。

更多分享

  1. Android跨进程通信中的关键字详解:in、out、inout、oneway
  2. 一文带你吃透Kotlin协程的launch()和async()的区别
  3. Kotlin 委托与扩展函数------新手入门
  4. Kotlin 作用域函数(let、run、with、apply、also)的使用指南
  5. 一文带你吃透Kotlin中 lateinit 和 by lazy 的区别和用法
  6. Kotlin 扩展方法(Extension Functions)使用详解
  7. Kotlin 中 == 和 === 的区别
  8. Kotlin 操作符与集合/数组方法详解------新手指南
  9. Kotlin 中 reified 配合 inline 不再被类型擦除蒙蔽双眼
  10. Kotlin Result 类型扩展详解 ------ 新手使用指南
相关推荐
Lary_Rock14 分钟前
Android 编译问题 prebuilts/clang/host/linux-x86
android·linux·运维
王江奎1 小时前
Android FFmpeg 交叉编译全指南:NDK编译 + CMake 集成
android·ffmpeg
limingade1 小时前
手机打电话通话时如何向对方播放录制的IVR引导词声音
android·智能手机·蓝牙电话·手机提取通话声音
小厂永远得不到的男人2 小时前
基于 Trae 的 WebSocket 聊天室保姆级教程(超详细版)
websocket·全栈·trae
hepherd2 小时前
Flutter 环境搭建 (Android)
android·flutter·visual studio code
_一条咸鱼_3 小时前
揭秘 Android ListView:从源码深度剖析其使用原理
android·面试·android jetpack
_一条咸鱼_3 小时前
深入剖析 Android NestedScrollView 使用原理
android·面试·android jetpack
_一条咸鱼_3 小时前
揭秘 Android ScrollView:深入剖析其使用原理与源码奥秘
android·面试·android jetpack
_一条咸鱼_3 小时前
深入剖析 Android View:从源码探寻使用原理
android·面试·android jetpack
_一条咸鱼_3 小时前
揭秘 Android View 绘制原理:从源码剖析到极致理解
android·面试·android jetpack