Skip to content

Binder 回调转主线程

以 SpeechRecognizerImpl 为真实案例,追踪远端识别服务回调如何进入 Binder Stub、转成主线程 Message,并在销毁和断连时丢弃迟到事件。

基于android-17.0.0_r1
AndroidBinderSpeechRecognizerHandler源码阅读

Binder 回调转主线程 ​

Binder 回调并不会自动运行在应用主线程。远端服务调用应用进程中的 Stub 时,回调首先进入 Binder 线程;如果 API 要求 RecognitionListener 在主线程执行,Stub 必须显式把数据封装成 Message 或交给 Executor。Android 17 的 SpeechRecognizerImpl 直接展示了这条路径: IRecognitionListener.Stub 收到服务回调,InternalRecognitionListener 把结果发送给主线程 Handler,Handler 再调用真正的 RecognitionListener。

本文面向已经读过 ActivityThread.H 主线程、 ContentObserver Loader 回调、Handler消息发送 和 InputEventReceiver 输入路径 的读者。本文只讲一个 真实 Binder callback 适配器,不把“Binder 转主线程”写成所有接口都必须遵守的模板;也不展开 Binder 驱动线程池、RecognitionService 内部录音或语音模型执行。

读完后,读者应能定位远端 callback 的 Stub、主线程 Handler、Message payload 和最终 listener, 解释连接建立前为什么要暂存命令,判断 destroy() 后的消息如何失效,并区分“Binder 方法 已返回”“Message 已入队”和“用户 listener 已执行”。

1. API 线程契约 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizer.java

SpeechRecognizer 的类注释要求其方法只能从应用主线程调用,并要求不再使用时调用 destroy()。 这约束的是调用者发起 startListening()、stopListening()、cancel() 的线程,不是远端 RecognitionListener Binder 回调的到达线程。

java
/**
 * This class's methods must be invoked only from the main application thread.
 * The caller MUST invoke destroy() when it is no longer needed.
 */
public abstract class SpeechRecognizer {
    @MainThread
    public void startListening(Intent recognizerIntent) {
        throw new UnsupportedOperationException();
    }

    public void destroy() {
        throw new UnsupportedOperationException();
    }
}

因此主线程 Handler 在这里有两个用途:执行调用者命令,执行远端回调。两条消息流都汇聚到 SpeechRecognizerImpl.mHandler,但它们的 owner 和 payload 不同。

2. 主线程 Handler ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

SpeechRecognizerImpl 用 new Handler(Looper.getMainLooper()) 创建内部 Handler,消息编号覆盖 开始、停止、取消、切换 listener、连接后查询以及销毁。

java
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_START -> handleStartListening((Intent) msg.obj);
            case MSG_STOP -> handleStopMessage();
            case MSG_CANCEL -> handleCancelMessage();
            case MSG_CHANGE_LISTENER -> handleChangeListener((RecognitionListener) msg.obj);
            case MSG_CHECK_RECOGNITION_SUPPORT -> {
                CheckRecognitionSupportArgs args = (CheckRecognitionSupportArgs) msg.obj;
                handleCheckRecognitionSupport(args.mIntent, args.mCallbackExecutor, args.mCallback);
            }
            case MSG_DESTROY -> handleDestroy();
        }
    }
};

Handler 的 Looper 由 API 线程契约固定为主 Looper;远端 Binder callback 不应直接调用 mInternalListener,而应发送 Message 让主线程顺序消费。

3. 命令入队 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

startListening()、stopListening() 和 cancel() 先检查主线程,再把命令包装成 Message。 如果远端服务尚未连接,putMessage() 不立即发给 Handler,而是保存到临时队列;连接成功后 按原顺序重新发送。

java
@Override
public void startListening(final Intent recognizerIntent) {
    if (recognizerIntent == null) {
        throw new IllegalArgumentException("intent must not be null");
    }
    checkIsCalledFromMainThread();
    if (mService == null) {
        connectToSystemService();
    }
    putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
}

private void putMessage(Message msg) {
    if (mService == null) {
        mPendingTasks.offer(msg);
    } else {
        mHandler.sendMessage(msg);
    }
}

这里存在两个队列:连接前的 mPendingTasks 和连接后的主线程 MessageQueue。前者不是 MessageQueue,因此 removeMessages() 不能替代它的清理;handleDestroy() 必须显式 mPendingTasks.clear()。

4. 连接重放 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

系统语音服务连接成功的 Binder callback 设置 mService,然后把暂存命令逐个投递给主线程 Handler。Binder callback 自身不执行 startListening() 的实际发送。

java
new IRecognitionServiceManagerCallback.Stub() {
    @Override
    public void onSuccess(IRecognitionService service) throws RemoteException {
        mService = service;
        while (!mPendingTasks.isEmpty()) {
            mHandler.sendMessage(mPendingTasks.poll());
        }
    }

    @Override
    public void onError(int errorCode) throws RemoteException {
        mListener.onError(errorCode);
    }
}

连接成功 callback 可能不在主线程;源码将消息发送到主 Handler,但 mService 字段和 pending 队列的并发可见性需要结合连接流程阅读,不能仅凭 sendMessage() 推导完整同步协议。

5. 远端 Stub ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

InternalRecognitionListener 实现 IRecognitionListener.Stub,它是远端识别服务调用的 Binder 入口。每个 callback 方法只负责用 Message.obtain(mInternalHandler, ...) 包装参数并 sendToTarget()。

java
private static class InternalRecognitionListener extends IRecognitionListener.Stub {
    private RecognitionListener mInternalListener;

    public void onBeginningOfSpeech() {
        Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
    }

    public void onError(final int error) {
        Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
    }

    public void onResults(final Bundle results) {
        Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
    }

    public void onPartialResults(final Bundle results) {
        Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
    }
}

这些方法不调用 mInternalListener.onResults()。Binder 线程的工作在 Message 入队后结束; 真正的 listener 由主 Looper 消费对应 Message。

6. 回调分发 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

内部 Handler 取出 Message 后,先检查 listener 是否仍存在,再按 what 转换 payload 并调用 用户回调。listener 被置空后,迟到的 Message 会被安全丢弃。

java
private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        if (mInternalListener == null) {
            return;
        }
        switch (msg.what) {
            case MSG_ERROR:
                mInternalListener.onError((Integer) msg.obj);
                break;
            case MSG_RESULTS:
                mInternalListener.onResults((Bundle) msg.obj);
                break;
            case MSG_PARTIAL_RESULTS:
                mInternalListener.onPartialResults((Bundle) msg.obj);
                break;
            case MSG_RMS_CHANGED:
                mInternalListener.onRmsChanged((Float) msg.obj);
                break;
        }
    }
};

mInternalListener == null 是消费时的生命周期闸门。它不能撤回已入队消息,但能阻止消息在 销毁后调用旧 listener。

图中 Binder 返回发生在主线程 listener 执行之前;这正是“远端回调已发送”与“应用回调已 处理”的差异。

7. 服务调用 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

主 Handler 取出 MSG_START 后,才调用远端 IRecognitionService.startListening()。服务断连 时 checkOpenConnection() 失败,内部 listener 收到 ERROR_CLIENT,而不是在 Binder 线程 同步等待重连。

java
private void handleStartListening(Intent recognizerIntent) {
    if (!checkOpenConnection()) {
        return;
    }
    try {
        mService.startListening(
                recognizerIntent, mListener, mContext.getAttributionSource());
    } catch (final Exception e) {
        Log.e(TAG, "startListening() failed", e);
        mListener.onError(ERROR_CLIENT);
    }
}

private boolean checkOpenConnection() {
    if (mService != null && mService.asBinder().isBinderAlive()) {
        return true;
    }
    mListener.onError(ERROR_CLIENT);
    return false;
}

这里的 mListener.onError() 会再次进入主 Handler Message 路径;异常处理没有绕过线程契约。

8. 回调序列 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

多个远端 callback 都发送到同一个主 Looper Handler,因此同一 recognizer 的回调按 MessageQueue 顺序串行执行。但这不等价于服务端事件时间戳排序,也不保证不同 Binder source 的全局顺序。

java
public void onReadyForSpeech(final Bundle noiseParams) {
    Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
}

public void onEndOfSpeech() {
    Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
}

public void onResults(final Bundle results) {
    Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
}

如果主线程正执行其它 callback,语音结果 Message 只能等待;Handler 转发解决线程归属, 不提供主线程抢占。

9. 销毁入口 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

destroy() 不直接调用远端 cancel,而是投递 MSG_DESTROY。这样销毁也遵守同一主线程顺序, 不会和已排队的 start/stop/cancel 操作并发修改 mService 和 listener。

java
@Override
public void destroy() {
    putMessage(mHandler.obtainMessage(MSG_DESTROY));
}

private void handleDestroy() {
    if (mService != null) {
        try {
            mService.cancel(mListener, /* isShutdown */ true);
        } catch (final Exception ignored) {
        }
    }
    mService = null;
    mPendingTasks.clear();
    mListener.mInternalListener = null;
}

销毁动作是顺序化的:先通知远端取消,再清空连接前 pending 命令,最后让后续回调因 listener 为空而丢弃。已经执行完的用户 listener 不会被回滚。

10. 支持回调 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

新式 support/download API 不一定使用固定主 Handler 作为最终回调线程;它把调用者提供的 Executor 保存到 InternalSupportCallback/InternalModelDownloadListener,Binder callback 直接交给该 Executor。

java
private static class InternalSupportCallback extends IRecognitionSupportCallback.Stub {
    private final Executor mExecutor;
    private final RecognitionSupportCallback mCallback;

    @Override
    public void onSupportResult(RecognitionSupport result) throws RemoteException {
        mExecutor.execute(() -> mCallback.onSupportResult(result));
    }

    @Override
    public void onError(int errorCode) throws RemoteException {
        mExecutor.execute(() -> mCallback.onError(errorCode));
    }
}

这条路径说明“Binder 回调转主线程”不是 API 的唯一选择:旧 RecognitionListener 固定走 主 Handler,新 Executor API 把线程选择权交给调用者。阅读源码时应先确认 callback contract。

11. 连接失败 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

连接失败 callback 不会自动把 mPendingTasks 重放;它把错误交给 listener。连接成功路径才 把 pending Message 转入主 Handler。因此连接失败时,之前缓存的命令可能一直留在临时队列,直到 destroy() 清理。

java
@Override
public void onError(int errorCode) throws RemoteException {
    Log.e(TAG, "Bind to system recognition service failed with error " + errorCode);
    mListener.onError(errorCode);
}

这里的错误路径不能被简化成“Handler 发送失败”:消息可能还没进入 Handler,而是停留在 mPendingTasks;不同阶段的失败需要分别检查。

12. 重复销毁 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

多次 destroy() 会继续向主 Handler 投递消息,但第一次 handleDestroy() 后, mService == null、pending 已清空、listener 已置空;后续销毁消息不会恢复服务,也不会调用 旧 listener。这个行为来自消费时状态检查,而不是 MessageQueue 自动去重。

java
private void handleDestroy() {
    if (mService != null) {
        // 只在仍有远端服务时发送 shutdown cancel
    }
    mService = null;
    mPendingTasks.clear();
    mListener.mInternalListener = null;
}

13. 验证输入 ​

源码文件:frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

第一组验证主线程命令:在主线程调用 startListening(),使用 fake IRecognitionService, 断言远端调用发生在 mHandler 所属主线程,而不是发起 Binder callback 的线程。

第二组验证远端回调:fake service 在非主线程调用 IRecognitionListener.onResults(),断言 RecognitionListener.onResults() 在主 Looper 执行;主线程阻塞时,先观察 Binder 方法返回, 再观察 listener 尚未执行。

第三组验证销毁:先让一个 callback Message 入队,再处理 MSG_DESTROY,随后推进旧 callback, 断言 mInternalListener == null 时不再调用用户 listener;连接前 pending 命令则应在 destroy 后清空。

这些输入证明线程切换和生命周期闸门,不证明 Binder 驱动线程数量、RecognitionService 内部 识别质量或用户 callback 的 exactly-once 语义。

14. 复查命令 ​

源码文件:

  • frameworks/base/core/java/android/speech/SpeechRecognizer.java
  • frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java
  • frameworks/base/core/java/android/speech/IRecognitionListener.aidl
  • frameworks/base/core/java/android/speech/IRecognitionService.aidl
bash
rg -n "mHandler = new Handler|MSG_START|MSG_DESTROY|putMessage|mPendingTasks" \
  frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

rg -n "class InternalRecognitionListener|onResults|onError|sendToTarget|mInternalListener" \
  frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

rg -n "connectToSystemService|onSuccess|onError|handleDestroy|checkOpenConnection" \
  frameworks/base/core/java/android/speech/SpeechRecognizerImpl.java

复述这条链路时,应区分:主线程命令 Message、连接前 pending queue、远端 Binder callback Stub、主线程结果 Message、用户 listener 和 destroy 状态。只写“Binder 回调 post 到主线程” 会漏掉连接、失败、重放和销毁阶段。

15. 适用边界 ​

Binder Stub + Handler 适合远端回调必须遵守本地线程契约、且需要把多个事件串行化的对象。它 不自动保证 callback 完成、不提供远端重试、不撤回已执行 listener,也不意味着所有 Binder 方法都必须转到主线程。简单只读查询可以直接在 Binder 线程完成,耗时工作则应选择明确的 worker Executor/Handler。

当排查“远端结果到了但 UI 没更新”时,按源码检查:Binder Stub 是否收到 callback,Message 是否进入目标 Handler,连接/销毁状态是否丢弃消息,主 Looper 是否忙碌,以及用户 listener 是否抛出异常。这样才能把跨进程传输、主线程排队和业务回调三个阶段分开。