Use the full descriptor to select one method, including its parameter and return types. Filtering only by class/name includes all overloads. For callers of Runnable.run()V:
SELECT DISTINCT file_id, method_idx, method
FROM method_calls
WHERE callee = 'Ljava/lang/Runnable;->run()V';
This finds instructions that reference the interface method, even when the interface is defined outside the input APK. An instruction referencing Lcom/example/Task;->run()V is a different target. method_calls has one row per call instruction: count(*) counts calls, while the DISTINCT projection above returns caller methods identified by (file_id, method_idx).
If you have already established that Task.run()V implements this interface method, query the selected targets together. Replace Task with the implementation you want to inspect:
SELECT DISTINCT file_id, method_idx, method
FROM method_calls
WHERE callee IN (
'Ljava/lang/Runnable;->run()V',
'Lcom/example/Task;->run()V'
);
To discover candidates, join the direct interface declarations to virtual method definitions. Keeping file_id in this join prevents mixing DEX-local class indices:
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';
The caller can select candidates from this result and reuse the resulting descriptor set for the same dataset. To count call instructions referencing any candidate or the interface method itself:
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);
Coverage and cost:
- The discovery query finds
run()Vmethods declared by types that directly listRunnablein their interfaces.method_kind = 'virtual'excludes static/private methods and reference-only rows. It does not require a concrete method body; abstract declarations can also be call targets. - Indirect interface implementations, subinterfaces, and methods inherited from a superclass require additional traversal through
classes.superclass_descriptorandclass_interfaces, followed by method resolution. A client can query these relations iteratively, deduplicate visited type descriptors, and apply signature/access rules before constructing the target set. For methods with parameters, match their types and order, not justparam_count. - The combined query counts references to the selected methods. It does not prove which implementation a particular interface call executes. In the selected-target caller query, also project
calleewhen you need distinct caller/target pairs. - Expand targets only when needed. Prefer the exact descriptor query for direct callers, or a selected
INlist when the targets are already known. The all-candidate CTE can produce a large target set on a large APK and cost substantially more; it is not a persistent cache. Returning a fullDISTINCTcaller list instead of the count can also exceed the intermediate-result memory budget. For a large caller search, query selected targets in measured batches and merge/deduplicate(file_id, method_idx)in the client. Reuse discovered targets within the same dataset and measure the composed query before putting it on a frequent path. - Keep cross-DEX target matching descriptor-based. A definition's
method_idxbelongs to its own DEX; references in other DEX files have their own indices. These recipes use the existing schema and perform expansion only when requested by the query.
Copyright (c) 2026 LSPosed Developers. Documentation licensed under MIT.