14、权限与后台:后台位置权限、前台服务权限、Android 10+后台限制
聊到后台权限,我脑子里第一个蹦出来的画面,就是几年前那次线上事故。
用户反馈说:“你们的App怎么一退到后台就‘死’了?” 我排查了半天,发现是后台位置权限没处理好,系统直接把进程给杀了。嗯,从那以后,我对后台权限这块就格外上心。
说白了,Android从10开始,对后台的限制越来越严。你想想看,如果每个App都在后台偷偷定位、偷偷启动服务,用户的电量撑不过半天。所以Google下了狠手。
14.1 后台位置权限:ACCESS_BACKGROUND_LOCATION
这个权限,是Android 10(API 29)新增的。以前只要申请了前台位置权限,后台也能用。现在不行了。
你必须单独申请 ACCESS_BACKGROUND_LOCATION。而且,用户可以在设置里随时关掉它。
核心规则:
- 如果你的App只在用户看得见的时候(前台)用位置,不需要后台权限。
- 如果需要在后台持续获取位置(比如运动轨迹记录),必须申请后台权限。
- 用户拒绝后台权限后,App不能反复弹窗骚扰用户。
我在项目中遇到过一种情况:用户明明给了前台位置权限,但App一退到后台,定位就停了。查了半天,发现是没加 ACCESS_BACKGROUND_LOCATION。这个坑,很多人踩过。
14.1.1 权限声明与申请
在 AndroidManifest.xml 里声明:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
注意顺序:先申请前台权限,再申请后台权限。Google建议分两步走。
代码申请时,我习惯这样写:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// 先检查前台权限
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
requestPermissions(
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_CODE_FOREGROUND
);
} else {
// 前台权限已获取,再申请后台权限
if (checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
requestPermissions(
new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
REQUEST_CODE_BACKGROUND
);
}
}
}
小技巧: 申请后台权限时,最好给用户一个明确的理由。比如弹个对话框说“我们需要后台定位来记录你的运动轨迹”,而不是直接弹系统权限框。用户更容易接受。
14.2 前台服务权限:FOREGROUND_SERVICE
Android 9(API 28)引入了前台服务权限。说白了,就是告诉系统:“我这个服务很重要,你别杀我。”
前台服务会显示一个持续的通知(Notification),用户能看到你在后台干活。
14.2.1 声明与使用
在 AndroidManifest.xml 里声明:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
启动前台服务的代码:
Intent serviceIntent = new Intent(this, MyForegroundService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}
在 MyForegroundService 的 onStartCommand 里,必须调用 startForeground():
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Notification notification = buildNotification();
startForeground(NOTIFICATION_ID, notification);
// 执行后台任务
return START_STICKY;
}
警告: 如果不调用 startForeground(),系统会在5秒内抛出 ForegroundServiceDidNotStartInTimeException,直接崩溃。我曾经因为这个被测试妹子追着骂了一下午。
14.3 Android 10+ 后台限制:后台启动Activity与Service
Android 10开始,系统对后台启动Activity做了严格限制。说白了,你的App在后台时,不能随便弹出一个Activity来打扰用户。
我记得有一次,用户反馈说“手机突然自己弹出个界面”,查了日志发现是某个第三方SDK在后台启动了Activity。嗯,这种体验非常糟糕。
14.3.1 后台启动Activity的限制
从Android 10开始,以下情况不允许后台启动Activity:
- App处于后台状态(没有可见的Activity)
- 用户没有与App交互
- 不是从通知、Shortcut、Widget等系统入口启动
如果你非要后台启动,系统会弹一个Toast提示:“App已阻止后台启动Activity”。
解决方案:
- 使用通知(Notification)来提醒用户,点击通知再启动Activity。
- 使用前台服务,保持App在前台状态。
- 使用
PendingIntent配合通知或Shortcut。
14.3.2 后台启动Service的限制
Android 8.0(API 26)开始,后台启动Service就被限制了。Android 10进一步收紧。
核心规则:
- App处于后台时,不能调用
startService()。 - 可以使用
startForegroundService(),但必须在5秒内调用startForeground()。 - 可以使用
JobScheduler或WorkManager来替代后台Service。
我个人习惯,能用 WorkManager 绝不用原生Service。WorkManager会自动处理兼容性问题,省心很多。
14.4 知识体系图:后台权限与限制
下面这张图,是我自己总结的。每次做新项目,我都会拿出来对照一遍。
14.5 避坑指南:我踩过的那些坑
做Android这么多年,后台权限这块的坑,我基本都踩了一遍。分享几个典型的:
坑1:后台位置权限申请时机不对
我曾经在App启动时同时申请前台和后台位置权限。结果用户直接拒绝,而且勾选了“不再询问”。后来我改成先申请前台权限,等用户用到轨迹记录功能时,再弹窗解释为什么需要后台权限。通过率提高了不少。
坑2:前台服务忘记调startForeground()
这个我前面提过。有一次上线前测试,发现App启动后5秒必崩。查了半天,发现是某个分支逻辑里忘了调 startForeground()。嗯,从那以后我写了个单元测试来检查这个。
坑3:后台启动Activity被系统拦截
有个需求是:收到推送后直接跳转到某个页面。结果在Android 10上,用户点击推送后没反应,只弹了个Toast。后来改成点击推送先打开MainActivity,再通过Intent跳转。完美解决。
14.6 总结
后台权限这块,说白了就是三个字:别作死。
系统限制越来越严,是为了保护用户隐私和体验。我们作为开发者,要做的不是绕开限制,而是顺应规则,用更优雅的方式实现功能。
我个人建议:
- 能用前台服务,就别用后台Service。
- 能用WorkManager,就别自己管理线程。
- 申请权限时,一定要给用户一个合理的解释。
- 测试时,务必在Android 10+的真机上跑一遍后台场景。
嗯,今天就聊到这里。记住,权限不是你想拿,想拿就能拿。
公众号:蓝海资料掘金营,微信deep3321