Guía completa para migrar módulos Xposed a la API LibXposed 102
Este documento resume el proceso estándar, el paradigma de código, las especificaciones de metadatos y las estrategias de optimización de rendimiento para migrar módulos Xposed tradicionales (basados en la API XposedBridge 54-93) a la API LibXposed 102 moderna (compatible con LSPosed 1.9.3+, Vector y otros frameworks modernos).
Para futuras instrucciones de migración a otros módulos, se puede hacer referencia directa a esta guía.
I. Comparación de cambios principales
| Característica / Dimensión | Xposed tradicional (API 54-93) | LibXposed moderno (API 102) |
|---|---|---|
| Coordenada de dependencia | de.robv.android.xposed:api:82 (o 93) | io.github.libxposed:api:102.0.0 |
| Clase/Interfaz de entrada | IXposedHookLoadPackage, IXposedHookZygoteInit | Heredar de io.github.libxposed.api.XposedModule |
| Declaración de entrada | assets/xposed_init | META-INF/xposed/java_init.list |
| Configuración de propiedades del módulo | <meta-data> en AndroidManifest.xml | META-INF/xposed/module.prop |
| Declaración del ámbito | xposedscope resource array | META-INF/xposed/scope.list |
| Modelo de programación de Hooking | Callbacks XC_MethodHook (before / after) | Interceptores encadenados estilo OkHttp Hooker (chain.proceed()) |
| Recarga en caliente (Hot Reload) | No soportado (requiere reinicio de la app objetivo o incluso del sistema) | Soporte nativo (sobrescribir onHotReloading / onHotReloaded) |
| Inyección en Zygote | Soporte para inyección global initZygote | Prohibida la inyección en Zygote, solo se inyecta en procesos de ámbito objetivo |
| Soporte para ofuscación/R8 | Requiere mantener estrictamente nombres de métodos y clases de hooks | Soporte nativo para ofuscación R8, solo declarar reglas de adaptación |
II. Método estándar de migración en cuatro pasos
Primer paso: Migración de compilación y dependencias (build.gradle)
-
Actualizar dependencias: En el
build.gradledel módulo (por ejemplo,app/build.gradleohook/build.gradle), reemplazar las dependencias:dependencies { // Eliminar: compileOnly 'de.robv.android.xposed:api:82' // Introducir la API LibXposed 102 moderna compileOnly 'io.github.libxposed:api:102.0.0' compileOnly 'androidx.annotation:annotation:1.5.0' } -
Compatibilidad de compilación de Java:
android { compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } } -
Configurar reglas ProGuard / R8 (
proguard-rules.pro):-dontwarn io.github.libxposed.annotation.** -adaptresourcefilecontents META-INF/xposed/java_init.list -keep,allowoptimization,allowobfuscation public class * extends io.github.libxposed.api.XposedModule { public <init>(); }
Segundo paso: Configuración de archivos de metadatos modernos (META-INF/xposed/)
Crear los siguientes archivos en el directorio de recursos del módulo src/main/resources/META-INF/xposed/:
1. module.prop (Metadatos del módulo)
minApiVersion=102
targetApiVersion=102
autoHotReload=true
exceptionMode=protective
minApiVersion: Versión mínima de API soportada, establecido en102.targetApiVersion: Versión de API objetivo, establecido en102.autoHotReload: Establecido entruepara que el módulo active automáticamente una recarga en caliente al instalar una actualización.exceptionMode:protective(modo de protección predeterminado, las excepciones en los hooks no provocan el colapso del host) opassthrough.
2. java_init.list (Declaración de clase de entrada)
Declarar el nombre completo de la clase de entrada del módulo en una sola línea:
com.example.myhook.MainHook
3. scope.list (Ámbito recomendado)
Un nombre de paquete objetivo por línea, declarando las aplicaciones en las que el módulo debe inyectarse de forma predeterminada:
com.android.chrome
com.microsoft.emmx
com.example.targetapp
Tercer paso: Configuración del archivo de manifiesto (AndroidManifest.xml)
Actualizar el AndroidManifest.xml del módulo:
<application ...>
<!-- Mantener los metadatos tradicionales para compatibilidad, clave: establecer xposedminversion en 102 -->
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="Descripción de la funcionalidad del módulo" />
<meta-data
android:name="xposedminversion"
android:value="102" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope" />
</application>
Cuarto paso: Reestructuración de la lógica de Hooking (XposedModule)
1. Plantilla básica y ciclo de vida
package com.example.myhook;
import android.util.Log;
import androidx.annotation.NonNull;
import io.github.libxposed.api.XposedInterface;
import io.github.libxposed.api.XposedModule;
public class MainHook extends XposedModule {
private static final String TAG = "MyHookModule";
private volatile boolean mHooked = false;
public MainHook() {
super();
}
/**
* Se activa cuando el ClassLoader de la aplicación host está listo y está a punto de crear la Application
*/
@Override
public void onPackageReady(@NonNull PackageReadyParam param) {
if (mHooked) return;
synchronized (this) {
if (mHooked) return;
mHooked = true;
initHooks(param.getPackageName(), param.getClassLoader());
}
}
/**
* Callback de preparación para recarga en caliente (se ejecuta en el código de la versión anterior)
* Devolver true permite que el framework reemplace con la nueva versión
*/
@Override
public boolean onHotReloading(@NonNull HotReloadingParam param) {
return true;
}
/**
* Callback de finalización de recarga en caliente (se ejecuta en el código de la nueva versión)
*/
@Override
public void onHotReloaded(@NonNull HotReloadedParam param) {
// Desregistrar todos los hooks registrados por la versión anterior
param.getOldHookHandles().forEach(XposedInterface.HookHandle::unhook);
synchronized (this) {
mHooked = false;
initHooks("hot-reload", getClass().getClassLoader());
}
}
private void initHooks(String packageName, ClassLoader classLoader) {
// Ejecutar la lógica de hooking específica
}
}
2. Comparación de sintaxis de Hooking principal
| Operación objetivo | Escritura Xposed tradicional | Escritura moderna LibXposed 102 |
|---|---|---|
| Buscar y hookear un método | XposedHelpers.findAndHookMethod("nombreClase", cl, "método", tiposArgs..., callback) | Method m = Clazz.class.getDeclaredMethod("método", tiposArgs...);hook(m).intercept(chain -> { ... }); |
| Ejecutar el método original | Se ejecuta directamente por defecto; llamar param.getResult() | Object result = chain.proceed(); |
| Modificar argumentos y llamar | param.args[0] = newVal; | Object result = chain.proceed(new Object[]{ newVal, ... }); |
| Interceptar y reemplazar el valor de retorno | param.setResult(customVal); | Devolver directamente el valor personalizado (sin llamar a chain.proceed()):hook(m).intercept(chain -> customVal); |
| Obtener argumentos de entrada | param.args[i] | chain.getArg(i) o chain.getArgs() |
| Obtener la instancia this | param.thisObject | chain.getThisObject() |
| Hookear un constructor | findAndHookConstructor(...) | Constructor<?> ctor = ...;hook(ctor).intercept(chain -> ...); |
| Hookear un bloque estático | Tradicionalmente no soportado por la API estándar | hookClassInitializer(TargetClass.class).intercept(...) |
III. Escenarios de hooking de alta frecuencia y patrones de optimización de rendimiento profundo
Regla de oro de optimización para llamadas de alta frecuencia (como isEnabled, dibujado de View, despacho táctil):
-
Ruta rápida de costo cero (sonda directa): Si el método es un método de consulta de estado (como
isEnabled(),isLoaded()), primero llamar a la lectura ligera del campo nativochain.proceed(). Si el estado real en sí mismo es falso, devolver directamente falso, saltando toda la lógica posterior y las comprobaciones de pila. -
Android 14+ (API 34+) usar
StackWalkerpara recorrido perezoso: El tradicionalnew Throwable().getStackTrace()provoca un despliegue completo de la pila de hilos (Native Unwind) y cientos de asignaciones de cadenas, lo que fácilmente causa pérdida de frames por GC. Usarjava.lang.StackWalkerde Java 9 / Android 14+, escaneando por demanda capa por capa, con cero asignación de pila completa. -
Android < 34: Posicionamiento dinámico de la pila (evitar codificación rígida): Ejecutar
new Throwable().getStackTrace()una sola vez, recorrer hacia abajo para encontrar la clase del sistema donde se encuentra el método objetivo (comoAccessibilityManager), el marco de pila inmediatamente siguiente es el verdadero llamante. Nunca codificar rígidamente[3]o[4], garantizando un funcionamiento estable entre versiones de LSPosed.
Código de ejemplo de optimización de rendimiento:
// Verificar StackWalker de Android 14+
private static final boolean HAS_STACK_WALKER;
static {
boolean has;
try {
Class.forName("java.lang.StackWalker");
has = (Build.VERSION.SDK_INT >= 34);
} catch (Throwable t) {
has = false;
}
HAS_STACK_WALKER = has;
}
// Ejemplo de hookeo de método de alta frecuencia
Method isEnabled = AccessibilityManager.class.getDeclaredMethod("isEnabled");
hook(isEnabled).intercept(chain -> {
Object real = chain.proceed();
if (Boolean.FALSE.equals(real)) {
return false; // El sistema ya está apagado, retorno directo de costo cero
}
return isCallerWhitelisted() ? real : false;
});
private static boolean isCallerWhitelisted() {
if (HAS_STACK_WALKER) {
try {
return Api34StackWalker.check();
} catch (Throwable ignored) {}
}
return checkLegacy();
}
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private static final class Api34StackWalker {
private static final StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
static boolean check() {
return Boolean.TRUE.equals(WALKER.walk(frames -> {
boolean foundTarget = false;
var it = frames.iterator();
while (it.hasNext()) {
var f = it.next();
if ("android.view.accessibility.AccessibilityManager".equals(f.getClassName())) {
foundTarget = true;
} else if (foundTarget) {
return isPackageAllowed(f.getClassName());
}
}
return false;
}));
}
}
IV. Errores comunes de migración y lista de verificación de solución de problemas
- ¿Es necesario conservar
assets/xposed_init?- La API moderna 102 solo lee
META-INF/xposed/java_init.list. Si se conservaassets/xposed_init, los frameworks antiguos pueden intentar cargarlo usando reflexión tradicional y provocar errores. Se recomienda eliminarlo directamente o dejarlo vacío.
- La API moderna 102 solo lee
compileSdky plataforma Android SDK:- Se recomienda actualizar
compileSdkytargetSdka Android 33 o 34 para poder usar las interfaces modernas y las características del lenguaje Java 11/17 sin problemas.
- Se recomienda actualizar
- Compatibilidad con Gradle:
- Si el entorno de compilación usa JDK 17 / 21, asegurarse de actualizar Gradle Wrapper a
7.4+(se recomienda7.5.1o8.x) y AGP a7.3+.
- Si el entorno de compilación usa JDK 17 / 21, asegurarse de actualizar Gradle Wrapper a
- Verificar el reconocimiento del módulo:
- Usar una herramienta de descompresión para ver el directorio raíz del APK final. Debe contener:
META-INF/xposed/module.propMETA-INF/xposed/java_init.listMETA-INF/xposed/scope.list
- Usar una herramienta de descompresión para ver el directorio raíz del APK final. Debe contener: