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 回调的到达线程。
/**
* 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、连接后查询以及销毁。
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,而是保存到临时队列;连接成功后 按原顺序重新发送。
@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() 的实际发送。
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()。
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 会被安全丢弃。
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 线程 同步等待重连。
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 的全局顺序。
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。
@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。
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() 清理。
@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 自动去重。
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.javaframeworks/base/core/java/android/speech/SpeechRecognizerImpl.javaframeworks/base/core/java/android/speech/IRecognitionListener.aidlframeworks/base/core/java/android/speech/IRecognitionService.aidl
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 是否抛出异常。这样才能把跨进程传输、主线程排队和业务回调三个阶段分开。
