18、后台定位:Android 8.0后台限制、前台服务定位、通知栏常驻提示
后台定位,说白了就是App退到后台了,还得继续拿位置。
Android 8.0之前,这事儿挺简单的。你开个Service,在后台默默收位置就行。但8.0之后,Google搞了个大动作——后台执行限制。我当年第一次适配8.0时,差点被搞崩溃。App一进后台,定位就停了,用户投诉说“走路轨迹断了”。嗯,从那以后,我彻底搞懂了前台服务定位这套东西。
18.1 Android 8.0的后台限制到底限了什么?
Android 8.0(API 26)引入了后台服务限制。核心就一句话:后台应用不能随便启动后台Service。
具体来说:
- App处于后台状态时,不能通过startService()启动后台Service
- 已经运行的后台Service,系统可能会在几分钟内把它停掉
- 只有绑定到前台通知的Service(即前台服务)才能长期存活
为什么会这样?说白了,Google是为了省电和防止流氓App。你想想看,如果每个App都在后台疯狂拿位置,手机半天就没电了。
但问题来了——导航、运动记录、打车这类App,确实需要在后台持续定位。怎么办?Google留了个口子:前台服务。
核心结论:Android 8.0之后,后台持续定位的唯一官方方案就是前台服务 + 通知栏常驻提示。
18.2 前台服务定位:这才是正统方案
前台服务,本质上还是一个Service。但它有一个特殊之处:必须显示一个通知栏通知,告诉用户“这个App正在后台干活”。
用户看到通知栏有个定位图标,就知道你在拿位置。这是Android 8.0强制要求的,不能隐藏。
我个人的习惯是,在定位前台服务里同时做两件事:
- 启动前台服务(startForeground)
- 绑定位置请求(FusedLocationProviderClient)
下面是一个完整的前台定位Service示例:
public class LocationForegroundService extends Service {
private static final int NOTIFICATION_ID = 1001;
private static final String CHANNEL_ID = "location_channel";
private FusedLocationProviderClient fusedLocationClient;
private LocationCallback locationCallback;
@Override
public void onCreate() {
super.onCreate();
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
createNotificationChannel();
startForeground(NOTIFICATION_ID, buildNotification("正在获取位置..."));
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult == null) return;
for (Location location : locationResult.getLocations()) {
// 处理位置数据,比如保存到数据库或上传服务器
updateNotification("纬度: " + location.getLatitude()
+ ", 经度: " + location.getLongitude());
}
}
};
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
requestLocationUpdates();
return START_STICKY;
}
private void requestLocationUpdates() {
LocationRequest locationRequest = LocationRequest.create()
.setInterval(5000) // 5秒一次
.setFastestInterval(2000) // 最快2秒
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
fusedLocationClient.requestLocationUpdates(locationRequest,
locationCallback, Looper.getMainLooper());
}
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"定位服务",
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription("用于后台定位的通知渠道");
NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) {
manager.createNotificationChannel(channel);
}
}
}
private Notification buildNotification(String content) {
return new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("正在定位")
.setContentText(content)
.setSmallIcon(android.R.drawable.ic_menu_mylocation)
.setOngoing(true) // 不可滑动删除
.build();
}
private void updateNotification(String content) {
NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) {
manager.notify(NOTIFICATION_ID, buildNotification(content));
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (fusedLocationClient != null && locationCallback != null) {
fusedLocationClient.removeLocationUpdates(locationCallback);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
个人经验:START_STICKY这个flag很重要。如果系统因为资源紧张杀了你的前台服务,START_STICKY会让系统尽量重启它。但别指望100%重启——极端情况下系统可能直接放弃。
18.3 通知栏常驻提示:不只是为了合规
通知栏常驻提示,很多人觉得是累赘。但我告诉你,它其实是个好东西。
第一,它让用户知道你在定位。用户如果不想被定位,可以手动关掉通知,或者杀掉App。这是隐私保护的基本要求。
第二,它提高了App的存活率。前台服务的进程优先级很高,系统轻易不会杀它。相比后台Service,前台服务的保活能力强太多了。
第三,你可以在通知里展示实时信息。比如导航App显示“距离目的地还有2公里”,运动App显示“已跑步5公里”。这本身就是一种用户体验优化。
我曾经在一个骑行App里,把通知栏做成了迷你仪表盘:显示速度、里程、海拔。用户不用打开App就能看到关键数据,反馈特别好。
18.4 权限与适配要点
前台服务定位需要两个权限:
| 权限 | 说明 | 备注 |
|---|---|---|
| ACCESS_FINE_LOCATION | 精确定位(GPS) | 必须运行时申请 |
| FOREGROUND_SERVICE | 前台服务权限 | Android 9+ 需要声明 |
| ACCESS_BACKGROUND_LOCATION | 后台定位权限 | Android 10+ 需要单独申请 |
注意,Android 10(API 29)之后,后台定位权限变成了一个独立权限。用户必须在设置里手动开启,不能只靠运行时弹窗申请。我见过很多App因为这个被用户投诉“定位不准”——其实就是没拿到后台权限。
避坑指南:我曾经踩过一个坑——在Android 10上,即使你用了前台服务,如果没申请ACCESS_BACKGROUND_LOCATION,系统仍然会在一段时间后停止位置更新。解决方案是:在用户首次开启定位时,引导用户去设置里开启“始终允许”定位权限。
18.5 知识体系图:后台定位核心逻辑
下面这张图,帮你理清Android 8.0后台定位的整体脉络:
18.6 实战建议:如何优雅地实现后台定位
根据我多年的踩坑经验,给你几条实战建议:
- 通知要友好——别只写“正在定位”,写点有用的信息,比如“已记录轨迹2.3公里”。用户看了不反感。
- 合理设置定位间隔——导航场景用高精度+1秒间隔,运动记录用5秒间隔,签到场景用30秒间隔。别一上来就最高频率,耗电快用户会骂。
- 处理好生命周期——用户主动停止定位时,记得调stopForeground()和stopSelf()。别让Service在后台空转。
- 适配Android 12+——Android 12引入了前台服务启动限制,后台不能直接startForegroundService()。建议用WorkManager或JobScheduler来触发。
我的习惯:在定位Service里加一个心跳检测。如果5分钟没收到位置更新,自动降级为低功耗模式,或者弹通知提醒用户检查GPS开关。这个小功能帮我解决了不少用户反馈的“定位不动”问题。
好了,后台定位这块内容就讲到这里。核心就一句话:Android 8.0之后,老老实实用前台服务+通知栏,别想走捷径。适配好各个版本的权限变化,你的App才能在后台稳稳地拿位置。
公众号:蓝海资料掘金营,微信deep3321