Guide complet de mise à niveau des modules Xposed vers LibXposed API 102
Ce document résume le processus standard, les paradigmes de code, les spécifications des métadonnées et les stratégies d'optimisation des performances pour mettre à niveau les modules Xposed traditionnels (basés sur XposedBridge API 54-93) vers la LibXposed API 102 moderne (compatible avec LSPosed 1.9.3+, Vector et autres frameworks modernes).
Pour toute instruction ultérieure de mise à niveau d'autres modules, référez-vous directement à cette spécification.
I. Comparaison des changements fondamentaux
| Caractéristique / Dimension | Xposed traditionnel (API 54-93) | LibXposed moderne (API 102) |
|---|---|---|
| Coordonnées de dépendance | de.robv.android.xposed:api:82 (ou 93) | io.github.libxposed:api:102.0.0 |
| Classe/Interface d'entrée | IXposedHookLoadPackage, IXposedHookZygoteInit | Hériter de io.github.libxposed.api.XposedModule |
| Déclaration d'entrée | assets/xposed_init | META-INF/xposed/java_init.list |
| Configuration des propriétés du module | <meta-data> dans AndroidManifest.xml | META-INF/xposed/module.prop |
| Déclaration de portée | Tableau de ressources xposedscope | META-INF/xposed/scope.list |
| Modèle de programmation Hook | Callbacks XC_MethodHook (before / after) | Intercepteur chaîné de type OkHttp avec Hooker (chain.proceed()) |
| Rechargement à chaud (Hot Reload) | Non supporté (nécessite de redémarrer l'application cible, voire le système) | Support natif (réécrire onHotReloading / onHotReloaded) |
| Injection Zygote | Supporte l'injection globale initZygote | Injection Zygote interdite, injection uniquement dans les processus cibles de la portée |
| Support d'obfuscation/R8 | Nécessité de préserver strictement les noms de méthodes et de classes Hook | Support natif de l'obfuscation R8, déclaration de règles d'adaptation suffisante |
II. Méthode standard de migration en quatre étapes
Première étape : Migration des dépendances et de la construction (build.gradle)
-
Mettre à jour les dépendances : Dans le fichier
build.gradledu module (par exempleapp/build.gradleouhook/build.gradle), remplacez les dépendances :dependencies { // Supprimer : compileOnly 'de.robv.android.xposed:api:82' // Introduire la nouvelle API LibXposed 102 compileOnly 'io.github.libxposed:api:102.0.0' compileOnly 'androidx.annotation:annotation:1.5.0' } -
Compatibilité de compilation Java :
android { compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } } -
Configurer les règles 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>(); }
Deuxième étape : Configuration des fichiers de métadonnées modernes (META-INF/xposed/)
Créez les fichiers suivants dans le répertoire src/main/resources/META-INF/xposed/ du module source :
1. module.prop (métadonnées du module)
minApiVersion=102
targetApiVersion=102
autoHotReload=true
exceptionMode=protective
minApiVersion: Version minimale de l'API prise en charge, définissez102.targetApiVersion: Version cible de l'API, définissez102.autoHotReload: Définisseztruepour déclencher automatiquement un rechargement à chaud lors de l'installation d'une mise à jour du module.exceptionMode:protective(mode de protection par défaut, une exception dans un Hook n'affecte pas le crash de l'hôte) oupassthrough.
2. java_init.list (déclaration des classes d'entrée)
Déclarez le nom de classe complet du point d'entrée du module sur une seule ligne :
com.example.myhook.MainHook
3. scope.list (portée recommandée)
Un nom de package cible par ligne, déclare les applications dans lesquelles le module doit être injecté par défaut :
com.android.chrome
com.microsoft.emmx
com.example.targetapp
Troisième étape : Configuration du fichier Manifest (AndroidManifest.xml)
Mettez à jour le AndroidManifest.xml du module :
<application ...>
<!-- Les métadonnées traditionnelles restent compatibles, l'essentiel est de définir xposedminversion sur 102 -->
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="Description des fonctionnalités du module" />
<meta-data
android:name="xposedminversion"
android:value="102" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope" />
</application>
Quatrième étape : Refonte de la logique des Hooks (XposedModule)
1. Modèle de base et cycle de vie
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();
}
/**
* Déclenché lorsque le ClassLoader de l'application hôte est prêt et que l'Application est sur le point d'être créée
*/
@Override
public void onPackageReady(@NonNull PackageReadyParam param) {
if (mHooked) return;
synchronized (this) {
if (mHooked) return;
mHooked = true;
initHooks(param.getPackageName(), param.getClassLoader());
}
}
/**
* Callback de préparation au rechargement à chaud (s'exécute dans l'ancienne version du code)
* Retourne true pour permettre au framework de remplacer par la nouvelle version
*/
@Override
public boolean onHotReloading(@NonNull HotReloadingParam param) {
return true;
}
/**
* Callback de fin de rechargement à chaud (s'exécute dans la nouvelle version du code)
*/
@Override
public void onHotReloaded(@NonNull HotReloadedParam param) {
// Désenregistrer tous les Hooks enregistrés par l'ancienne version
param.getOldHookHandles().forEach(XposedInterface.HookHandle::unhook);
synchronized (this) {
mHooked = false;
initHooks("hot-reload", getClass().getClassLoader());
}
}
private void initHooks(String packageName, ClassLoader classLoader) {
// Exécution de la logique de Hook spécifique
}
}
2. Correspondance de la syntaxe de Hook principale
| Opération cible | Syntaxe Xposed traditionnelle | Syntaxe moderne LibXposed 102 |
|---|---|---|
| Trouver et Hook une méthode | XposedHelpers.findAndHookMethod("NomClasse", cl, "méthode", typesArg..., callback) | Method m = Clazz.class.getDeclaredMethod("méthode", typesArg...);hook(m).intercept(chain -> { ... }); |
| Exécuter la méthode originale | Exécution par défaut ; appeler param.getResult() | Object result = chain.proceed(); |
| Modifier un argument et appeler | param.args[0] = newVal; | Object result = chain.proceed(new Object[]{ newVal, ... }); |
| Intercepter et remplacer la valeur de retour | param.setResult(customVal); | Retourner directement une valeur personnalisée (sans appeler chain.proceed()) :hook(m).intercept(chain -> customVal); |
| Obtenir les paramètres d'entrée | param.args[i] | chain.getArg(i) ou chain.getArgs() |
| Obtenir l'instance this | param.thisObject | chain.getThisObject() |
| Hook un constructeur | findAndHookConstructor(...) | Constructor<?> ctor = ...;hook(ctor).intercept(chain -> ...); |
| Hook un bloc statique | Non supporté par l'API standard traditionnelle | hookClassInitializer(TargetClass.class).intercept(...) |
III. Scénarios de Hook à haute fréquence et modèles d'optimisation des performances approfondis
Règle d'or d'optimisation pour les appels à haute fréquence (comme isEnabled, rendu View, distribution tactile) :
-
Chemin rapide à zéro surcoût (sondage direct) : Si la méthode est une méthode de requête d'état (comme
isEnabled(),isLoaded()), appelez d'abord la lecture de champ natif légèrechain.proceed(). Si l'état réel est faux, retournez directement faux, sautez toute la logique ultérieure et la vérification de la pile. -
Android 14+ (API 34+) : Utiliser
StackWalkerpour une traversée paresseuse :new Throwable().getStackTrace()traditionnel provoque un déroulement complet de la pile de threads (Native Unwind) et des centaines d'allocations de chaînes, entraînant facilement des chutes de trames GC. Utilisezjava.lang.StackWalkerde Java 9 / Android 14+ pour une analyse couche par couche à la demande, zéro allocation de pile complète. -
Android < 34 : Localisation dynamique de la pile (pas de codage en dur) : Exécutez
new Throwable().getStackTrace()une seule fois, parcourez vers le bas pour trouver la classe système où se trouve la méthode cible (commeAccessibilityManager), le cadre de pile immédiatement suivant est le véritable appelant. Ne codez jamais en dur[3]ou[4], garantissant un fonctionnement stable entre les versions LSPosed.
Code d'exemple d'optimisation des performances :
// Vérifier StackWalker 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;
}
// Exemple de Hook de méthode à haute fréquence
Method isEnabled = AccessibilityManager.class.getDeclaredMethod("isEnabled");
hook(isEnabled).intercept(chain -> {
Object real = chain.proceed();
if (Boolean.FALSE.equals(real)) {
return false; // Le système est déjà désactivé, retour direct à zéro surcoût
}
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. Pièges courants de mise à niveau et liste de vérification
assets/xposed_initdoit-il être conservé ?- L'API 102 moderne lit uniquement
META-INF/xposed/java_init.list. Siassets/xposed_initest conservé, les anciens frameworks peuvent tenter de l'utiliser avec une réflexion traditionnelle et générer une erreur. Il est recommandé de le supprimer directement ou de le laisser vide.
- L'API 102 moderne lit uniquement
compileSdket plateforme Android SDK :- Il est suggéré de mettre à niveau
compileSdkettargetSdkvers Android 33 ou 34 pour utiliser facilement les interfaces modernes et les fonctionnalités du langage Java 11/17.
- Il est suggéré de mettre à niveau
- Compatibilité Gradle :
- Si l'environnement de construction utilise JDK 17/21, assurez-vous que le wrapper Gradle est mis à niveau vers
7.4+(7.5.1ou8.xrecommandé), et que AGP est mis à niveau vers7.3+.
- Si l'environnement de construction utilise JDK 17/21, assurez-vous que le wrapper Gradle est mis à niveau vers
- Vérification de la reconnaissance du module :
- Utilisez un outil de décompression pour vérifier le répertoire racine de l'APK final. Il doit contenir :
META-INF/xposed/module.propMETA-INF/xposed/java_init.listMETA-INF/xposed/scope.list
- Utilisez un outil de décompression pour vérifier le répertoire racine de l'APK final. Il doit contenir :