Android Service 基础:生命周期、启动方式、绑定方式
讲 Foreground Service 之前,咱们得先把普通 Service 的基础摸透。说白了,Service 就是后台干活的一个组件,没有界面。你想想看,一个没有 UI 的东西,它怎么活?怎么被人调用?生命周期怎么走?这些搞不清楚,后面的长时间任务根本没法落地。
我刚开始接触 Service 时,踩过一个坑:以为 Service 和 Activity 一样,onCreate → onStart → onResume 那一套。结果发现完全不是那么回事。它的生命周期更简单,但也更容易在绑定和启动混合使用时绕晕。今天咱们把这几个核心点理清。
一、Service 的生命周期
Service 的生命周期只有两个关键路径:
- 启动式(Started):通过
startService()启动,独立运行,直到调用stopSelf()或外部stopService()。 - 绑定式(Bound):通过
bindService()启动,生命周期与绑定它的客户端(如 Activity)绑定,所有客户端解除绑定后自动销毁。
但实际情况中,Service 可能同时处于“已启动”和“已绑定”状态。这时候生命周期怎么走?看这张图就清楚了。
二、启动方式详解
启动 Service 最直接的方式就是 startService(Intent)。调一次,onStartCommand 就会执行一次。注意是“每次调用都执行”,但 onCreate 只在第一次创建时执行。
// 启动服务
Intent intent = new Intent(this, MyService.class);
startService(intent);
// 服务内部
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
Log.d("TAG", "Service 创建");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("TAG", "收到 start 命令");
// 在这里做耗时工作,记得另起线程
return START_STICKY; // 被系统杀掉后自动重启
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("TAG", "Service 销毁");
}
}
onStartCommand 的返回值决定了 Service 被系统杀死后的重启策略。我项目里常用的有三种:
| 返回值常量 | 含义 | 我遇到的情况 |
|---|---|---|
START_STICKY |
被杀后自动重启,但 intent 为 null | 适合纯后台轮询任务,比如位置上报 |
START_NOT_STICKY |
被杀后不重启 | 适合一次性任务,比如上传日志 |
START_REDELIVER_INTENT |
被杀后重新传递最后一次 intent | 适合关键任务,比如消息推送确认 |
三、绑定方式详解
绑定方式用于在组件和服务之间建立一条“通道”。实现方式是在 Service 里创建一个 Binder 对象,通过 onBind() 返回给客户端。客户端拿到 Binder 后可以直接调用 Service 里的方法。
我个人习惯把绑定分为两种场景:本地绑定和远程绑定(通过 AIDL)。本地绑定最简单,Service 和客户端在同一个进程时用。
// Service 端
public class LocalService extends Service {
private final IBinder binder = new LocalBinder();
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public void doHeavyWork() {
// 实际工作方法
}
}
// Activity 端
private LocalService service;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
service = ((LocalService.LocalBinder) binder).getService();
service.doHeavyWork();
}
@Override
public void onServiceDisconnected(ComponentName name) {
service = null;
}
};
@Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, LocalService.class), connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
unbindService(connection);
}
重点:绑定服务时一定要在合适的生命周期里解绑,比如 Activity 的 onStop 或 onDestroy。否则会造成泄漏,Service 永远无法销毁。我记得有一次线上崩溃就是因为在 Dialog 里绑了服务没解绑,导致 Activity 泄漏了……
四、混合使用:即启动又绑定
很多实际场景下,同一个 Service 既需要被 startService 长时间运行,又需要支持外部组件绑定获取数据。这种情况下,生命周期管理要特别小心。
- 停止时机:只有同时满足“没有客户端绑定”且“没有调用 stopSelf”时,Service 才会真正销毁。
onDestroy只会被调用一次。 - 推荐做法:启动后立刻在内部持有一个唤醒锁(WakeLock),防止 CPU 休眠。同时对外提供 Binder 查询当前状态。
- 我的教训:以前写过一个下载服务,启动+绑定混合,结果因为忘记在 onDestroy 里释放 WakeLock,导致手机一晚上掉电 30%。从那以后我都在 onDestroy 里统一清理所有资源。
五、小结(用表格收个尾)
| 特性 | 启动方式 | 绑定方式 |
|---|---|---|
| 生命周期 | 由 stopSelf/stopService 控制 | 由客户端绑定数量控制 |
| 通信方式 | 通过 Intent 传递数据 | 通过 IBinder 直接调用方法 |
| 多次调用 | onStartCommand 每次触发 | onBind 只调用一次,但可连接多个客户端 |
| 典型场景 | 后台音乐播放、位置跟踪 | 与 UI 交互的下载任务、即时数据查询 |
说到底,Service 基础不复杂,复杂的是怎么跟 Activity 的屏幕旋转、后台杀死、内存不足这些现实问题结合起来处理。下一章我们开始专门搞 Foreground Service,这个才是真刀真枪的长时间任务测试。不过在此之前,你最好亲手写一个 Demo,把启动、绑定、混合三种情况都跑一遍,观察 Logcat 里的回调顺序——我当年就是这么彻底搞懂的。
adb shell dumpsys activity services 查看当前运行的 Service 列表和绑定状态,比 Log 更直观。