From 565067aa124636e043bd91b21b22a2709ea3882d Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 29 Jun 2026 23:23:39 +0200 Subject: [PATCH 1/3] feat: object scan --- README.md | 6 +-- api/build.gradle.kts | 2 +- .../github/libxposed/api/XposedInterface.java | 49 ++++++++++++++++++- .../libxposed/api/XposedInterfaceWrapper.java | 15 ++++++ .../io/github/libxposed/api/package-info.java | 9 ++++ 5 files changed, 76 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 081f36b..b3009fc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # libxposed API -[![API](https://img.shields.io/badge/API-102-brightgreen)](https://github.com/libxposed/api) +[![API](https://img.shields.io/badge/API-103-brightgreen)](https://github.com/libxposed/api) [![Maven Central](https://img.shields.io/maven-central/v/io.github.libxposed/api?color=blue)](https://central.sonatype.com/artifact/io.github.libxposed/api) [![Android Min SDK](https://img.shields.io/badge/minSdk-26-orange)](https://developer.android.com/about/versions/oreo) [![License](https://img.shields.io/github/license/libxposed/api)](LICENSE) @@ -13,7 +13,7 @@ Modern Xposed Module API — a type-safe, redesigned replacement for the legacy ```kotlin dependencies { - compileOnly("io.github.libxposed:api:102.0.0") + compileOnly("io.github.libxposed:api:103.0.0") } ``` @@ -33,7 +33,7 @@ These rules keep module entry classes from being removed and rewrite `META-INF/x ```kotlin dependencies { - implementation("io.github.libxposed:api:102.0.0") + implementation("io.github.libxposed:api:103.0.0") } ``` diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 8f9ea18..79b4893 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -32,7 +32,7 @@ android { } } -val libVersion = "102.0.0" +val libVersion = "103.0.0" val publishSnapshot = providers.gradleProperty("publishSnapshot").orNull == "true" val dependencySnapshot = providers.gradleProperty("dependencySnapshot").orNull == "true" fun String.real(snapshot: Boolean) = if (snapshot) "$this-SNAPSHOT" else this diff --git a/api/src/main/java/io/github/libxposed/api/XposedInterface.java b/api/src/main/java/io/github/libxposed/api/XposedInterface.java index c36b775..ffe52dd 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -51,11 +51,20 @@ public interface XposedInterface { */ int API_102 = 102; + /** + * API version 103. + *

New features

+ * + */ + int API_103 = 103; + /** * The API version of this library. This is a static value for the framework. * Modules should use {@link #getApiVersion()} to check the API version at runtime. */ - int LIB_API = API_102; + int LIB_API = API_103; /** * The framework has the capability to hook system_server and other system processes. @@ -507,6 +516,44 @@ default int getApiVersion() { @NonNull CtorInvoker getInvoker(@NonNull Constructor constructor); + /** + * Scans the Java heap for live objects matching the supplied class. + * + *

This can be used to obtain already-created target objects directly instead of installing nested hooks + * only to capture them from later calls.

+ * + *

Returned object references are strong references. Keeping the returned results reachable also keeps + * returned objects reachable.

+ * + *

The framework does not run a garbage collection before scanning. Non-reachable but not-yet-collected objects + * will be returned as well. If you need to exclude objects that are only waiting to be collected, you should trigger + * garbage collection before calling this method.

+ * + *

The overload of scanning one class is the same as scanning multiple classes with the batched API. + * If you need to scan multiple classes, it is recommended to use the batched API to reduce the number + * of heap walks and improve performance.

+ * + * @param clazz The class to match. Primitive classes and {@code void.class} are not valid. + * @param assignable Whether to match subclasses of the class. + * @param The expected type of matching objects + * @return A list of matching objects. The list is immutable and contains strong references to the objects. + */ + @SinceApi(API_103) + @NonNull + List findInstances(@NonNull Class clazz, boolean assignable); + + /** + * Scans the Java heap for live objects matching the supplied classes. + * + * @param classes The classes to match. Primitive classes and {@code void.class} are not valid. + * @param assignable Whether to match subclasses of the classes. + * @return An array of lists of matching objects. The array is in the same order as the input classes. + * @see #findInstances(Class, boolean) + */ + @SinceApi(API_103) + @NonNull + List[] findInstances(@NonNull Class[] classes, boolean assignable); + /** * Writes a message to the Xposed log. * diff --git a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java index fdfc9ae..9b70e9b 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -11,6 +11,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; +import java.util.List; import io.github.libxposed.annotation.InternalApi; import io.github.libxposed.annotation.SinceApi; @@ -150,6 +151,20 @@ public final CtorInvoker getInvoker(@NonNull Constructor constructor) return mBase.getInvoker(constructor); } + @NonNull + @Override + public List findInstances(@NonNull Class clazz, boolean assignable) { + ensureAttached(); + return mBase.findInstances(clazz, assignable); + } + + @NonNull + @Override + public List[] findInstances(@NonNull Class[] classes, boolean assignable) { + ensureAttached(); + return mBase.findInstances(classes, assignable); + } + @Override public final void log(int priority, @Nullable String tag, @NonNull String msg) { ensureAttached(); diff --git a/api/src/main/java/io/github/libxposed/api/package-info.java b/api/src/main/java/io/github/libxposed/api/package-info.java index 74a07ca..4bd4036 100644 --- a/api/src/main/java/io/github/libxposed/api/package-info.java +++ b/api/src/main/java/io/github/libxposed/api/package-info.java @@ -97,6 +97,15 @@ * getInvoker(Constructor)}. The invoker type controls what part of the hook chain is executed * (see {@link io.github.libxposed.api.XposedInterface.Invoker.Type Invoker.Type}).

* + *

Object Scanning (API 103+)

+ * + *

Find already-created objects by class with + * {@link io.github.libxposed.api.XposedInterface#findInstances(java.lang.Class, boolean)} or + * {@link io.github.libxposed.api.XposedInterface#findInstances(java.lang.Class[], boolean)}. + * This is useful when a module needs a target object that may already exist before its hooks are + * installed, avoiding extra nested hooks whose only purpose is to capture that object from later + * calls.

+ * *

Module Lifecycle Callbacks

* *

Override the following callbacks in {@link io.github.libxposed.api.XposedModule}:

From 9673fcd6fc5cb73ace8b4f4e019bdeace1a669f9 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 29 Jun 2026 23:37:09 +0200 Subject: [PATCH 2/3] docs: add throws for findInstances --- api/src/main/java/io/github/libxposed/api/XposedInterface.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/src/main/java/io/github/libxposed/api/XposedInterface.java b/api/src/main/java/io/github/libxposed/api/XposedInterface.java index ffe52dd..7f440e8 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -537,6 +537,7 @@ default int getApiVersion() { * @param assignable Whether to match subclasses of the class. * @param The expected type of matching objects * @return A list of matching objects. The list is immutable and contains strong references to the objects. + * @throws IllegalArgumentException if the class is primitive or void */ @SinceApi(API_103) @NonNull @@ -548,6 +549,7 @@ default int getApiVersion() { * @param classes The classes to match. Primitive classes and {@code void.class} are not valid. * @param assignable Whether to match subclasses of the classes. * @return An array of lists of matching objects. The array is in the same order as the input classes. + * @throws IllegalArgumentException if any class is primitive, void or null * @see #findInstances(Class, boolean) */ @SinceApi(API_103) From 80d977267d1f31d9554de1d9620ddd6ac638883b Mon Sep 17 00:00:00 2001 From: Nullptr Date: Wed, 8 Jul 2026 21:19:32 +0200 Subject: [PATCH 3/3] refactor: use array for findInstances --- .../java/io/github/libxposed/api/XposedInterface.java | 9 +++++---- .../io/github/libxposed/api/XposedInterfaceWrapper.java | 5 ++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/io/github/libxposed/api/XposedInterface.java b/api/src/main/java/io/github/libxposed/api/XposedInterface.java index 7f440e8..4a15c76 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -536,25 +536,26 @@ default int getApiVersion() { * @param clazz The class to match. Primitive classes and {@code void.class} are not valid. * @param assignable Whether to match subclasses of the class. * @param The expected type of matching objects - * @return A list of matching objects. The list is immutable and contains strong references to the objects. + * @return An array of matching objects. The array is empty if no matching objects are found. * @throws IllegalArgumentException if the class is primitive or void */ @SinceApi(API_103) @NonNull - List findInstances(@NonNull Class clazz, boolean assignable); + T[] findInstances(@NonNull Class clazz, boolean assignable); /** * Scans the Java heap for live objects matching the supplied classes. * * @param classes The classes to match. Primitive classes and {@code void.class} are not valid. * @param assignable Whether to match subclasses of the classes. - * @return An array of lists of matching objects. The array is in the same order as the input classes. + * @return An array of arrays of matching objects. Each inner array corresponds to the matching objects for the class + * at the same index in the input array. The inner arrays are empty if no matching objects are found for that class. * @throws IllegalArgumentException if any class is primitive, void or null * @see #findInstances(Class, boolean) */ @SinceApi(API_103) @NonNull - List[] findInstances(@NonNull Class[] classes, boolean assignable); + Object[][] findInstances(@NonNull Class[] classes, boolean assignable); /** * Writes a message to the Xposed log. diff --git a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java index 9b70e9b..2d0085e 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -11,7 +11,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; -import java.util.List; import io.github.libxposed.annotation.InternalApi; import io.github.libxposed.annotation.SinceApi; @@ -153,14 +152,14 @@ public final CtorInvoker getInvoker(@NonNull Constructor constructor) @NonNull @Override - public List findInstances(@NonNull Class clazz, boolean assignable) { + public T[] findInstances(@NonNull Class clazz, boolean assignable) { ensureAttached(); return mBase.findInstances(clazz, assignable); } @NonNull @Override - public List[] findInstances(@NonNull Class[] classes, boolean assignable) { + public Object[][] findInstances(@NonNull Class[] classes, boolean assignable) { ensureAttached(); return mBase.findInstances(classes, assignable); }