继续上一个demo,Proxy.add 加上日志:

在 Stub.onTransact 里加日志:

进程号就不打印了,因为之前已经测试过了,已经知道transact方法是运行在客户端进程,而Stub.onTransact是运行在服务端进程。
运行,关键日志:

可见,before transact 和 after transact 都是运行在客户端进程的主线程。而中间的onTransact是运行在服务进程,binder线程。而且transact是同步调用,它会等待服务端的onTransact调用结果。
问题:为什么 onTransact 跑到了另一个进程的 Binder:25891_4 线程上?因为transact 是一个同步的跨进程调用。客户端主线程会阻塞在 transact 上等结果;Binder 驱动把请求传递给服务端进程,服务端由 Binder 线程池里的一个线程执行 onTransact,执行完再把结果通过驱动送回客户端,主线程才从 transact 返回。
客户端,mRemote.transact 在 Java 层看起来是个普通方法调用,但 mRemote 是 BinderProxy,它的 transact 会走到 Native 层,最终进入 Binder 驱动。
而服务端进程启动时,主线程在跑 Looper,负责 UI、生命周期、消息循环。Binder 请求不由主线程处理,而是由 Binder 驱动维护的线程池处理。
流程总结:
1、客户端 transact 进入驱动。
2、驱动发现目标进程是 25891,把请求放到该进程的待处理队列。
3、驱动唤醒/创建一个 Binder 线程
4、该线程从Binder驱动读到请求,回调 Stub.onTransact。
5、onTransact 里调用我们写的那个add方法,把结果写进 reply。
6、线程把 reply 交回驱动。
7、驱动把 reply 送回客户端,唤醒主线程。
8、主线程从 transact 返回,打印 after transact。
画成图:
客户端进程 (pid 31216) 服务端进程 (pid 25891)
───────────────────── ─────────────────────
主线程 main
│
├─ before transact 日志
│
├─ BinderProxy.transact()
│ │
│ ▼
│ JNI -> Native
│ │
│ ▼
│ Binder 驱动 ──────────────────────► Binder 线程池
│ │ │
│ │ ├─ 创建/唤醒 Binder:25891_4
│ │ │
│ │ ├─ 回调 Stub.onTransact
│ │ │ │
│ │ │ ├─ onTransact 日志
│ │ │ ├─ 调用 add(1,2)
│ │ │ └─ 写 reply
│ │ │
│ │ ◄─────────────────────────────────┘
│ ▼
│ 主线程被唤醒
│
├─ after transact 日志
▼
继续执行
再把 transact 的最后一个参数改成 IBinder.FLAG_ONEWAY验证下,即改成这样:
boolean _status = mRemote.transact(TRANSACTION_add, data, reply, IBinder.FLAG_ONEWAY);
运行:

日志:
Groovy
---------------------------- PROCESS STARTED (11277) for package com.example.binderdemo ----------------------------
---------------------------- PROCESS STARTED (13445) for package com.example.binderdemo ----------------------------
2026-10-17 13:03:42.081 13445-13445 RemoteService2 com.example.binderdemo D onBind thread=main, pid=13445
2026-10-17 13:03:42.082 11277-11277 ClientMainActivity com.example.binderdemo D onServiceConnected thread=main
2026-10-17 13:03:42.082 11277-11277 MiniProxy com.example.binderdemo D before transact, thread=main
2026-10-17 13:03:42.082 11277-11277 MiniProxy com.example.binderdemo D after transact, status=true,thread=main
2026-10-17 13:03:42.082 13445-13468 MiniStub com.example.binderdemo D onTransact add, thread=Binder:13445_3
2026-10-17 13:03:42.082 13445-13468 RemoteService2 com.example.binderdemo D MiniStub.add(9527,0) thread=Binder:13445_3, pid=13445
2026-10-17 13:03:42.084 11277-11277 ClientMainActivity com.example.binderdemo D 当前客户端进程:11277
2026-10-17 13:03:42.084 11277-11277 ClientMainActivity com.example.binderdemo D remote: com.example.binderdemo.MiniProxy@20cc485
2026-10-17 13:03:42.084 11277-11277 ClientMainActivity com.example.binderdemo D service: android.os.BinderProxy@72e62da
result为0了。计算结果明显不对。看日志发现,after transact 紧跟 before transact,中间没有等。主线程没有阻塞在 transact 里。而且after transact 先打印,服务端 onTransact 后执行。客户端已经走完了,服务端才开始处理。所以这种情况客户端获取到的是默认值0。服务端仍然在 Binder:13445_3 线程执行,依然跨进程,变的只是"等不等结果"。
阻塞与否的分叉点在 IPCThreadState::transact 里,有这段代码:
cpp
if ((flags & TF_ONE_WAY) == 0) {
// 同步调用
if (reply) {
err = waitForResponse(reply); // 等 BR_REPLY,带回结果
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply); // 没有 reply 也等,确保事务完成
}
} else {
// oneway 调用
err = waitForResponse(nullptr, nullptr); // 也调用了 waitForResponse,但不等 reply
}
画成图是这样:
transact()
│
├─ writeTransactionData(BC_TRANSACTION, ...)
│
├─ 同步 (TF_ONE_WAY == 0)
│ └─ waitForResponse(reply)
│ ├─ 等 BR_TRANSACTION_COMPLETE(驱动确认)
│ └─ 继续等 BR_REPLY(服务端结果)→ 返回
│
└─ oneway (TF_ONE_WAY != 0)
└─ waitForResponse(nullptr, nullptr)
└─ 只等 BR_TRANSACTION_COMPLETE(驱动确认)→ 返回
transact 在 Native 层根据 TF_ONE_WAY 标志决定是否进入 waitForResponse。同步调用进入 waitForResponse,它循环和驱动交互,直到收到 reply 才返回,所以 Java 层主线程阻塞在 transact 上。oneway 调用跳过 waitForResponse,发完请求立刻返回,服务端异步处理,客户端不等结果。这就是日志里 after transact 先于 onTransact 打印的原因。