Skip to content

搜索调用链

以 IApplicationThread.scheduleTransaction 为案例,讲解如何从文本命中定位声明、发送者、Binder入口、主线程消费者、失败分支和对应测试。

基于android-17.0.0_r1
Android源码阅读搜索rgctagscscope

搜索调用链 ​

本文面向已经能在 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 工作区的单一提交。

bash
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.aidl

Android 17 的真实命中包括:IApplicationThread.aidl:170 协议声明,ActivityThread.java:2394 的 ApplicationThread 方法,ClientTransaction.java:245 的调用,以及 ClientLifecycleManager.java 和 ActivityTaskSupervisor.java 的服务端发送路径。这一步只得到候选位置,动态方向还要通过接收者类型、参数和下游消费者确认。

3.2 范围过滤 ​

rg 适合在已 checkout 源码中快速迭代。先按路径、语言和词边界过滤,避免把 scheduleTransactionItems、注释和测试辅助方法混在一起:

bash
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 上下文窗口 ​

bash
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 很大时,只为当前专题目录建索引:

bash
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

java
interface IApplicationThread {
    // ... other process callbacks
    void scheduleTransaction(in ClientTransaction transaction);
}

AIDL 声明确定了协议形状;下一段再确认哪个手写 Java 类继承生成的 Stub 并接收回调。

源码文件:frameworks/base/core/java/android/app/ActivityThread.java

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 文本反查 ​

bash
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++ 专题,可以按明确文件列表建立数据库:

bash
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。

text
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

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

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

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

java
public RemoteException schedule() {
    try {
        mClient.scheduleTransaction(this);
        return null;
    } catch (RemoteException e) {
        return e;
    }
}

远端调用返回应用进程后,下一段代码才把同一个 transaction 交给主线程消息队列。

源码文件:frameworks/base/core/java/android/app/ClientTransactionHandler.java

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

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

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

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

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,是否符号通过字符串或反射间接使用。

bash
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 | head

out/soong 的生成 Java 可帮助解释编译结果,但不能替代固定 tag 源码事实。

7.2 角色误判 ​

命中角色进程输入 owner输出消费者
IApplicationThread.aidl协议声明跨进程AIDL 编译器Stub/Proxy
ClientLifecycleManager发送调度system_serverClientTransactionIApplicationThread
ApplicationThreadBinder 入口应用远端事务ActivityThread.scheduleTransaction
ClientTransactionHandler消息入队应用transaction 参数ActivityThread.H
TransactionExecutor事务消费应用主线程item 列表lifecycle callback

角色表迫使读者回答状态在哪里生效、谁读取它,也能暴露搜索结果中缺失的跨进程边界。

7.3 工具故障 ​

8. 搜索复现 ​

8.1 五步搜索 ​

bash
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 可执行核对 ​

bash
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

如果设备可用,可将源码路径与运行现象对照,但不要把一次设备结果外推为所有产品:

bash
adb shell am start -W -n com.android.settings/.Settings
adb logcat -d -s ActivityTaskManager ActivityThread | tail -80

9. 工具边界 ​

本文展示的是一个完整搜索过程:在 Android 17 固定源码中,从 IApplicationThread.scheduleTransaction 文本命中出发,经过定义分类、发送者与消费者核对、线程边界确认和测试断言阅读,建立 Java 调用关系。

rg 不理解 Java 语义,ctags 不保证生成完整 AIDL 交叉引用,cscope 不适合 Java 调用图,cs.android.com 也不包含厂商私有修改或当前产品的本地状态。搜索结果始终需要回到固定源码、调用上下文和对应测试。