定位单个方法时使用完整 descriptor,包含参数类型和返回类型。仅按类名/方法名过滤会包含所有重载。查询 Runnable.run()V 的调用者:
SELECT DISTINCT file_id, method_idx, method
FROM method_calls
WHERE callee = 'Ljava/lang/Runnable;->run()V';
这会找到指令中引用该接口方法的调用,即使接口定义位于输入 APK 之外也可以。指令引用 Lcom/example/Task;->run()V 时,对应的是另一个目标。method_calls 每条调用指令占一行:count(*) 统计调用次数,上面的 DISTINCT 投影则返回按 (file_id, method_idx) 标识的调用者方法。
如果已经确认 Task.run()V 实现了该接口方法,可以一起查询选定目标。将 Task 替换为要检查的实现类:
SELECT DISTINCT file_id, method_idx, method
FROM method_calls
WHERE callee IN (
'Ljava/lang/Runnable;->run()V',
'Lcom/example/Task;->run()V'
);
发现候选方法时,将直接接口声明与 virtual 方法定义关联。join 保留 file_id,避免混用不同 DEX 的类索引:
SELECT DISTINCT m.descriptor
FROM methods m
JOIN class_interfaces i
ON m.file_id = i.file_id AND m.class_idx = i.class_idx
WHERE i.interface_descriptor = 'Ljava/lang/Runnable;'
AND m.name = 'run'
AND m.param_count = 0
AND m.return_descriptor = 'V'
AND m.method_kind = 'virtual';
调用方可以从结果中选取候选方法,并在同一 dataset 上复用得到的 descriptor 集合。统计指向任一候选方法或接口方法本身的调用记录数:
WITH targets AS (
SELECT 'Ljava/lang/Runnable;->run()V' AS descriptor
UNION
SELECT m.descriptor
FROM methods m
JOIN class_interfaces i
ON m.file_id = i.file_id AND m.class_idx = i.class_idx
WHERE i.interface_descriptor = 'Ljava/lang/Runnable;'
AND m.name = 'run'
AND m.param_count = 0
AND m.return_descriptor = 'V'
AND m.method_kind = 'virtual'
)
SELECT count(*) AS call_count
FROM method_calls
WHERE callee IN (SELECT descriptor FROM targets);
覆盖范围与查询成本:
- 候选查询找到的是在接口列表中直接声明
Runnable的类型自己定义的run()V方法。method_kind = 'virtual'排除了 static/private 方法和仅引用行;它不要求存在具体方法体,抽象声明也可以是调用目标。 - 间接接口实现、子接口以及从父类继承的方法,需要沿
classes.superclass_descriptor和class_interfaces进一步遍历,再解析对应方法。调用方可以迭代查询这些关系,对已访问的类型 descriptor 去重,按签名和访问规则构造目标集合。有参数的方法要匹配参数类型及顺序,不能只比较param_count。 - 组合查询统计选定方法的引用数,不表示某次接口调用一定执行哪个实现。使用选定目标查询调用者时,如果需要保留不同调用目标,可同时投影
callee,得到去重后的调用者/目标对。 - 按需展开目标。只查直接调用者时优先使用精确 descriptor,目标已知时使用选定的
IN列表。全候选 CTE 在大型 APK 上可能生成很大的目标集合,查询成本显著增加;它不是持久缓存。将计数改为返回完整的DISTINCT调用者列表,还可能触发中间结果内存预算。大范围查找调用者时,可按实测确定的批量大小查询选定目标,再在调用方按(file_id, method_idx)合并去重。重复查询可在同一 dataset 内复用已发现的目标,高频使用前先测量组合查询成本。 - 跨 DEX 匹配目标仍使用完整 descriptor。定义处的
method_idx只属于该 DEX,其他 DEX 中的引用有自己的索引。这些示例使用现有 schema,仅在查询要求时展开目标。
Copyright (c) 2026 LSPosed Developers。本文档采用 MIT 许可证。