Communityライティング&編集github.com

Marukon/skill-upgrade-libxposed-102

エージェントスキル

skill-upgrade-libxposed-102 とは?

このスキルは、従来のXposedモジュール(XposedBridge API 54-93ベース)を最新のLibXposed API 102(LSPosed 1.9.3以降やVectorなどのモダンフレームワーク向け)にアップグレードするための完全なガイドです。依存関係の移行、メタデータファイルの構成、マニフェストの更新、Hookロジックのリファクタリング、パフォーマンス最適化戦略を網羅しています。特に、チェーンベースのHookerモデル、自動ホットリロード、Android 14以上でのStackWalkerを用いた効率的なスタックトレース処理など、モダンなAPIの特徴を活かした実装パターンを詳述しています。このスキルにより、開発者は従来のXposedモジュールを効率的に移行し、LSPosedなどの最新フレームワーク上で安定かつ高性能に動作させることが可能になります。

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

Installed? Explore more ライティング&編集 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

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, IXposedHookZygoteInitio.github.libxposed.api.XposedModule を継承
エントリポイントの宣言assets/xposed_initMETA-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)

  1. 依存関係の更新: モジュールの 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'
    }
    
  2. Java コンパイル互換性:

    android {
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_11
            targetCompatibility JavaVersion.VERSION_11
        }
    }
    
  3. 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.thisObjectchain.getThisObject()
コンストラクタをフックfindAndHookConstructor(...)Constructor<?> ctor = ...;hook(ctor).intercept(chain -> ...);
静的初期化ブロックをフック従来の標準APIでは非対応hookClassInitializer(TargetClass.class).intercept(...)

三、高頻度 Hook シナリオとパフォーマンス深度最適化パターン

高頻度呼び出し(isEnabled、View描画、タッチディスパッチなど)に対する黄金の最適化ルール:

  1. ゼロオーバーヘッドの高速パス(スニッフィングバイパス): メソッドが状態を問い合わせるもの(isEnabled()、isLoaded() など)の場合、まず軽量なネイティブフィールド読み取り chain.proceed() を呼び出します。実際の状態が偽であれば、そのまま偽を返し、以降のすべてのロジックとスタックチェックをスキップします。

  2. Android 14+ (API 34+) では StackWalker による遅延走査を採用: 従来の new Throwable().getStackTrace() は、スレッドスタック全体の深い展開(Native Unwind)と数百の文字列割り当てを引き起こし、GCによるフレーム落ちの原因になりやすいです。 Java 9 / Android 14+ の java.lang.StackWalker を使用し、必要に応じて1フレームずつスキャンするため、全スタック割り当てがゼロになります。

  3. 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;
        }));
    }
}

四、アップグレードのよくある落とし穴とチェックリスト

  1. assets/xposed_init は残すべきか?
    • モダンなAPI 102 は META-INF/xposed/java_init.list のみを読み取ります。assets/xposed_init を残すと、古いフレームワークが従来のリフレクションでそれを読み込もうとしてエラーになる可能性があるため、削除するか空にしておくことを推奨します。
  2. compileSdk と Android SDK プラットフォーム:
    • モダンなインターフェースや Java 11/17 の言語機能を問題なく使用するために、compileSdk と targetSdk を Android 33 または 34 にアップグレードすることをお勧めします。
  3. Gradle 互換性:
    • ビルド環境が JDK 17 / 21 を使用している場合、Gradle Wrapper を 7.4+(推奨 7.5.1 または 8.x)に、AGP を 7.3+ にアップグレードする必要があります。
  4. モジュール認識の確認:
    • 解凍ツールを使用して最終的な APK ルートディレクトリを確認し、以下が含まれていることを確認してください。
      • META-INF/xposed/module.prop
      • META-INF/xposed/java_init.list
      • META-INF/xposed/scope.list

関連スキル