Skip to content

IntentService 请求生命周期

从 ActivityThread 的服务启动回调追踪 IntentService 的串行队列、startId 停止判定和进程死亡重投语义。

基于android-17.0.0_r1
AndroidIntentServiceServiceHandlerThread源码阅读

IntentService 请求生命周期 ​

IntentService 是 Service 的一个已废弃子类。它解决的不是“让 Service 并发执行任务”,而是把每次 startService() 产生的请求转成消息,交给一个单独的 HandlerThread 串行处理,处理完当前 startId 后再请求停止服务。

本文面向已经看过 HandlerThread 生命周期 和 Looper 主循环 的读者,重点回答一个可沿源码验证的问题:一次服务启动请求如何从应用进程的主线程进入 worker 队列,谁拥有 startId,什么时机决定服务停止,以及进程在处理期间死亡时 setIntentRedelivery() 能保证什么。本文不把 IntentService 当作现代后台任务方案,也不展开 JobScheduler 或 WorkManager 的实现。

1. 入口 ​

源码文件:

  • frameworks/base/core/java/android/app/ActivityThread.java
  • frameworks/base/core/java/android/app/IntentService.java

服务实例的回调仍由应用进程主线程承接。ActivityThread.handleServiceArgs() 取出服务对象后调用 s.onStartCommand(data.args, data.flags, data.startId),调用完成后再向 ActivityManager 报告 serviceDoneExecuting()。因此,onStartCommand() 不能做耗时工作;它只应完成入队和快速返回。

java
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”的假设不再成立。

java
@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。

java
@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 保持。

java
@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() 返回。

java
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

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

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.java
  • frameworks/base/core/java/android/app/ActivityThread.java

最后一个 startId 的停止请求被系统接受后,应用主线程收到停止服务消息,ActivityThread.handleStopService() 调用 onDestroy()。IntentService.onDestroy() 只退出保存的 Looper。

java
@Override
public void onDestroy() {
    mServiceLooper.quit();
}

quit() 是立即退出请求,不等待正在执行的 onHandleIntent() 返回;因此“最后一条消息已经调用 stopSelf()”与“worker 线程已经结束”不是同一个时刻。onDestroy() 也没有调用 super.onDestroy(),这是 Android 17 该类的实际实现,不应凭经验补写成另一种清理顺序。

java
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.java
  • frameworks/base/core/java/android/app/Service.java

setIntentRedelivery(true) 只改变 onStartCommand() 的返回值:

java
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.java
  • frameworks/base/core/java/android/os/Looper.java

ServiceHandler.handleMessage() 没有 try/finally:

java
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.java
  • tests/BandwidthTests/AndroidManifest.xml

Android 源码树中的 BandwidthEnforcementTestService 是一个真实消费者:构造函数传入服务名,onHandleIntent() 在 worker 线程执行网络测试,并把 Intent extra 中的输出文件名用于结果写入。

java
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 注释给出了可执行入口:

xml
<!-- 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.java
  • frameworks/base/core/java/android/app/ActivityThread.java
  • frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
  • tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestService.java

可以用下面的搜索把文章主线重新接回源码:

bash
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 的组合中推导出来。