Xposed 模組升級至 LibXposed API 102 完全指南
本文檔總結了將傳統 Xposed 模組(基於 XposedBridge API 54-93)升級至現代 LibXposed API 102(適用於 LSPosed 1.9.3+、Vector 等現代框架)的標準流程、程式碼範式、元資料規範與效能最佳化策略。
後續對任何其他模組執行升級指示時,可直接參照本規範執行。
一、核心變更對比
| 特性 / 維度 | 傳統 Xposed (API 54-93) | 現代 LibXposed (API 102) |
|---|---|---|
| 依賴座標 | de.robv.android.xposed:api:82 (或 93) | io.github.libxposed:api:102.0.0 |
| 入口基底類別/介面 | IXposedHookLoadPackage, IXposedHookZygoteInit | 繼承 io.github.libxposed.api.XposedModule |
| 入口宣告 | assets/xposed_init | META-INF/xposed/java_init.list |
| 模組屬性配置 | AndroidManifest.xml 中的 <meta-data> | META-INF/xposed/module.prop |
| 作用域宣告 | xposedscope resource array | META-INF/xposed/scope.list |
| Hook 程式設計模型 | XC_MethodHook (before / after) 回呼 | 類 OkHttp 的鏈式攔截器 Hooker (chain.proceed()) |
| 熱重載 (Hot Reload) | 不支援(需重啟目標應用甚至系統) | 原生支援(覆寫 onHotReloading / onHotReloaded) |
| Zygote 注入 | 支援 initZygote 全域注入 | 禁止 Zygote 注入,僅注入到作用域目標程序 |
| 混淆/R8 支援 | 需嚴格保留 Hook 方法名與類別名 | 原生支援 R8 混淆,宣告適配規則即可 |
二、標準遷移四步法
第一步:建構與依賴遷移 (build.gradle)
-
更新依賴項: 在模組的
build.gradle(如app/build.gradle或hook/build.gradle)中替換依賴:dependencies { // 移除: compileOnly 'de.robv.android.xposed:api:82' // 引入現代 LibXposed 102 API compileOnly 'io.github.libxposed:api:102.0.0' compileOnly 'androidx.annotation:annotation:1.5.0' } -
Java 編譯相容性:
android { compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } } -
配置 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>(); }
第二步:現代元資料檔案配置 (META-INF/xposed/)
在模組源碼目錄 src/main/resources/META-INF/xposed/ 下建立以下檔案:
1. module.prop(模組元資料)
minApiVersion=102
targetApiVersion=102
autoHotReload=true
exceptionMode=protective
minApiVersion: 最低支援的 API 版本,設為102。targetApiVersion: 目標 API 版本,設為102。autoHotReload: 設為true表示模組更新安裝時自動觸發熱重載。exceptionMode:protective(預設保護模式,Hook 拋異常不影響宿主崩潰)或passthrough。
2. java_init.list(入口類別宣告)
單行宣告模組入口全限定類別名:
com.example.myhook.MainHook
3. scope.list(推薦作用域)
每行一個目標包名,宣告模組預設需要注入的應用:
com.android.chrome
com.microsoft.emmx
com.example.targetapp
第三步:清單檔案配置 (AndroidManifest.xml)
更新模組的 AndroidManifest.xml:
<application ...>
<!-- 傳統元資料保持相容,關鍵將 xposedminversion 設為 102 -->
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="模組功能描述" />
<meta-data
android:name="xposedminversion"
android:value="102" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope" />
</application>
第四步:Hook 邏輯重構 (XposedModule)
1. 基礎模板與生命週期
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();
}
/**
* 宿主應用 ClassLoader 就緒且即將建立 Application 時觸發
*/
@Override
public void onPackageReady(@NonNull PackageReadyParam param) {
if (mHooked) return;
synchronized (this) {
if (mHooked) return;
mHooked = true;
initHooks(param.getPackageName(), param.getClassLoader());
}
}
/**
* 熱重載準備回呼(執行在舊版本程式碼中)
* 傳回 true 允許框架替換為新版本
*/
@Override
public boolean onHotReloading(@NonNull HotReloadingParam param) {
return true;
}
/**
* 熱重載完成回呼(執行在新版本程式碼中)
*/
@Override
public void onHotReloaded(@NonNull HotReloadedParam param) {
// 解除舊版本註冊的所有 Hook
param.getOldHookHandles().forEach(XposedInterface.HookHandle::unhook);
synchronized (this) {
mHooked = false;
initHooks("hot-reload", getClass().getClassLoader());
}
}
private void initHooks(String packageName, ClassLoader classLoader) {
// 執行具體 Hook 邏輯
}
}
2. 核心 Hook 語法對照
| 目標操作 | 傳統 Xposed 寫法 | LibXposed 102 現代寫法 |
|---|---|---|
| 查詢並 Hook 方法 | XposedHelpers.findAndHookMethod("類別名", cl, "方法", argTypes..., callback) | Method m = Clazz.class.getDeclaredMethod("方法", argTypes...);hook(m).intercept(chain -> { ... }); |
| 執行原方法 | 預設直接執行;呼叫 param.getResult() | Object result = chain.proceed(); |
| 修改參數並呼叫 | param.args[0] = newVal; | Object result = chain.proceed(new Object[]{ newVal, ... }); |
| 攔截並替換回傳值 | param.setResult(customVal); | 直接回傳自訂值(不呼叫 chain.proceed()):hook(m).intercept(chain -> customVal); |
| 取得入參 | param.args[i] | chain.getArg(i) 或 chain.getArgs() |
| 取得 this 實例 | param.thisObject | chain.getThisObject() |
| Hook 建構函數 | findAndHookConstructor(...) | Constructor<?> ctor = ...;hook(ctor).intercept(chain -> ...); |
| Hook 靜態程式碼區塊 | 傳統不支援標準 API | hookClassInitializer(TargetClass.class).intercept(...) |
三、高頻 Hook 場景與效能深度最佳化模式
針對高頻呼叫(如 isEnabled、View 繪製、觸摸派發)的黃金最佳化法則:
-
零開銷快速路徑(嗅探直通): 若方法是狀態查詢方法(如
isEnabled()、isLoaded()),先呼叫輕量級的原生欄位讀取chain.proceed()。如果真實狀態本身即為假,直接回傳假,跳過所有後續邏輯與堆疊檢查。 -
Android 14+ (API 34+) 採用
StackWalker惰性遍歷: 傳統new Throwable().getStackTrace()會導致全執行緒堆疊深度展開(Native Unwind)和幾百個字串分配,極易引起 GC 掉幀。 使用 Java 9 / Android 14+ 的java.lang.StackWalker,按需逐層掃描,零全堆疊分配。 -
Android < 34 動態堆疊定位(杜絕硬編碼): 僅單次執行
new Throwable().getStackTrace(),向下遍歷查詢目標方法所在的系統類別(如AccessibilityManager),緊隨其後的堆疊幀即為真實呼叫者。絕不硬編碼[3]或[4],保證跨 LSPosed 版本穩定執行。
效能最佳化示範程式碼:
// 檢查 Android 14+ StackWalker
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;
}
// 高頻方法 Hook 示範
Method isEnabled = AccessibilityManager.class.getDeclaredMethod("isEnabled");
hook(isEnabled).intercept(chain -> {
Object real = chain.proceed();
if (Boolean.FALSE.equals(real)) {
return false; // 系統本就是關的,零開銷直通回傳
}
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;
}));
}
}
四、升級常見陷阱與排查清單
assets/xposed_init是否仍需保留?- 現代 API 102 僅讀取
META-INF/xposed/java_init.list。如果保留assets/xposed_init,舊版框架可能嘗試使用傳統反射載入它而報錯,推薦直接移除或保持為空。
- 現代 API 102 僅讀取
compileSdk與 Android SDK 平台:- 建議將
compileSdk與targetSdk升級至 Android 33 或 34,以便順利使用現代介面和 Java 11/17 語言特性。
- 建議將
- Gradle 相容性:
- 如果建構環境使用的是 JDK 17 / 21,必須確保 Gradle Wrapper 升級至
7.4+(推薦7.5.1或8.x),AGP 升級至7.3+。
- 如果建構環境使用的是 JDK 17 / 21,必須確保 Gradle Wrapper 升級至
- 驗證模組識別:
- 使用解壓工具檢視最終 APK 根目錄,必須包含:
META-INF/xposed/module.propMETA-INF/xposed/java_init.listMETA-INF/xposed/scope.list
- 使用解壓工具檢視最終 APK 根目錄,必須包含: