IntentService 请求生命周期
IntentService 是 Service 的一个已废弃子类。它解决的不是“让 Service 并发执行任务”,而是把每次 startService() 产生的请求转成消息,交给一个单独的 HandlerThread 串行处理,处理完当前 startId 后再请求停止服务。
本文面向已经看过 HandlerThread 生命周期 和 Looper 主循环 的读者,重点回答一个可沿源码验证的问题:一次服务启动请求如何从应用进程的主线程进入 worker 队列,谁拥有 startId,什么时机决定服务停止,以及进程在处理期间死亡时 setIntentRedelivery() 能保证什么。本文不把 IntentService 当作现代后台任务方案,也不展开 JobScheduler 或 WorkManager 的实现。
1. 入口
源码文件:
frameworks/base/core/java/android/app/ActivityThread.javaframeworks/base/core/java/android/app/IntentService.java
服务实例的回调仍由应用进程主线程承接。ActivityThread.handleServiceArgs() 取出服务对象后调用 s.onStartCommand(data.args, data.flags, data.startId),调用完成后再向 ActivityManager 报告 serviceDoneExecuting()。因此,onStartCommand() 不能做耗时工作;它只应完成入队和快速返回。
private void handleServiceArgs(ServiceArgsData data) {
Service s = mServices.get(data.token);
if (s != null) {
int res;
if (!data.taskRemoved) {
res = s.onStartCommand(data.args, data.flags, data.startId);
} else {
s.onTaskRemoved(data.args);
res = Service.START_TASK_REMOVED_COMPLETE;
}
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
}
}这里的 startId 是系统为每次启动分配的递增请求标识,不是 Intent 的字段,也不是 worker 线程 ID。IntentService 后面会把它原样放进消息的 arg1,使处理完成时仍能对应回这一次启动。
2. 类边界
源码文件:frameworks/base/core/java/android/app/IntentService.java
类注释明确了三个性质:请求按需启动服务、所有请求由单个 worker 线程处理、同一时刻只处理一个请求。Android 17 源码还标注了 @Deprecated,原因是 Android 8.0 之后的后台执行限制使“随时启动后台 Service”的假设不再成立。
@Deprecated
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}IntentService 不提供绑定接口,onBind() 返回 null。它的公开扩展点是构造函数、setIntentRedelivery() 和 onHandleIntent();子类不应重写 onStartCommand() 来替换内部队列,因为该类已经把启动协议固定为“主线程入队、worker 线程消费”。
3. 创建
源码文件:frameworks/base/core/java/android/app/IntentService.java
onCreate() 只在服务实例第一次建立时执行。它创建并启动一个 HandlerThread,取得已经发布的 Looper,再用该 Looper 构造 ServiceHandler。
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}这里有两个不同的所有者:HandlerThread 拥有 worker 线程和 Looper,IntentService 保存 Looper 与 Handler 的引用。ServiceHandler 没有再创建线程,它只是把消息投递到已经启动的 worker Looper。若 onCreate() 在主线程完成后仍不能取得 Looper,说明 worker 启动或生命周期已经异常,后续 onStart() 无法正常入队。
图中 ActivityThread 和 IntentService.onStartCommand() 位于应用主线程;ServiceHandler 的 handleMessage() 和 onHandleIntent() 位于 HandlerThread。跨线程边界发生在 sendMessage(),不是发生在 startService() API 调用处。
4. 请求
源码文件:frameworks/base/core/java/android/app/IntentService.java
onStart() 把两个值写入同一个 Message:arg1 保存 startId,obj 保存 Intent。消息发送成功后,主线程立即返回;请求顺序由这个单一 Looper 的 MessageQueue 保持。
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}flags 没有被 IntentService.onStartCommand() 用来改变本次分发,而是由系统根据之前的服务存活结果传入。onStart() 也不检查 intent 是否为空;源码注释允许重启场景下收到 null,所以子类的 onHandleIntent() 必须把 null 当作合法输入边界处理。
5. 串行
源码文件:frameworks/base/core/java/android/app/IntentService.java
ServiceHandler 是非静态内部类,持有外部 IntentService 实例。Looper 每次取出一条消息后调用 handleMessage();由于只有一个 HandlerThread,下一条消息要等当前 onHandleIntent() 返回。
private final class ServiceHandler extends Handler {
ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent) msg.obj);
stopSelf(msg.arg1);
}
}stopSelf(msg.arg1) 必须放在当前 Intent 成功返回之后。假设 startId=10 和 startId=11 已经连续入队:处理 10 后调用 stopSelf(10),系统知道还有更晚的启动请求,不应把服务整体停止;处理 11 后调用 stopSelf(11),才可能进入停止流程。若这里使用无参数 stopSelf(),前一个请求就可能终止仍有后续消息的服务。
源码文件:frameworks/base/core/java/android/app/Service.java
public final void stopSelf(int startId) {
if (mActivityManager == null) {
return;
}
try {
mActivityManager.stopServiceToken(
new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
}Service 将停止请求交给 ActivityManager;IntentService 不在本地维护“还有多少条消息”的计数器。请求是否已经是最后一个启动标识,由系统服务根据 startId 和服务 token 判定。
源码文件:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
if (startId >= 0) {
ServiceRecord.StartItem si = r.findDeliveredStart(startId, false, false);
if (si != null) {
while (r.deliveredStarts.size() > 0) {
ServiceRecord.StartItem cur = r.deliveredStarts.remove(0);
cur.removeUriPermissionsLocked();
if (cur == si) {
break;
}
}
}
if (r.getLastStartId() != startId) {
return false;
}
}
bringDownServiceIfNeededLocked(r, false, false, false,
SERVICE_BIND_OOMADJ_POLICY_LEGACY, "stopServiceToken");系统侧先找到当前 startId 对应的已交付项,删除它以及更早的条目,并释放这些 start item 持有的 URI 权限;随后比较 r.getLastStartId()。只有当前 ID 等于最近一次启动 ID,才继续撤销 started 状态并尝试 bring down 服务。正是这个判断让 stopSelf(10) 在已经出现 startId=11 时返回而不停止服务。
6. 停止
源码文件:
frameworks/base/core/java/android/app/IntentService.javaframeworks/base/core/java/android/app/ActivityThread.java
最后一个 startId 的停止请求被系统接受后,应用主线程收到停止服务消息,ActivityThread.handleStopService() 调用 onDestroy()。IntentService.onDestroy() 只退出保存的 Looper。
@Override
public void onDestroy() {
mServiceLooper.quit();
}quit() 是立即退出请求,不等待正在执行的 onHandleIntent() 返回;因此“最后一条消息已经调用 stopSelf()”与“worker 线程已经结束”不是同一个时刻。onDestroy() 也没有调用 super.onDestroy(),这是 Android 17 该类的实际实现,不应凭经验补写成另一种清理顺序。
private void handleStopService(IBinder token) {
Service s = mServices.remove(token);
if (s != null) {
s.onDestroy();
s.detachAndCleanUp();
ActivityManager.getService().serviceDoneExecuting(
token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
}
}如果 onHandleIntent() 内部仍有异步线程、文件句柄或注册回调,IntentService 不会替子类清理它们。onDestroy() 的 Looper 退出只覆盖内部 HandlerThread;子类自己的资源必须在自己的生命周期代码中释放。
7. 重投
源码文件:
frameworks/base/core/java/android/app/IntentService.javaframeworks/base/core/java/android/app/Service.java
setIntentRedelivery(true) 只改变 onStartCommand() 的返回值:
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;两种返回值的差异发生在进程死亡之后:
| 返回值 | 服务进程死亡时的处理 | 适用边界 |
|---|---|---|
START_NOT_STICKY | 没有新的显式启动时不重建服务,未完成 Intent 随进程消失 | 允许任务丢失 |
START_REDELIVER_INTENT | 安排重启并重投尚未以 stopSelf(startId) 完成的最近请求 | 需要重复尝试,但必须接受重复执行 |
START_REDELIVER_INTENT 不是事务提交,也不是 exactly-once 保证。进程可能在 onHandleIntent() 已经产生外部副作用、但尚未调用 stopSelf() 时死亡,重启后同一 Intent 可能再次执行。真正需要幂等或持久化进度时,必须由任务本身建立去重和恢复协议。
图中的“重投”是系统服务根据返回值和未完成的启动状态作出的生命周期动作,不是 ServiceHandler 自己重新发送旧消息。文章只依赖 Service 对 START_REDELIVER_INTENT 的契约说明,不把它扩展成对所有崩溃时序的保证。
8. 异常
源码文件:
frameworks/base/core/java/android/app/IntentService.javaframeworks/base/core/java/android/os/Looper.java
ServiceHandler.handleMessage() 没有 try/finally:
public void handleMessage(Message msg) {
onHandleIntent((Intent) msg.obj);
stopSelf(msg.arg1);
}如果 onHandleIntent() 抛出未捕获异常,stopSelf() 不会执行;异常会沿 Looper.loopOnce() 的消息分发路径重新抛出,worker 的消息循环可能终止。此时不能把“异常后自动停止并完成清理”当成 IntentService 的保证:服务停止回调、未处理消息和进程级崩溃取决于更上层的异常处理与服务状态。
这也解释了为什么 onHandleIntent() 的业务实现需要自行捕获可恢复异常,并在必要时记录已经完成的外部副作用。START_REDELIVER_INTENT 能覆盖进程在 stopSelf() 前死亡的重启语义,却不能替业务决定失败请求是否应该重试。
9. 消费者
源码文件:
tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestService.javatests/BandwidthTests/AndroidManifest.xml
Android 源码树中的 BandwidthEnforcementTestService 是一个真实消费者:构造函数传入服务名,onHandleIntent() 在 worker 线程执行网络测试,并把 Intent extra 中的输出文件名用于结果写入。
public class BandwidthEnforcementTestService extends IntentService {
public BandwidthEnforcementTestService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
String outputFile = intent.getStringExtra(OUTPUT_FILE);
dumpResult("testUrlConnection", testUrlConnection(), outputFile);
dumpResult("testSntp", testSntp(getApplicationContext()), outputFile);
}
}它的 manifest 注释给出了可执行入口:
<!-- adb shell am startservice -n com.android.tests.bandwidthenforcement/.BandwidthEnforcementTestService -->
<service android:name=".BandwidthEnforcementTestService" />这个消费者证明的是使用方式和线程归属,不证明后台执行限制已经被绕过,也不证明网络测试结果必然成功。读者可以沿 startservice、onStartCommand、onHandleIntent 和输出文件四个节点检查自己的理解。
10. 复现
源码文件:
frameworks/base/core/java/android/app/IntentService.javaframeworks/base/core/java/android/app/ActivityThread.javaframeworks/base/services/core/java/com/android/server/am/ActiveServices.javatests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestService.java
可以用下面的搜索把文章主线重新接回源码:
rg -n "handleServiceArgs|onStartCommand|serviceDoneExecuting" \
frameworks/base/core/java/android/app/ActivityThread.java
rg -n "onCreate|onStart\(|onStartCommand|handleMessage|stopSelf|onDestroy" \
frameworks/base/core/java/android/app/IntentService.java
rg -n "START_NOT_STICKY|START_REDELIVER_INTENT|START_FLAG_REDELIVERY|stopSelf\(" \
frameworks/base/core/java/android/app/Service.java
rg -n "stopServiceTokenLocked|findDeliveredStart|getLastStartId|deliveredStarts" \
frameworks/base/services/core/java/com/android/server/am/ActiveServices.java读者应能据此回答三个具体问题:为什么 onStartCommand() 在主线程只做入队?stopSelf(startId) 如何避免前一个请求结束时误停服务?若进程在 onHandleIntent() 返回前死亡,哪些行为由 START_REDELIVER_INTENT 覆盖,哪些幂等性仍必须由业务实现?
11. 边界
Android 17 的 IntentService 仍然展示了清晰的“Service 主线程接收 → HandlerThread 串行消费 → startId 条件停止”模式,但它受后台执行限制影响并已废弃。它不提供并发处理、持久化队列、网络约束、exactly-once 执行或子类资源的自动清理;这些需求不能从 HandlerThread + Handler 的组合中推导出来。
