4、权限管理:Android 6.0+运行时权限、ACCESS_FINE_LOCATION与ACCESS_COARSE_LOCATION、权限请求最佳实践
做位置开发,绕不开的就是权限。说实话,我见过太多新手在权限这步翻车了。明明代码写得没问题,一运行就崩溃,或者定位死活拿不到数据。嗯,今天我们就来彻底搞定它。
4.1 为什么要有运行时权限?
在 Android 6.0(API 23)之前,你只要在 AndroidManifest.xml 里声明了权限,安装时用户同意一次,App 就能随便用。听起来挺省事对吧?但问题来了——用户根本不知道你什么时候在用他的位置。
我记得有个项目,用户反馈说「你们的 App 为什么半夜还在定位?」。查了半天,原来是后台一个 Service 在持续请求位置更新。用户很生气,后果很严重。所以 Google 在 6.0 引入了运行时权限机制:危险权限不仅要声明,还要在运行时弹窗让用户确认。
核心原则:权限请求必须与用户操作直接关联。不要一启动 App 就弹权限框,用户会反感的。
4.2 ACCESS_FINE_LOCATION 与 ACCESS_COARSE_LOCATION
这两个权限是位置服务的入口。我简单解释一下区别:
| 权限名 | 精度 | 典型来源 | 使用场景 |
|---|---|---|---|
ACCESS_FINE_LOCATION |
高精度(米级) | GPS + 网络 + 基站 | 导航、运动轨迹、附近搜索 |
ACCESS_COARSE_LOCATION |
低精度(百米级) | Wi-Fi + 基站 | 天气、新闻、粗略位置展示 |
你想想看,如果你的 App 只是显示个天气,其实 COARSE 就够了。但很多开发者图省事,直接申请 FINE。用户一看「这个天气 App 要精确定位?」,直接拒绝。何必呢?
我的建议:只申请你真正需要的权限。如果只需要粗略位置,就别碰 FINE。这样用户更容易接受,审核也更容易通过。
4.3 运行时权限请求流程
说白了,就是三步走:检查 → 请求 → 处理回调。但细节里全是坑。
4.3.1 检查权限是否已授予
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
// 权限已授予,直接开始定位
startLocationUpdates()
} else {
// 权限未授予,需要请求
requestLocationPermission()
}
这里有个细节:checkSelfPermission 必须在主线程调用。我曾经在子线程里调过,结果返回永远是 DENIED,排查了半天才发现是线程问题。
4.3.2 请求权限
private fun requestLocationPermission() {
// 判断是否需要展示解释说明
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_FINE_LOCATION)) {
// 用户之前拒绝过,需要解释为什么需要这个权限
showPermissionRationaleDialog()
} else {
// 直接请求
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
LOCATION_PERMISSION_REQUEST_CODE)
}
}
shouldShowRequestPermissionRationale 这个方法很有意思。它返回 true 表示用户之前拒绝过但没勾选「不再询问」。这时候你应该弹个对话框解释一下。如果返回 false,有两种情况:要么是第一次请求,要么是用户勾选了「不再询问」。后者就比较棘手了。
注意:如果用户勾选了「不再询问」,你再调用 requestPermissions 是不会弹窗的。系统会直接回调 onRequestPermissionsResult 并返回 DENIED。这时候你只能引导用户去设置里手动开启。
4.3.3 处理回调结果
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 用户同意了,开始定位
startLocationUpdates()
} else {
// 用户拒绝了
showPermissionDeniedFeedback()
}
}
}
嗯,这里要注意:grantResults 数组的顺序和 permissions 数组是一一对应的。如果你一次请求了多个权限,一定要按索引匹配。
4.4 权限请求最佳实践
做了这么多年 Android,我总结了几条铁律:
- 时机要对:在用户真正需要位置功能时才请求。比如用户点击「开始导航」按钮时,而不是 App 启动时。
- 解释要清:如果用户拒绝过,用
shouldShowRequestPermissionRationale检测到后,弹一个友好的对话框说明原因。 - 拒绝要处理:用户拒绝后,不要反复弹窗。可以给个提示,或者降级使用
COARSE权限。 - 区分权限:如果同时需要
FINE和COARSE,先请求COARSE,用户同意后再请求FINE。这样用户体验更好。 - 考虑 Android 10+:从 Android 10 开始,后台位置权限需要单独申请
ACCESS_BACKGROUND_LOCATION。这个权限的弹窗和前台权限是分开的。
我曾经在一个外卖 App 里踩过坑:用户在前台允许了位置权限,但后台定位一直失败。查了半天才发现 Android 10 之后后台权限要单独申请。从那以后,我每次做位置功能都会先检查 targetSdkVersion。
4.5 知识体系图
下面这张图把权限管理的核心逻辑串起来了。你可以对照着梳理自己的代码:
4.6 常见问题与避坑
- 权限弹窗不显示? 检查一下
targetSdkVersion是否大于等于 23。如果小于 23,系统会沿用旧版安装时授权机制。 - 用户拒绝后反复弹窗? 别这么做。用户会烦,甚至可能卸载你的 App。记录一下拒绝次数,超过两次就引导去设置。
- 同时请求多个权限? 建议分开请求。一次只弹一个权限框,用户更容易接受。我见过一个 App 一次弹了 5 个权限,用户直接拒绝所有。
- Android 11 的权限自动重置? 如果用户几个月没用你的 App,系统会自动撤销权限。记得在
onResume里重新检查权限状态。
重要提醒:千万不要在 onCreate 里直接请求权限。这时候 Activity 还没完全初始化,弹窗可能会出问题。我习惯在 onResume 里检查,或者等用户触发某个操作后再请求。
好了,权限管理这块就讲到这里。记住一句话:尊重用户,权限才能拿到。你越是彬彬有礼地解释为什么需要位置,用户越愿意给你授权。反过来,一上来就硬要,用户只会点「拒绝」。
公众号:蓝海资料掘金营,微信deep3321