CommunityRedacción y edicióngithub.com

Marukon/skill-upgrade-libxposed-102

Guía completa para migrar módulos Xposed a la API LibXposed 102.

¿Qué es skill-upgrade-libxposed-102?

Esta habilidad proporciona una guía completa y estandarizada para migrar módulos Xposed basados en la API XposedBridge (54-93) a la moderna API LibXposed 102, compatible con frameworks como LSPosed 1.9.3+ y Vector. Cubre todos los aspectos esenciales de la migración, incluyendo la actualización de dependencias en Gradle (reemplazando 'de.robv.android.xposed:api' por 'io.github.libxposed:api:102.0.0'), la reestructuración de la metainformación del módulo (usando archivos en META-INF/xposed/ como module.prop, java_init.list y scope.list), y la refactorización completa de la lógica de hooking para adoptar el nuevo modelo de interceptores encadenados similar a OkHttp. También aborda las diferencias clave, como la eliminación de la inyección en Zygote, la adición de soporte nativo para recarga en caliente (hot reload) y la compatibilidad con ofuscadores R8. El documento detalla patrones de optimización para hooks de alta frecuencia, incluyendo el uso de rutas rápidas de costo cero y StackWalker para Android 14+, con ejemplos de código concretos. Finalmente, incluye una lista de verificación de errores comunes en la migración, como la necesidad de eliminar el archivo legacy assets/xposed_init y verificar la correcta inclusión de los archivos de metadatos modernos en el APK final.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add Marukon/skill-upgrade-libxposed-102

Installed? Explore more Redacción y edición skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

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ónXposed tradicional (API 54-93)LibXposed moderno (API 102)
Coordenada de dependenciade.robv.android.xposed:api:82 (o 93)io.github.libxposed:api:102.0.0
Clase/Interfaz de entradaIXposedHookLoadPackage, IXposedHookZygoteInitHeredar de io.github.libxposed.api.XposedModule
Declaración de entradaassets/xposed_initMETA-INF/xposed/java_init.list
Configuración de propiedades del módulo<meta-data> en AndroidManifest.xmlMETA-INF/xposed/module.prop
Declaración del ámbitoxposedscope resource arrayMETA-INF/xposed/scope.list
Modelo de programación de HookingCallbacks 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 ZygoteSoporte para inyección global initZygoteProhibida la inyección en Zygote, solo se inyecta en procesos de ámbito objetivo
Soporte para ofuscación/R8Requiere mantener estrictamente nombres de métodos y clases de hooksSoporte 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)

  1. Actualizar dependencias: En el build.gradle del módulo (por ejemplo, app/build.gradle o hook/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'
    }
    
  2. Compatibilidad de compilación de Java:

    android {
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_11
            targetCompatibility JavaVersion.VERSION_11
        }
    }
    
  3. 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 en 102.
  • targetApiVersion: Versión de API objetivo, establecido en 102.
  • autoHotReload: Establecido en true para 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) o passthrough.

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 objetivoEscritura Xposed tradicionalEscritura moderna LibXposed 102
Buscar y hookear un métodoXposedHelpers.findAndHookMethod("nombreClase", cl, "método", tiposArgs..., callback)Method m = Clazz.class.getDeclaredMethod("método", tiposArgs...);hook(m).intercept(chain -> { ... });
Ejecutar el método originalSe ejecuta directamente por defecto; llamar param.getResult()Object result = chain.proceed();
Modificar argumentos y llamarparam.args[0] = newVal;Object result = chain.proceed(new Object[]{ newVal, ... });
Interceptar y reemplazar el valor de retornoparam.setResult(customVal);Devolver directamente el valor personalizado (sin llamar a chain.proceed()):hook(m).intercept(chain -> customVal);
Obtener argumentos de entradaparam.args[i]chain.getArg(i) o chain.getArgs()
Obtener la instancia thisparam.thisObjectchain.getThisObject()
Hookear un constructorfindAndHookConstructor(...)Constructor<?> ctor = ...;hook(ctor).intercept(chain -> ...);
Hookear un bloque estáticoTradicionalmente no soportado por la API estándarhookClassInitializer(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):

  1. 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 nativo chain.proceed(). Si el estado real en sí mismo es falso, devolver directamente falso, saltando toda la lógica posterior y las comprobaciones de pila.

  2. Android 14+ (API 34+) usar StackWalker para recorrido perezoso: El tradicional new 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. Usar java.lang.StackWalker de Java 9 / Android 14+, escaneando por demanda capa por capa, con cero asignación de pila completa.

  3. 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 (como AccessibilityManager), 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

  1. ¿Es necesario conservar assets/xposed_init?
    • La API moderna 102 solo lee META-INF/xposed/java_init.list. Si se conserva assets/xposed_init, los frameworks antiguos pueden intentar cargarlo usando reflexión tradicional y provocar errores. Se recomienda eliminarlo directamente o dejarlo vacío.
  2. compileSdk y plataforma Android SDK:
    • Se recomienda actualizar compileSdk y targetSdk a Android 33 o 34 para poder usar las interfaces modernas y las características del lenguaje Java 11/17 sin problemas.
  3. Compatibilidad con Gradle:
    • Si el entorno de compilación usa JDK 17 / 21, asegurarse de actualizar Gradle Wrapper a 7.4+ (se recomienda 7.5.1 o 8.x) y AGP a 7.3+.
  4. 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.prop
      • META-INF/xposed/java_init.list
      • META-INF/xposed/scope.list

Skills relacionados