搜索调用链
本文面向已经能在 AOSP 目录中执行 rg,但经常把“搜到一个同名字符串”误当成“找到了真实调用”的读者。建议先读 ADB架构 了解进程边界,再读 Java调用链 了解客户端事务角色;本文不重复启动机制,而是解释如何把搜索结果连成可继续追踪的调用链。
我们使用 Android 17 frameworks/base 中的 IApplicationThread.scheduleTransaction 作为贯穿案例。问题不是“哪个工具最快”,而是:从 AIDL 声明开始,如何确认谁实现接口、谁调用它、调用在哪个进程、消息何时入队、哪个测试约束了这条关系?
本文不把 cs.android.com、grep、ctags、cscope 做成横向功能清单,也不声称交叉引用工具能理解 Java 反射、AIDL 生成物或厂商分支的全部动态行为。读完后,你应能把一次搜索拆成发现、分类、双向追踪、源码核对和测试核对五个阶段,并能说明每个工具的能力边界。
1. 搜索强度
搜索结果有不同强度。命中一行文本只能确认文件中出现了字符;找到定义后可以确认声明位置;找到调用方和消费者,才开始形成调用链;测试断言用于检查顺序和条件边界。
| 层级 | 要回答的问题 | 工具 | 未覆盖内容 |
|---|---|---|---|
| 发现 | 哪些文件出现符号? | rg、git grep | 同名是否同义 |
| 定义 | 哪个声明是目标? | ctags、源码上下文 | 调用者和时机 |
| 关系 | 谁调用、谁被调用? | cscope、在线交叉引用 | 反射和生成代码 |
| 语义 | 参数如何变、状态谁拥有? | 固定源码阅读 | 产品运行时差异 |
| 测试 | 测试约束什么? | AOSP tests、实验 | 未覆盖分支 |
搜索工具是导航工具,不是结论生成器。关键结论要有“路径 + 符号 + 调用上下文”,重要行为还要有测试、失败分支或实验。
2. 案例问题
Android 17 的 AIDL 声明只有一句 scheduleTransaction,但它跨越两个进程:system_server 将 ClientTransaction 发给应用进程,应用进程的 ApplicationThread 再把请求交给 ActivityThread。本文要找齐四类位置:协议声明、服务端发送者、Binder 入口到 Handler 的转换,以及检查顺序或取消的测试。
同一符号会出现在 AIDL、生成接口、实现类、测试和注释中;rg -n scheduleTransaction 的结果很多,但只有少数行能确定调用方向和线程边界。
3. 文本发现
3.1 固定版本
先在固定 tag 上搜索,而不是浮动分支或可能含本地修改的工作树。下面的 git grep 和 git show 命令应在 frameworks/base Git project 内执行;tree-ish 属于该 project,不是整个 AOSP 工作区的单一提交。
git grep -n 'scheduleTransaction' android-17.0.0_r1 -- \
core/java/android/app services/core/java/com/android/server/wm
git grep -n 'scheduleTransaction' android-17.0.0_r1 -- \
core/java/android/app/IApplicationThread.aidlAndroid 17 的真实命中包括:IApplicationThread.aidl:170 协议声明,ActivityThread.java:2394 的 ApplicationThread 方法,ClientTransaction.java:245 的调用,以及 ClientLifecycleManager.java 和 ActivityTaskSupervisor.java 的服务端发送路径。这一步只得到候选位置,动态方向还要通过接收者类型、参数和下游消费者确认。
3.2 范围过滤
rg 适合在已 checkout 源码中快速迭代。先按路径、语言和词边界过滤,避免把 scheduleTransactionItems、注释和测试辅助方法混在一起:
rg -n -w 'scheduleTransaction' \
frameworks/base/core/java/android/app \
frameworks/base/services/core/java/com/android/server/wm
rg -n -w 'scheduleTransaction' \
--glob '*.java' --glob '*.aidl' \
frameworks/base/core/java/android/app \
frameworks/base/services/core/java/com/android/server/wm
rg -l -w 'scheduleTransaction' \
frameworks/base/core/java/android/app \
frameworks/base/services/core/java/com/android/server/wm-w 只是文本边界,不是语义解析。它仍会同时命中声明、实现和调用,下一步必须读接收者类型与进程上下文。
3.3 上下文窗口
git show android-17.0.0_r1:core/java/android/app/IApplicationThread.aidl \
| sed -n '160,176p'
git show android-17.0.0_r1:core/java/android/app/ActivityThread.java \
| sed -n '2384,2402p'
git show android-17.0.0_r1:core/java/android/app/ClientTransactionHandler.java \
| sed -n '52,65p'三段上下文分别显示:AIDL 定义跨进程契约;ApplicationThread 把 Binder 回调转发给外层 ActivityThread;ClientTransactionHandler 才把事务转换为 H.EXECUTE_TRANSACTION。把它们合并成“接口实现直接执行事务”会丢失线程边界。
4. 定义导航
4.1 ctags边界
ctags 的强项是从当前文件跳到定义。它适合确认类型或方法声明位置,不会自动还原 Java AIDL 生成代理、反射或调用者集合。AOSP 很大时,只为当前专题目录建索引:
cd frameworks/base
ctags -R -f /tmp/frameworks-base-app.tags \
core/java/android/app \
services/core/java/com/android/server/wm
rg -n '^scheduleTransaction\s' /tmp/frameworks-base-app.tags | head不同 ctags 实现对 Java、AIDL 和注解的支持并不完全一致;需要语言过滤时再根据本机实现查看 ctags --list-languages,tags 命中只能作为导航线索,正文仍应回到固定 tag 源码路径。
4.2 声明与实现
源码文件:frameworks/base/core/java/android/app/IApplicationThread.aidl
interface IApplicationThread {
// ... other process callbacks
void scheduleTransaction(in ClientTransaction transaction);
}AIDL 声明确定了协议形状;下一段再确认哪个手写 Java 类继承生成的 Stub 并接收回调。
源码文件:frameworks/base/core/java/android/app/ActivityThread.java
private class ApplicationThread extends IApplicationThread.Stub {
@Override
public void scheduleTransaction(ClientTransaction transaction)
throws RemoteException {
ActivityThread.this.scheduleTransaction(transaction);
}
}这里要标注“实现”而不是“消费者”:ApplicationThread 是 Binder 回调入口,真正消费 ClientTransaction 的是 ActivityThread.H 和 TransactionExecutor。
5. 关系追踪
5.1 文本反查
rg -n -C 4 'mClient\.scheduleTransaction\(' \
frameworks/base/core/java/android/app/servertransaction
rg -n -C 5 'scheduleTransactionItems\(' \
frameworks/base/services/core/java/com/android/server/wm --glob '*.java'
rg -n -C 3 'H\.EXECUTE_TRANSACTION|mTransactionExecutor\.execute' \
frameworks/base/core/java/android/app/ActivityThread.java这三次搜索分别回答封装发送、服务端调度和客户端消费。它们可能漏掉反射、生成代码、变量重命名或条件分支,所以结论应写成“在已搜索路径中发现”,不能写成“全局只有这些调用”。
5.2 cscope范围
cscope 主要面向 C/C++ 符号和 caller/callee 查询,不应被描述为 AOSP Java 调用图工具。对 frameworks/native 的 Binder C++ 专题,可以按明确文件列表建立数据库:
cd frameworks/native
find libs/binder -type f \
\( -name '*.c' -o -name '*.cc' -o -name '*.cpp' -o -name '*.h' \) \
> /tmp/binder.cscope.files
cscope -b -q -k -i /tmp/binder.cscope.files
cscope -d -L3 'IPCThreadState::transact'
cscope -d -L2 'IPCThreadState::transact'数据库依赖建立时的文件列表和解析能力;如果目录未加入数据库,空结果不是“没有调用”。它也不会自动知道 Java AIDL 生成关系。
5.3 在线引用
cs.android.com 适合没有完整本地 checkout 时查看官方树的定义和引用;它是辅助导航,不包含当前产品的本地修改或厂商私有代码。使用时应固定仓库和分支,最终结论仍回到 Android 17 Git object。
https://cs.android.com/search?q=scheduleTransaction&sq=&ss=android%2Fplatform%2Fframeworks%2Fbase无论候选关系来自本地索引还是在线引用,最终都要进入同一条“候选 → 固定源码 → 测试”的取证时序。
6. 主线核对
6.1 服务发送
搜索到 scheduleTransactionItems 后,必须读它如何聚合 item、何时发送。Android 17 将 item 放进 ClientTransaction,冷启动场景要求立即 dispatch:
源码文件:frameworks/base/services/core/java/com/android/server/wm/ClientLifecycleManager.java
boolean scheduleTransactionItems(@NonNull IApplicationThread client,
boolean shouldDispatchImmediately,
@NonNull ClientTransactionItem... items) {
final ClientTransaction transaction =
getOrCreatePendingTransaction(client);
for (int i = 0; i < items.length; i++) {
transaction.addTransactionItem(items[i]);
}
return onClientTransactionItemScheduled(transaction,
shouldDispatchImmediately);
}
private boolean onClientTransactionItemScheduled(
ClientTransaction clientTransaction,
boolean shouldDispatchImmediately) {
if (shouldDispatchImmediately
|| shouldDispatchPendingTransactionsImmediately()) {
mPendingTransactions.remove(clientTransaction.getClient().asBinder());
return scheduleTransaction(clientTransaction);
}
return true;
}调用者 ActivityTaskSupervisor 明确传入 true,并消费发送失败:
源码文件:frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
final boolean isSuccessful = mService.getLifecycleManager()
.scheduleTransactionItems(proc.getThread(),
true /* shouldDispatchImmediately */,
launchActivityItem, lifecycleItem);
if (!isSuccessful) {
return new DeadObjectException(
"Failed to dispatch the ClientTransaction to dead process");
}继续进入 ClientLifecycleManager.scheduleTransaction,才能知道布尔失败来自哪里:ClientTransaction.schedule() 捕获的 RemoteException 被转换为 false,而不是在此处直接抛给 ActivityTaskSupervisor。
源码文件:frameworks/base/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@VisibleForTesting
boolean scheduleTransaction(@NonNull ClientTransaction transaction) {
final RemoteException e = transaction.schedule();
if (e != null) {
final WindowProcessController wpc =
mWms.mAtmService.getProcessController(transaction.getClient());
Slog.w(TAG, "Failed to deliver transaction for " + wpc
+ "\ntransaction=" + this, e);
return false;
}
return true;
}这段代码确认了“立即发送”和失败被观察的时机;它不包含 Activity 的 onCreate() 执行。搜索时若只停在返回 DeadObjectException 的调用方,就会漏掉真正捕获 Binder 异常的位置。
6.2 客户端消费
源码文件:frameworks/base/core/java/android/app/servertransaction/ClientTransaction.java
public RemoteException schedule() {
try {
mClient.scheduleTransaction(this);
return null;
} catch (RemoteException e) {
return e;
}
}远端调用返回应用进程后,下一段代码才把同一个 transaction 交给主线程消息队列。
源码文件:frameworks/base/core/java/android/app/ClientTransactionHandler.java
void scheduleTransaction(ClientTransaction transaction) {
transaction.preExecute(this);
sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}前者是 Binder 调用,后者是消息入队。还要看到 H.handleMessage 中的消费者,才能把“发送消息”写成“事务开始执行”:
源码文件:frameworks/base/core/java/android/app/ActivityThread.java
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
final ClientTransactionListenerController controller =
ClientTransactionListenerController.getInstance();
controller.onClientTransactionStarted();
try {
mTransactionExecutor.execute(transaction);
} finally {
controller.onClientTransactionFinished();
}
break;6.3 顺序测试
TransactionExecutorTests 用 mock handler 和状态记录,验证事务 item 的顺序:
源码文件:frameworks/base/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@Test
public void testTransactionResolution() {
ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
final ClientTransaction transaction = new ClientTransaction();
transaction.addTransactionItem(callback1);
transaction.addTransactionItem(callback2);
transaction.addTransactionItem(mActivityLifecycleItem);
transaction.preExecute(mTransactionHandler);
mExecutor.execute(transaction);
InOrder inOrder = inOrder(mTransactionHandler, callback1,
callback2, mActivityLifecycleItem);
inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
inOrder.verify(mActivityLifecycleItem).execute(
eq(mTransactionHandler), eq(mClientRecord), any());
}TransactionExecutor 再按 item 类型选择普通 callback 或生命周期 callback。当前索引 i 会影响是否提前走到最后生命周期状态,因此不能只搜索 item.execute 并忽略循环上下文:
源码文件:frameworks/base/core/java/android/app/servertransaction/TransactionExecutor.java
public void executeTransactionItems(@NonNull ClientTransaction transaction) {
final List<ClientTransactionItem> items = transaction.getTransactionItems();
final int size = items.size();
for (int i = 0; i < size; i++) {
final ClientTransactionItem item = items.get(i);
if (item.isActivityLifecycleItem()) {
executeLifecycleItem(transaction, (ActivityLifecycleItem) item);
} else {
executeNonLifecycleItem(transaction, item,
shouldExcludeLastLifecycleState(items, i));
}
}
}这个测试检查 item 执行顺序和 handler 关系,没有启动真实 Binder 或应用 Looper。搜索到测试名称后仍需读 arrange/action/assert,不能把“有测试文件”当作已经走过真实跨进程链路。
同一测试文件还覆盖取消竞态。测试先让 destroy transaction 的 preExecute 把 token 放入待销毁容器,再执行排队更早的 launch transaction,断言启动 item 不被消费:
源码文件:frameworks/base/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@Test
public void testDoNotLaunchDestroyedActivity() {
final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed =
new ArrayMap<>();
when(mTransactionHandler.getActivitiesToBeDestroyed())
.thenReturn(activitiesToBeDestroyed);
when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
final IBinder token = mock(IBinder.class);
final ClientTransaction destroyTransaction = new ClientTransaction();
destroyTransaction.addTransactionItem(
new DestroyActivityItem(token, false));
destroyTransaction.preExecute(mTransactionHandler);
final ClientTransaction launchTransaction = new ClientTransaction();
final LaunchActivityItem launchItem = spy(
new LaunchActivityItemBuilder(token, new Intent(),
new ActivityInfo()).build());
launchTransaction.addTransactionItem(launchItem);
mExecutor.execute(launchTransaction);
verify(launchItem, never()).execute(any(), any());
mExecutor.execute(destroyTransaction);
assertTrue(activitiesToBeDestroyed.isEmpty());
}该断言检查取消容器会阻止一个尚未创建 Activity record 的 launch item;它不检查 Binder 到达顺序,因为测试直接构造并执行 transaction。
7. 失败分流
7.1 漏搜原因
结果为空或过少时,先检查搜索边界:tag 是否正确,是否漏了 .aidl、生成目录或语言 glob,是否只搜了一个 project,是否符号通过字符串或反射间接使用。
git diff --stat android-17.0.0_r1 -- \
core/java/android/app services/core/java/com/android/server/wm
rg -n 'IApplicationThread|ClientTransaction|EXECUTE_TRANSACTION' \
frameworks/base/core/java/android/app \
frameworks/base/services/core/java/com/android/server/wm
rg -n 'scheduleTransaction' out/soong 2>/dev/null | headout/soong 的生成 Java 可帮助解释编译结果,但不能替代固定 tag 源码事实。
7.2 角色误判
| 命中 | 角色 | 进程 | 输入 owner | 输出消费者 |
|---|---|---|---|---|
IApplicationThread.aidl | 协议声明 | 跨进程 | AIDL 编译器 | Stub/Proxy |
ClientLifecycleManager | 发送调度 | system_server | ClientTransaction | IApplicationThread |
ApplicationThread | Binder 入口 | 应用 | 远端事务 | ActivityThread.scheduleTransaction |
ClientTransactionHandler | 消息入队 | 应用 | transaction 参数 | ActivityThread.H |
TransactionExecutor | 事务消费 | 应用主线程 | item 列表 | lifecycle callback |
角色表迫使读者回答状态在哪里生效、谁读取它,也能暴露搜索结果中缺失的跨进程边界。
7.3 工具故障
8. 搜索复现
8.1 五步搜索
git grep -n 'SYMBOL' android-17.0.0_r1 -- path/to/project
rg -n -C 3 'SYMBOL|InterfaceName|TestName' \
path/to/project --glob '*.java' --glob '*.aidl'
rg -n -C 5 'receiver\.SYMBOL\(|return .*SYMBOL' path/to/project
git show android-17.0.0_r1:path/to/file.java | sed -n 'N,Mp'
rg -n -C 5 'TEST_NAME|assert|verify|expected' path/to/tests五步分别对应发现、分类、双向反查、固定源码核对和对应测试。对本文案例,应能复述为:AIDL 定义协议;ClientLifecycleManager 发送 transaction;ClientTransaction.schedule() 调 Binder;ApplicationThread 转发;ClientTransactionHandler 入队;TransactionExecutor 消费。测试覆盖顺序和状态路径,不包含真实跨进程传输。
8.2 可执行核对
for pattern in \
'IApplicationThread' \
'ClientLifecycleManager' \
'mClient\.scheduleTransaction' \
'EXECUTE_TRANSACTION' \
'TransactionExecutorTests'; do
printf '\n== %s ==\n' "$pattern"
rg -n "$pattern" frameworks/base/core/java/android/app \
frameworks/base/services/core/java/com/android/server/wm \
frameworks/base/core/tests/coretests/src/android/app/servertransaction \
| head -20
done如果设备可用,可将源码路径与运行现象对照,但不要把一次设备结果外推为所有产品:
adb shell am start -W -n com.android.settings/.Settings
adb logcat -d -s ActivityTaskManager ActivityThread | tail -809. 工具边界
本文展示的是一个完整搜索过程:在 Android 17 固定源码中,从 IApplicationThread.scheduleTransaction 文本命中出发,经过定义分类、发送者与消费者核对、线程边界确认和测试断言阅读,建立 Java 调用关系。
rg 不理解 Java 语义,ctags 不保证生成完整 AIDL 交叉引用,cscope 不适合 Java 调用图,cs.android.com 也不包含厂商私有修改或当前产品的本地状态。搜索结果始终需要回到固定源码、调用上下文和对应测试。
