13、Service组件原理:启动与绑定流程、前台服务、进程保活与ANR预防
Service,说白了就是Android里跑后台任务的组件。很多新手觉得它简单,不就是startService和bindService嘛。但真到了线上,各种被系统杀、ANR、保活失败的问题就全冒出来了。
我个人习惯把Service理解成「没有界面的Activity」。它不跟用户交互,但可以在后台干活。嗯,这里要注意——Service默认跑在主线程,你要是直接在里面做耗时操作,ANR分分钟教你做人。
13.1 启动流程:startService到底干了啥?
你调用startService(intent),系统会走这么几步:
- AMS收到请求,检查Service是否已创建
- 没创建?那就调onCreate()
- 然后调onStartCommand(),把Intent传进去
- Service进入运行状态,直到有人调stopService或自己调stopSelf
我在项目中遇到过一个问题:连续多次startService,onCreate只调一次,但onStartCommand会调多次。这个细节很多人忽略,结果在onCreate里初始化了资源,以为每次都会走,导致第二次启动时状态不对。
// 典型启动写法
Intent intent = new Intent(this, MyService.class);
startService(intent);
// Service内部
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 这里处理每次启动的任务
return START_STICKY; // 被杀后自动重启
}
13.2 绑定流程:bindService的玄机
绑定跟启动是两套逻辑。你调用bindService,系统会:
- 检查Service是否存在,不存在则创建
- 调onBind(),返回一个IBinder对象
- 客户端通过这个Binder跟Service通信
- 所有客户端都解绑后,Service销毁
说白了,启动模式适合「只管干活,不管谁在调用」的场景。绑定模式适合「需要跟Service交互,拿数据」的场景。
// 绑定写法
Intent intent = new Intent(this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
// ServiceConnection
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 拿到Binder,开始通信
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 连接意外断开
}
};
13.3 前台服务:为什么必须用?
Android 8.0以后,后台Service的限制越来越严。你想想看,如果每个App都在后台乱跑,用户的电量撑不过半天。
前台服务就是「告诉用户:我在干活,别杀我」。它必须在通知栏显示一条通知,用户能明确看到。
// 启动前台服务
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("正在下载")
.setContentText("下载进度: 50%")
.setSmallIcon(R.drawable.ic_download)
.build();
startForeground(NOTIFICATION_ID, notification);
我曾经遇到一个坑:某些厂商ROM对前台服务的通知有特殊要求,比如必须带进度条或者必须显示具体内容。否则系统会直接杀掉你的Service。嗯,适配起来确实头疼。
13.4 进程保活:别做梦了
很多人问我:「怎么让Service永远不被杀?」
我的回答是:别想了。Android系统设计的目标就是保证用户体验和续航,不是让你App常驻后台的。
常见的保活手段:
- 双进程守护:两个进程互相拉起。但Android 5.0以后效果越来越差
- 前台服务:最正规的方式,但用户能手动关掉通知
- JobScheduler:系统统一调度,省电又稳定
- WorkManager:Google推荐的方案,兼容性好
13.5 ANR预防:Service里最容易踩的坑
Service默认跑在主线程。你在onStartCommand里做个网络请求,或者写个数据库操作,20秒没返回——ANR就来了。
为什么会这样?因为系统认为Service应该在短时间内完成工作。如果超时,它就认为你的App卡死了。
| 场景 | 超时时间 | 建议 |
|---|---|---|
| onCreate | 20秒 | 不要做任何耗时操作 |
| onStartCommand | 20秒 | 启动线程或协程处理 |
| onBind | 20秒 | 直接返回Binder,别阻塞 |
| onDestroy | 20秒 | 清理资源要快 |
// 正确做法:开线程
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(() -> {
// 耗时操作在这里做
// 做完记得调stopSelf
}).start();
return START_STICKY;
}
13.6 知识体系总览
下面这张图是我整理的Service核心知识结构,你看一眼就能明白整体脉络:
这张图把Service的五个核心维度都串起来了。你从中间开始,往四周看,每个分支都是一个独立的知识点。我个人建议先掌握启动和绑定,这两个是基础。前台服务是保活的正规手段,ANR预防是保命技能。