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 リソース配列 | META-INF/xposed/scope.list |
| Hook プログラミングモデル | XC_MethodHook (before / after) コールバック | OkHttp 風のチェーンインターセプター Hooker (chain.proceed()) |
| ホットリロード (Hot Reload) | 非対応(対象アプリやシステムの再起動が必要) | ネイティブ対応 (onHotReloading / onHotReloaded をオーバーライド) |
| Zygote 注入 | initZygote によるグローバル注入をサポート | Zygote 注入禁止、スコープ対象プロセスにのみ注入 |
| 難読化/R8 サポート | Hookメソッド名とクラス名を厳密に保持する必要あり | R8難読化をネイティブサポート、適応ルールを宣言するのみ |
二、標準的な移行の4つのステップ
ステップ1:ビルドと依存関係の移行 (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>(); }
ステップ2:モダンメタデータファイルの設定 (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(エントリポイントクラスの宣言)
モジュールエントリポイントの完全修飾クラス名を1行で宣言します。
com.example.myhook.MainHook
3. scope.list(推奨スコープ)
1行に1つのターゲットパッケージ名を記述し、モジュールがデフォルトで注入する必要があるアプリを宣言します。
com.android.chrome
com.microsoft.emmx
com.example.targetapp
ステップ3:マニフェストファイルの設定 (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>
ステップ4: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 のモダンな記法 |
|---|---|---|
| メソッドを検索してフック | XposedHelpers.findAndHookMethod("クラス名", cl, "メソッド名", 引数型..., callback) | Method m = Clazz.class.getDeclaredMethod("メソッド名", 引数型...);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() |
| コンストラクタをフック | findAndHookConstructor(...) | Constructor<?> ctor = ...;hook(ctor).intercept(chain -> ...); |
| 静的初期化ブロックをフック | 従来の標準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を使用し、必要に応じて1フレームずつスキャンするため、全スタック割り当てがゼロになります。 -
Android < 34 での動的スタック位置特定(ハードコーディング禁止): 1回だけ
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 プラットフォーム:- モダンなインターフェースや Java 11/17 の言語機能を問題なく使用するために、
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 ルートディレクトリを確認し、以下が含まれていることを確認してください。