Androidプラグインの総合分析

プラグイン学習準備知識

クラスローダのクラスローディングメカニズム

バインダー,AIDL,IPC

プラグインとコンポーネント化の違い

コンポーネントベース開発とは、アプリを複数のモジュールに分割し、各モジュールがコンポーネントです.開発プロセス中に、これらのコンポーネントを相互に依存させたり、コンポーネントを個別にデバッグしたりできますが、最終リリースが作成されると、これらのコンポーネントはapk リリースにマージされ、プラグインは 1 つのホストと複数のプラグイン apk に分割されます. プラグインの高コストは、Android バージョンに適応することです. Android バージョンごとにソース コードの実装が異なります. . 新しいバージョンが出るたびに、ソースコードを読んで修正する必要があります. このソースコードは適応されています.

プラグイン外からのクラスのロード

クラスのロードはクラスローダーに依存することがわかっているため、プラグイン クラスをホストにロードする場合、2 つのオプションがあります。

最初の方法は、各プラグインのクラスローダーを取得し、プラグインのクラスローダーを使用してプラグインのクラスをロードし、リフレクションを通じてクラスの情報を取得する方法です。

    //获取每个插件的classloader 然后利用插件的classloader 去加载插件的类。
    public void loadPluginClass(Context context, String pluginPath) {

        pluginPath = "/sdcard/plugin";

        if (TextUtils.isEmpty(pluginPath)) {
            throw new IllegalArgumentException("插件路径不能拿为空!");
        }

        File pluginFile = new File(pluginPath);
        if (!pluginFile.exists()) {
            Log.e("zjs", "插件文件不存在!");
            return ;
        }
   
        File optDir = context.getDir("optDir", Context.MODE_PRIVATE);
        String optDirPath = optDir.getAbsolutePath();
        Log.d("zjs", "optDirPath " + optDirPath);



        try {
            //获取到插件的DexClassLoader
            DexClassLoader dexClassLoader = new DexClassLoader(pluginPath, optDirPath, null, context.getClassLoader());
            //就可以利用插件的DexClassLoader 去加载 插件的一个个类,然后反射获取类的信息。
            Class<?> classType = dexClassLoader.loadClass("com.example.plugin.Book");
            Constructor<?> constructor = classType.getConstructor(String.class, int.class);
            Object book = constructor.newInstance("android开发艺术探索", 88);
            Method getNameMethod = classType.getMethod("getName");
            getNameMethod.setAccessible(true);
            Object name = getNameMethod.invoke(book);
            Log.d("zjs", "name " + name);
        } catch (Exception e) {
            Log.d("zjs", "e" , e);
            e.printStackTrace();

        }

    }

2 番目の方法:
プラグイン クラスローダの dexpathlist 内の Element【】要素とホスト classLoader の dexpathlist 内の Element【】要素を新しい Element【】要素にマージし、ホスト classLoader をこの新しい Element【】に置き換えます
。 element Element [] dexpathlist 内の要素
。これにより、ホストはホストの classLoader を直接使用して、プラグインの任意のクラスをロードできます。
もちろん、プラグイン classLoader の dexpathlist 内の Element [] 要素をこの新しい Element [] 要素に置き換えることができるので、プラグ
イン classLoader を直接使用してプラグインの任意のクラスをプラグイン。


    public void mergeHostAndPluginDex(Context context,String pluginPath){

        if (TextUtils.isEmpty(pluginPath)) {
            throw new IllegalArgumentException("插件路径不能拿为空!");
        }

        try {

            Class<?> clazz  = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListField = clazz.getDeclaredField("pathList");
            pathListField.setAccessible(true);

            Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
            Field dexElements = dexPathListClass.getDeclaredField("dexElements");
            dexElements.setAccessible(true);

            // 1.获取宿主的ClassLoader中的 dexPathList 在从 dexPathList 获取 dexElements
            ClassLoader pathClassLoader = context.getClassLoader();

        Object dexPathList = pathListField.get(pathClassLoader);
        Object[] hostElements = (Object[]) dexElements.get(dexPathList);
            // 2.获取插件的 dexElements
            DexClassLoader dexClassLoader = new DexClassLoader(pluginPath,
                    context.getCacheDir().getAbsolutePath(), null, pathClassLoader);
            Object pluginPathList = pathListField.get(dexClassLoader);
            Object[] pluginElements = (Object[]) dexElements.get(pluginPathList);

            // 3.先创建一个空的新数组
            Object[] allElements = (Object[]) Array.newInstance(hostElements.getClass().getComponentType(),
                    hostElements.length + pluginElements.length);

            //4把插件和宿主的Elements放进去
            System.arraycopy(hostElements, 0, allElements, 0, hostElements.length);
            System.arraycopy(pluginElements, 0, allElements, hostElements.length, pluginElements.length);

            // 5.把宿主的classloader 的 dexPathList 中的dexElements 换成 allElements
            dexElements.set(dexPathList, allElements);
        } catch (Exception e) {
            Log.d("zjs", "e" , e);
            e.printStackTrace();
        }

    }

次のように使用します。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        //获取sdcard 读写权限
        ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        // 高版本Android SDK时使用如下代码
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if(!Environment.isExternalStorageManager()){
                Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                startActivity(intent);
                return;
            }
        }
        //把插件的dex和宿主的dex和在宿主的classloader中
        mergeHostAndPluginDex(this, "/sdcard/plugin.apk");

        //就可以直接在宿主的ClassLoader 去加载 插件的一个个类,然后反射获取类的信息。
        try {
            ClassLoader classLoader =  this.getClassLoader();
            Class<?> classType = classLoader.loadClass("com.example.plugin.Book");
            Constructor<?> constructor = classType.getConstructor(String.class, int.class);
            Object book = constructor.newInstance("android开发艺术探索", 88);
            Method getNameMethod = classType.getMethod("getName");
            getNameMethod.setAccessible(true);
            Object name = getNameMethod.invoke(book);
            Log.d("zjs", "name " + name);
        } catch (Exception e) {
            Log.d("zjs", "e " , e);
            e.printStackTrace();
        }
    }

この操作には欠点もあります.
プラグインとホストが同じライブラリの異なるバージョンを参照すると、プログラム エラーが発生する可能性があり、それを回避するには特別な処理が必要です
.
ここに画像の説明を挿入
ホストとプラグインが異なる. システム内のクラスはシステムの PathClassLoader によってロードされるため、ホストを最初にロードする必要があり、親委任メカニズムの存在により、すでにロードされているクラスv1.0 と v2.0 の差分コードを呼び出すと、問題が発生する可能性があります。
また、プラグインが多すぎると、ホストの dexElements 配列のサイズが大きくなります。

**

4 つのコンポーネントのワークフロー

以下のソースコード分析は、android 7.0 に基づいています
**

序章

4 つの主要コンポーネントの作業プロセスは、実際には 4 つの主要コンポーネントと AMS の間のプロセス通信プロセスです。
各プロセスの主要な 4 つのコンポーネントに関する情報が AMS に登録されます。
オンにするたびに、PMS は各アプリケーションを再インストールし、各アプリケーションの androidmanifest ファイルを読み取り、登録のために AMS に提供します。

では、AMS はどのようにして各アプリケーションの 4 つの主要コンポーネントの情報を取得するのでしょうか?

4 つの主要コンポーネントのワークフローを見る前に

まず第一に, プロセスの最も重要なスレッドはメイン スレッドの ActivityThread であることを理解しなければなりません. この ActivityTherad には
2 つの非常に重要なクラスが内部にあり、1 つは H クラスと呼ばれ、Handle クラスです。もう 1 つは ApplicationThread クラスと呼ばれ、現在のプロセスの Binder を表すバインダーです。
それらの役割と関係は、ApplicationThread が ams からのさまざまなメッセージの受信を担当し、次に H クラスを使用して対応するメッセージを送信し、次に独自のハンドル メッセージを使用して対応するメッセージを受信し、ActivityThread の対応するメソッドを使用することです。

活動開始の流れ

開始アクティビティには 2 つのタイプがあります。1 つはルート アクティビティの開始で、ランチャー プロセスはデスクトップ アイコンをクリックしてプロセスを開始し、最初のアクティビティを開始します。他のすべてのケースは通常のアクティビティです。以下で分析する開始アクティビティは、ルート アクティビティです。

アプリ プロセスの開始方法。

視覚効果は、デスクトップ上のアイコンをクリックしてプロセスを開始することです。

各アプリ プロセス androidmanifest は、ホームページのアクティビティをマークします。

  <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

システムが起動するたびに。PMS は毎回 apk アプリケーションをインストールし、プロセスは各アプリの androidmanifest 内の情報を読み取ります。次に、この情報をデスクトップ プロセス ランチャーに渡します。ランチャーは、この情報に基づいてアプリのアイコンとテキストを表示し、パッケージ情報とこれに対する最初のアクティビティをマークします。

ユーザーがアイコンをクリックしたとき。
その後の工程です。ランチャー プロセスは ams プロセスと通信し、ams はアプリケーション プロセスと通信します。
基本的に、ランチャーはそのアクティビティを開始するためにどのプロセスを開始するかを ams に指示し、ams はどのプロセスを開始します。そして、どのページを開始するかをアプリケーションに指示すると、アプリケーションはどのページを開始します。
ここに画像の説明を挿入

サーバーのワークフロー

サーバー ワークフローには 2 種類あり、
1 つは context.startServer(intent) で
、もう 1 つは context.bindSever(intent) です。
ここに画像の説明を挿入

ブロードキャスト

ブロードキャストは受信側であり、ID カード Intentfiler を AMS に登録し
、ID に対応するブロードキャストを AMS に送信します. AMS は対応する受信者を見つけ、対応するブロードキャストをプロセスに送信し、最後に受信者が情報を受信します.

次に動的登録についてですが、静的登録とは、PMSがインストールされるたびにAndroidMasnifestから情報を取得してAMSに登録することです。
ここに画像の説明を挿入

コンテンツプロバイダー

Contentprovider の本質は、データが SQLite データベースに格納されることです
. 異なるデータには異なるデータ テーブルがあります. Contentprovider は SQLite をカプセル化するだけです.

If you want to operation Contentprovider, you have to use ContentResolver. ContentResolver は、操作する Contentprovider のどのテーブルを示すために uri を指定する必要があります。

ここに画像の説明を挿入

4 つの主要コンポーネントのロード プロセスを読んだ後、引き続きリソースを見てみましょう。

リソース リソースの読み込みプロセス、プラグイン リソースの取得方法、およびプラグイン スキニングの実現方法

プロキシモード

エージェントは、静的エージェントと動的エージェントに分けられます。

たとえば、静的プロキシ

クラス Class1 にはメソッド doSomething() があります

次に、この時点で Class1 のプロキシ クラスを作成します。最初にインターフェイス ClassInterface を実装します。インターフェイス メソッドは doSomething() で、次に
Class1 にこのインターフェイスを実装させます。
次に、プロキシ クラス Class2 を作成して ClassInterface も実装します。 、そしてdoSomethingを書き直して、
Class2にClass1のクラスを持たせて、Class2のdoSomethingメソッドでClass1のdoSomethingを呼び出して、Class2のdoSomethingを実現し、Class1のdoSomethingを実現することが最も重要です。クラス2を実現する クラス1を置き換える

public class Class2 implements ClassInterface {
Class1 class1 = new Class1 ();
@Override
public void doSomething () {

//在调用class1.doSomething()之前做一些你需要的逻辑
 class1.doSomething() ;
 //在调用class1.doSomething()之后做一些你需要的逻辑
 }
 }

なぜそのようなプロキシを埋め込む必要があるのでしょうか? 実際には、class1 の元の機能に影響を与えることなく、必要なロジックを class2 に追加できるという利点があります。

動的プロキシ:

動的プロキシの目的は、クラスのプロキシ クラスを作成して、元のクラスのロジックを変更せずに必要なロジックを追加することです。

動的クラスは
Proxy クラスの newProxyInstance メソッドに依存しており、その宣言は次のとおりです。

static Object newProxylnstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

Proxy クラスの newProxyInstance メソッドは、各インターフェイス タイプ オブジェクトのプロキシ オブジェクトを作成できます.
たとえば、クラス class1 が ClassInterface インターフェイスを実装している場合、
Proxy クラスの newProxyInstance メソッドを使用して、この class1 のプロキシ オブジェクトを作成できます。

最初のパラメータはターゲット オブジェクトのクラスローダーで、
2 番目のパラメータはターゲット オブジェクトのインターフェイス クラス タイプです. リフレクションを使用してインターフェイス クラス タイプを取得できます. 3 番目のパラメータは InvocationHandler
インターフェイスを実装するクラス オブジェクトです. pass it コンストラクターは
ターゲット オブジェクトを注入します。

具体例は以下のとおりです。

ClassInterface class1 = new Class1();
ClassInterface classlProxy = (Classllnterface) Proxy .newProxylnstance(
class1. getClass () .getClassLoader (), 
class1.getClass () .getlnterfaces (),
new InvocationHandlerForTest(class1));

public class InvocationHandlerForTest implements InvocationHandler { 

//这个target 其实就是传进来原来对象
private Object target ;

public InvocationHandlerForTest (Object target) {
 this.target = target;
 
 }

 @Override
public Object invoke (Object o, Method method, Object [] objects) throws. Throwable {
//method.invoke(target , objects)这个方法就是还原目标对象的原来的方法
Object obj =method.invoke(target , objects) ; 
return obj ;

}

}

当你调用 classlProxy.doSomething 》InvocationHandlerForTest.invoke > method.invoke(target , objects) >
class1.doSomething

そのため、カスタム InvocationHandler クラスを作成するときは、呼び出しメソッド method.invoke(target, objects) を使用する必要があります。この方法でのみ、元の論理操作を復元できます。

動的プロキシを理解した後、システムの特定のクラス オブジェクトをフックする方法を学びましょう. フックとは、システムの特定のクラス オブジェクトに対してプロキシ オブジェクトを作成し、リフレクションを使用して元のオブジェクトをプロキシ オブジェクトに置き換えることです。次に、アプリケーション プロセスで AMS のバインダー オブジェクトのプロキシ オブジェクトを作成し、このプロキシ オブジェクトを使用してソース コードのバインダー オブジェクトを置き換えます。

public class HookAMP {

    //android 8.0
    public  static  void hookAMP(Context context){

        try {
            //先获取ActivityManager类里面的静态变量 IActivityManagerSingleton
            Class activityManagerClass = Class.forName("android.app.ActivityManager");
            Field fieldActivityManagerSingleton  = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
            fieldActivityManagerSingleton.setAccessible(true);
            Object IActivityManagerSingleton = fieldActivityManagerSingleton.get(null);

            //从这个 IActivityManagerSingleton获取他的mInstance对象。这个对象就是AMP
            Class classSingleton = Class.forName("android.util.Singleton");
            Field mInstanceField = classSingleton.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            Object mInstance = mInstanceField.get(IActivityManagerSingleton);

            // 自定义一个 mInstance  的代理
            Class<?> iActivityManagerInterface =Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(context.getClassLoader(),
                    new Class<?>[]{iActivityManagerInterface},
                    new InvocationHandlerBinder(mInstance));

            //把IActivityManagerSingleton的mInstance替换为 proxy
            mInstanceField.set(IActivityManagerSingleton,proxy);



        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
            Log.d("zjs", "hookAMP: ",e);
            e.printStackTrace();
        }
    }


    //android 8.0之前
    public  static  void hookAMP2(Context context){

        try {
            //先获取ActivityManagerNative类里面的静态变量 gDefault 它是个 Singleton 类型的
            Class activityManagerClass = Class.forName("android.app.ActivityManagerNative");
            Field gDefaultField  = activityManagerClass.getDeclaredField("gDefault");
            gDefaultField.setAccessible(true);
            Object gDefault = gDefaultField.get(null);

            //从这个 gDefault获取他的mInstance对象。这个mInstance 对象就是AMP
            Class classSingleton = Class.forName("android.util.Singleton");
            Field mInstanceField = classSingleton.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            Object mInstance = mInstanceField.get(gDefault);

            // 自定义一个 mInstance  的代理
            Class<?> iActivityManagerInterface =Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(context.getClassLoader(),
                    new Class<?>[]{iActivityManagerInterface},
                    new InvocationHandlerBinder(mInstance));

            //把gDefault的mInstance替换为 proxy
            mInstanceField.set(gDefault,proxy);


        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
            Log.d("zjs", "hookAMP: ",e);
            e.printStackTrace();
        }
    }

    static  class InvocationHandlerBinder implements InvocationHandler{
        private  Object mBase;

        public InvocationHandlerBinder(Object base) {
            mBase= base;
        }

        @Override
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
            //动态代理了AMS在 应用进程的binder对象,这样 应用进程 调用 ams 的binder对象 通信给 ams的每个方法都会 经过这里
            Log.d("zjs", "you are hook: method:" + method.getName());
            //这里依旧是还原原本变量应该做的事情,
            return method.invoke(mBase,objects);
        }
    }
}

正しいフックポイントの見つけ方とフックポイントを見つける原理

静的変数またはシングルトン オブジェクトを見つけようとする
パブリック メソッドおよびオブジェクトを見つけようとする

最も単純な apk プラグインの実装

前回の調査で、プラグインのすべてのクラスをホストにロードし、プラグインのすべてのリソースを取得する方法がわかりましたが、最も重要なことが 1 つあります。それは、プラグの 4 つの主要コンポーネントです。 -in. 4つの主要なコンポーネントのみの通常のクラスは、もちろん学習して​​ロードしましたが、4つの主要なコンポーネントは単なる通常のクラスではなく、anroidmainfestで宣言する必要があり、宣言されていない場合はシステムを処理する必要があります、システムはそれらを認識しません。
つまり、ホスト androidmainfest ですべてのプラグインの 4 つの主要コンポーネントを宣言することで、ホストでプラグインの 4 つの主要コンポーネントを起動することは
、独自のホストの 4 つの主要なコンポーネントの違いですが、これは実際にはプラグインの意味を失っています。数千のプラグインと数千のコンポーネントがある場合、それらはホスト androidmainfest で宣言する必要があるため、プレースホルダーと数千万のアイデアに対する 1 つのソリューションがあります。
次に、4 つの主要コンポーネントのプラグイン実装について説明します。

アクティビティ プラグインの実装

最初に実装のアイデアについて話させてください
. 最初に達成することは、ホストでプラグインのアクティビティを有効にすることです.

		//在宿主内
        Intent intent = new Intent();
        ComponentName pluginActivity= new ComponentName("com.example.plugin", "com.example.plugin.MainActivity");
        intent.setComponent(pluginActivity);
        startActivity(intent);

これをホストに書き込むと、ホストの androidmainfest でこの com.example.plugin.MainActivity を宣言しなかったため、間違いなくエラーが報告されます。AMS はプラグインのアクティビティを認識していないため、最初のステップは次のとおりです。ホストでそれを行うには
androidmainfest はパペットの SubActivity を追加します。

<activity android:name="com.example.myapplication.SubActivity"/>

次に、ホストがフックを介して startActivity(pluginActivity); を呼び出すと、AMS が SubActivity を開始するように AMS に指示します (AMS はこの SubActivity を認識しており、AMS の通知後に SubActivity を開始するため)。
上記では、SubActivity はプラグイン内の数千万のアクティビティを処理する必要があると述べました。つまり、ホストでプラグインを開く必要があるアクティビティが何であれ、AMS をだまして SubActivity を開始させます。開くアクティビティデータを SubActivity に入れて保存するのがポイントで、AMS 通知が SubActivity を開始して戻ってきたら、
SubActivity から取り出して実際に realActivity を開始します。

さて、上記のアイデアが出てくると、私たちはそれを実現し始めます。

1 つ目は、ホストでプラグインのアクティビティを開始することです。

	//在宿主内,开启任意想要的activity
       Intent intent = new Intent();
        ComponentName pluginActivity= new ComponentName("com.example.plugin", "com.example.plugin.MainActivity");
        intent.setComponent(pluginActivity);
        startActivity(intent);

ホストでパペット SubActivity を宣言する

<activity android:name="com.example.myapplication.SubActivity"/>

AMS に Hook Deception で SubActivity を開始するように指示し、SubActivity に本来開始されるアクティビティの Intent 情報を格納する

以前にアクティビティのワークフローを学習し、アプリケーション プロセスにおける AMS のプロキシ オブジェクトが AMP であること、つまり、プロセスが AMP を介して AMS に何をすべきかを伝えることを知っているので、AMP をフックできます。以前に学んだ

    //android 8.0之前
    public  static  void hookAMP(Context context){

        try {
            //先获取ActivityManagerNative类里面的静态变量 gDefault 它是个 Singleton 类型的
            Class activityManagerClass = Class.forName("android.app.ActivityManagerNative");
            Field gDefaultField  = activityManagerClass.getDeclaredField("gDefault");
            gDefaultField.setAccessible(true);
            Object gDefault = gDefaultField.get(null);

            //从这个 gDefault获取他的mInstance对象。这个mInstance 对象就是AMP
            Class classSingleton = Class.forName("android.util.Singleton");
            Field mInstanceField = classSingleton.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            Object mInstance = mInstanceField.get(gDefault);

            // 自定义一个 mInstance  的代理
            Class<?> iActivityManagerInterface =Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(context.getClassLoader(),
                    new Class<?>[]{iActivityManagerInterface},
                    new InvocationHandlerBinder(mInstance));

            //把gDefault的mInstance替换为 proxy
            mInstanceField.set(gDefault,proxy);


        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
            Log.d("zjs", "hookAMP: ",e);
            e.printStackTrace();
        }
    }





     static  class InvocationHandlerBinder implements InvocationHandler{
        private  Object mBase;

        public InvocationHandlerBinder(Object base) {
            mBase= base;
        }

        @Override
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
            //动态代理了AMS在 应用进程的binder对象,这样 应用进程 调用 ams 的binder对象 通信给 ams的每个方法都会 经过这里

            try {

                //如果应用进程让AMS 开启活动
                if ("startActivity".equals(method.getName())) {

                    int index = 0;
                    for (int i = 0; i < objects.length; i++) {
                        if (objects[i] instanceof Intent) {
                            index = i;
                        }
                    }

                    //从参数中获得实际上要启动activity
                    Intent realActivity = (Intent) objects[index];

                    //从这里判断这个activity 的包名是不是 不是 宿主的,不是才需要 创建个傀儡subActivity,然后把真正要启动到 realActivity放入subActivity中
                    //最好偷梁换柱,把原本的 变量realActivity 换成 subActivity
                    if (!("com.example.myapplication".equals(realActivity.getComponent().getPackageName()))) {
                        Intent subActivity = new Intent();
                        subActivity.setComponent(new ComponentName("com.example.myapplication", "com.example.myapplication.SubActivity"));
                        subActivity.putExtra("plugin", realActivity);
                        objects[index] = subActivity;
                    }

                }
            }catch (Exception e){
                Log.d("zjs", "invoke: ",e);
            }


            //这里依旧是还源变量应该做的事情,
            return method.invoke(mBase,objects);
        }
    }

最後のステップで、AMS がアプリケーション プロセスに SubActivity を開始するよう指示した場合、代わりに SubActivity で readActivity を開始します。


前のアクティビティ ワークフローのソース コードから、AMS がアプリケーション プロセスに SubActivity を開始するように指示すると、アプリケーション プロセスの ActivityThread でハンドル クラス H を通過することがわかります.この H は、LAUNCHER_ACTIVITY = というメッセージを送信します。 100、Handle H () メソッドの dipathchMessage、このメソッドのソース コードから、
この Handle H にプロキシ CallbackProxy を設定できることがわかり、 CallbackProxy にメッセージを処理させることができます。

 public static  void hookHandleCallback(){
        try {
            //先获取ActivityThread类里面的静态变量 sCurrentActivityThread
            Class activityThread  = Class.forName("android.app.ActivityThread");

            Field currentActivityThreadField  = activityThread.getDeclaredField("sCurrentActivityThread");
            currentActivityThreadField.setAccessible(true);
            Object sCurrentActivityThread = currentActivityThreadField.get(null);

            //在从sCurrentActivityThread内部 获取Handle类 mH对象 变量
            Field mHField  = activityThread.getDeclaredField("mH");
            mHField.setAccessible(true);
            Handler mH = (Handler) mHField.get(sCurrentActivityThread);

            //把mh的mCallback字段替换成代理的
            Class handle  = Handler.class;;
            Field mCallbackField  = handle.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);
            mCallbackField.set(mH,new CallbackProxy(mH));


        } catch (Exception e) {
            Log.d("zjs", "hookHandleCallback",e);
            e.printStackTrace();
        }
    }
    private static class CallbackProxy  implements  Handler.Callback{
        Handler mH;

        public CallbackProxy(Handler mH) {
            this.mH = mH;
        }


        private void handleLauncherActivity(Message message) {
            try {
                //这里的message.obj 其实就是个ActivityClientRecord 对象
                Object obj = message.obj;
                //从这个ActivityClientRecord获取intent变量
                Class object = obj.getClass();
                Field intentField = object.getDeclaredField("intent");
                intentField.setAccessible(true);
                //这个raw Activity  就是AMS要去启动的Activity
                Intent raw = (Intent) intentField.get(obj);

                //我们对这个Activity进行判断 如果它里面存有插件活动,则证明这个Activity是个SubActivity
                //那么我们就需要 把这个activity 设置 realActivity
                Intent realActivity = raw.getParcelableExtra("plugin");
                if(realActivity!=null){
                    raw.setComponent(realActivity.getComponent());
                }

                //到了这里不管是不是插件活动还是宿主活动 raw 都会是正确的值
                Log.d("zjs", "handleLauncherActivity: "+ raw.getComponent().getClassName());

            }catch (Exception e){
                Log.d("zjs", "handleLauncherActivity: ",e);
            }

        }

        @Override
        public boolean handleMessage(@NonNull Message message) {
            final int LAUNCH_ACTIVITY = 100;
            switch (message.what){
                case LAUNCH_ACTIVITY:
                    handleLauncherActivity(message);
            }
            //还原原本操作
            mH.handleMessage(message);
            return true;
        }
    }

結局のところ、それは

public class MyApplication  extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        //将插件和宿主dex合并
        LoadPluginDex.mergeHostAndPluginDex(this,"/sdcard/plugin.apk")

        //告诉AMS启动的SubActivity
        Hook.hookAMP(this);
        //回来启动的realActivity
        Hook.hookHandleCallback();
    }
}

使用するには、ホストでプラグイン アクティビティを開始します。

  Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("zjs", "onClick:plugin");
                Intent intent = new Intent();
                ComponentName pluginActivity  = new ComponentName("com.example.plugin", "com.example.plugin.MainActivity");
                intent.setComponent(pluginActivity);
                startActivity(intent);
            }
        });

効果は次のとおりです。
ここに画像の説明を挿入

活動資源の問題

フックを介して, ホストでプラグインのアクティビティを開始できます. アクティビティは開始されますが, アクティビティのインターフェースは起動していません. リソースのプラグインには 2 つのソリューションがあります. 1 つは結合することです.プラグインのリソースとホストのリソース
. allResource をマージしますが、プラグインのリソース ID がホストのリソース ID と同じ場合、リソースがないという欠点がありますallResource 内のプラグインの ID。解決策は、appt を使用してプラグイン プレフィックスのリソース ID を変更することです。

もう 1 つの解決策は、プラグインのリソースをホストのリソースから分離することです。そのため、注意すべき重要な点がいくつかあります。

プラグインのリソースを取得したら、ホストで取得するか、プラグイン自体が独自のリソースを取得するか? 実際には、プラグインでリフレクションを使用して独自のリソースを取得する方が適切です。ホストでリフレクションを使用してプラグインの pluginResource を取得します。プラグインのコードは、リソースを取得するためにホストの pluginResource オブジェクトを参照する必要があります。

2. プラグインの dex をホストにマージすると、親委任メカニズムに従って、ロードされたクラスが再度ロードされることはなく、ホストの dex がプラグイン、つまり、すべてがホストに基づいています。マスター、ホストの場合、プラグインの MainActivity は実際には通常のクラスであり、特別なものはありません。

プラグインで

public class MainActivity extends Activity {
 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        getApplication()//获取的是宿主的Application,就算插件的自定义Application这个Application也不会被执行,因为整个项目里,Application,已经被宿主的Application加载过了
        context getResource//获取的也是宿主的Resource
        setContentView(R.layout.activity_main);//context 的 setContentView(R.layout.activity_main)也是从宿主的Resource中找

3. ホストとプラグインの両方にシステム リソースがあるかどうかに注意してください。または、参照されている 3 番目のライブラリのリソースが、同じ名前が原因で競合します。

例えば。プラグインの MainActivity が AppCompatActivity を継承する場合、この AppCompatActivity は、ホストとプラグインの両方でサードパーティの Android x ライブラリを参照します。AppCompatActivity のビュー読み込みプロセスに ID R.id.deco_content_parent があります。プラグイン apk をコンパイルするとき。その ID が 0x7f07004d だとしましょう。
ホストの apk がコンパイルされると、その ID は 0x7f07004e になります。次に、プラグイン内の conetxt がホストであるため、プラグインが ID 0x7f07004d を見つけようとすると、0x7f07004e しか見つからないことがわかり、エラーが報告されます。

ホストにとって、プラグインの MainActivity クラスは、実際にはホスト内の通常のクラスであり、特別なことは何もありませんが、対応する MainActivity クラスのみを実行します。 host.メソッドで、
プラグイン MainActivity クラスに反映して独自の pluginResource を取得し、MainActivity で Context を作成し、リフレクションによって pluginResource をこの Context に設定し、この Context からビューを取得します。

後続の MainActivity クラスは、このコンテキストからリソースを取得します。

コードは以下のように表示されます:

プラグインで:

package com.example.plugin;

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;


import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.appcompat.widget.AppCompatButton;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

//
public class MainActivity extends AppCompatActivity {
    public Resources pluginResources;

    private  Context pluginContext;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //在这个类里先获取插件的pluginResources
        preloadResource(getApplication(), "/sdcard/plugin.apk");

        //创建一个pluginContext,把pluginResources设置到pluginContext中去
        pluginContext = new ContextThemeWrapper(getBaseContext(),0);
        Class classname = pluginContext.getClass();
        try {
            Field field = classname.getDeclaredField("mResources");
            field.setAccessible(true);
            field.set(pluginContext, pluginResources);

        }catch (Exception e){
            Log.d("zjs", "plugin onCreate: ",e);
        }

        //通过pluginContext创造view
        View view = LayoutInflater.from(pluginContext).inflate(R.layout.activity_main, null);
        setContentView(view);
        AppCompatButton button = view.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("zjs", "plugin MainActivity button: ");
            }
        });

        Log.d("zjs", "plugin MainActivity onCreate: ");


    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d("zjs", "plugin MainActivity onPause: ");
    }

    //验证插件中是否能获取宿主的资源
    @Override
    protected void onResume() {
        super.onResume();
        Log.d("zjs", "plugin MainActivity onResume: ");

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d("zjs", "plugin MainActivity onDestroy: ");
    }


    public  void preloadResource(Context context, String apkFilePath) {
        try {

            //反射调用AssetManager的addAssetPath方法把插件路径传入
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPathMethod = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPathMethod.setAccessible(true);
            addAssetPathMethod.invoke(assetManager,apkFilePath);

            //以插件的 AssetManager 创作属于插件的 resources。
            //这里的resource的后面两个参数,一般跟宿主的配置一样就可以了,根据当前的设备显示器信息 与 配置(横竖屏、语言等) 创建
            pluginResources = new Resources(assetManager,context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

作成したコンテキストは ContextThemeWrapper で、インポートされたパッケージは Android x の ContextThemeWrapper です。
ここに画像の説明を挿入

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">




    <androidx.appcompat.widget.AppCompatButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/plugin_button"
        android:background="@color/black"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:id="@+id/button"/>

    <androidx.appcompat.widget.AppCompatTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toTopOf="@id/button"
        android:text="@string/plugin_button"/>






</androidx.constraintlayout.widget.ConstraintLayout>

ライフサイクル

最後に、AMS が SubActiviy を開始し、それを RealActivity に置き換えると考えられます.ライフ サイクルは影響を受けますか?実際には影響はありません.上記のアクティビティのソース コード プロセスを通じて知ることができます.

AMS とアプリケーション プロセスの両方が、ArrayMap<IBinder,ActivityClientRecord> である mActivitys を維持します。

AMS は、このキーに従って、キーに対応する ActivityClientRecord にメッセージを送信し
、アプリケーション プロセスは、このキーに基づいて、キーに対応する ActivityClientRecord をどうするかを AMS に指示します。

最初に、AMS を欺いてサブアクティビティを開始するようにフックし、次に AMS mActivitys をフックしました。そのトークン キーの 1 つはサブアクティビティに対応します。

次に、AMS がアプリケーション プロセスに、このキーに従ってサブアクティビティを開始するように指示します。
アプリケーションプロセスがこのメッセージを受信するプロセスは次のとおりです。

LAUNCH ACTIVITY = 100》handleLaunchActivity》performlaunchActivity》mInstrumentation.newActiviry>mInstrumentation.callonActivityonCreate>onCreate
PAUSE_ACTIVITY = 101》handlePauseActivity》performPauseActivity>》mInstrumentation.callActivityonPause>onPause

performlaunchActivity メソッド内。
mActivitys.put(r.token, r) は、
現在の ActivityClientRescord とアプリケーション プロセスのトークンを保存します。

And what we hooked is LAUNCH ACTIVITY = 100 "handleLaunchActivity". このステップでは、ActivityClientRescord のインテントのコンポーネントを元のサブアクティビティから必要な RealActivity に変更します。performlaunchActivity のステップで、mActivitys.put(r.token, r) は
このトークンの RealActivity を格納します。
したがって、アプリケーション プロセスの mActivities は、AMS の mActivities 内の同じトークン キーとは異なります。
アプリケーション プロセスに対応する RealActivity と、AMS に対応するサブアクティビティ。

アプリケーションプロセスがアクティビティの onpause メソッドを呼び出す必要がある場合、プロセスは Instrumentation に実行され、バインダーを使用して AMSにトークンに対応するアクティビティの
onpause を呼び出すように指示します。
AMS がメッセージを受け取った後、このトークンによると、サブアクティビティの onpause を呼び出していると見なされ、アプリケーション プロセスがメッセージを受信した後、このトークンに従って RealActivity の onpause を呼び出していると見なされます。

サーバープラグイン

実際、sercer の実装にはアクティビティと多くの類似点がありますが、サーバーの電源が入っている場合、startServer を複数回呼び出す必要があり、複数ではなく 1 つのインスタンスのみが開かれることに注意してください。

ここにコードを直接投稿してください。

public class MyApplication  extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        //将插件和宿主dex合并
        LoadPluginDex.mergeHostAndPluginDex(this,"/sdcard/plugin.apk");
        //将插件和宿主reource合并
        PluginAndHostResource.init(this);

        //告诉AMS启动的SubService
        Hook.hookAMP(this);
        //回来启动的realService
        Hook.hookHandleCallback();


    }
}
 //android 8.0之前
    public  static  void hookAMP(Context context){

        try {
            //先获取ActivityManagerNative类里面的静态变量 gDefault 它是个 Singleton 类型的
            Class activityManagerClass = Class.forName("android.app.ActivityManagerNative");
            Field gDefaultField  = activityManagerClass.getDeclaredField("gDefault");
            gDefaultField.setAccessible(true);
            Object gDefault = gDefaultField.get(null);

            //从这个 gDefault获取他的mInstance对象。这个mInstance 对象就是AMP
            Class classSingleton = Class.forName("android.util.Singleton");
            Field mInstanceField = classSingleton.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            Object mInstance = mInstanceField.get(gDefault);

            // 自定义一个 mInstance  的代理
            Class<?> iActivityManagerInterface =Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(context.getClassLoader(),
                    new Class<?>[]{iActivityManagerInterface},
                    new InvocationHandlerBinder(mInstance));

            //把gDefault的mInstance替换为 proxy
            mInstanceField.set(gDefault,proxy);


        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
            Log.d("zjs", "hookAMP: ",e);
            e.printStackTrace();
        }
    }



    public static  void hookHandleCallback(){
        try {
            //先获取ActivityThread类里面的静态变量 sCurrentActivityThread
            Class activityThread  = Class.forName("android.app.ActivityThread");

            Field currentActivityThreadField  = activityThread.getDeclaredField("sCurrentActivityThread");
            currentActivityThreadField.setAccessible(true);
            Object sCurrentActivityThread = currentActivityThreadField.get(null);

            //在从sCurrentActivityThread内部 获取H类 mH对象 变量
            Field mHField  = activityThread.getDeclaredField("mH");
            mHField.setAccessible(true);
            Handler mH = (Handler) mHField.get(sCurrentActivityThread);

            //把mh的mCallback字段替换成代理的
            Class handle  = Handler.class;;
            Field mCallbackField  = handle.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);
            mCallbackField.set(mH,new CallbackProxy(mH));


        } catch (Exception e) {
            Log.d("zjs", "hookHandleCallback",e);
            e.printStackTrace();
        }
    }
    static  class InvocationHandlerBinder implements InvocationHandler{
        private  Object mBase;

        public InvocationHandlerBinder(Object base) {
            mBase= base;
        }

        @Override
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
            //动态代理了AMS在 应用进程的binder对象,这样 应用进程 调用 ams 的binder对象 通信给 ams的每个方法都会 经过这里

            try {

                //如果应用进程让AMS 开启服务
                if ("startService".equals(method.getName()) || "stopService".equals(method.getName())) {

                    int index = 0;
                    for (int i = 0; i < objects.length; i++) {
                        if (objects[i] instanceof Intent) {
                            index = i;
                        }
                    }

                    //从参数中获得实际上要启动服务
                    Intent realService = (Intent) objects[index];

                    //从这里判断这个SubService 的包名是不是 不是 宿主的,不是才需要 创建SubService,然后把真正要启动到 realService放入SubService中
                    if (!("com.example.myapplication".equals(realService.getComponent().getPackageName()))) {
                        Intent subService = new Intent();
                        subService.setComponent(new ComponentName("com.example.myapplication", "com.example.myapplication.SubService"));
                        subService.putExtra("pluginService", realService);
                        objects[index] = subService;
                    }

                }

            }catch (Exception e){
                Log.d("zjs", "invoke: ",e);
            }


            //这里依旧是还源变量应该做的事情,
            return method.invoke(mBase,objects);
        }
    }
    private static class CallbackProxy  implements  Handler.Callback{
        Handler mH;

        public CallbackProxy(Handler mH) {
            this.mH = mH;
        }


        private void handleCreateService(Message message) {
            try {

                //这里的obj其实是个CreateServiceData
                Object obj = message.obj;

                //从CreateServiceData获取ServiceInfo  info  对象,info对象里面的name 就是要开启的server name,
                //我们只需要跟换这个name就可以了换成我们要的启动的realService
                ServiceInfo serviceInfo = (ServiceInfo) RefInvoke.getFieldObject(obj, "info");
                if("com.example.myapplication.SubService".equals(serviceInfo.name)){
                    serviceInfo.name = "com.example.plugin.PluginServer";
                }
                
                Log.d("zjs", "handleCreateService: "+ serviceInfo.name);

            }catch (Exception e){
                Log.d("zjs", "handleCreateService: ",e);
            }

        }

        @Override
        public boolean handleMessage(@NonNull Message message) {
            final int CREATE_SERVICE = 114;
            switch (message.what){
                case CREATE_SERVICE:
                    handleCreateService(message);
            }
            //还原原本操作
            mH.handleMessage(message);
            return true;
        }
    }
}

上記のコードは実際にはアクティビティに似ており、2 つのキー ポイントを前後にフックしていますが、2 つのプロキシ クラスのロジックのみを変更しています。

最終的な用途は、ホストでサービスを開始することです

 Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("zjs", "onClick:plugin");
                Intent intent = new Intent();
                ComponentName pluginServer= new ComponentName("com.example.plugin", "com.example.plugin.PluginServer");
                intent.setComponent(pluginServer);
                startService(intent);

            }
        });

効果は以下の通り
ここに画像の説明を挿入

放送プラグイン

ブロードキャスト プロセスは、受信者の登録とブロードキャストの送信に分けられます。

レシーバーの登録には静的登録と動的登録があり、レシーバーの動的登録はandroidmanifestへの登録が不要なため、プラグインへのレシーバークラスの動的登録は一切の処理が不要です。ホストがプラグインクラスをロードできる限り、通常のクラス。

プラグインの静的登録の受け入れクラスには 2 つの実装があります。

1 PMS はホスト アプリの androidmanifest で静的レシーバーを読み取ることができるため、PMS を手動で制御して、リフレクションを通じてプラグインの androidmanifest で静的レシーバーを読み取り、ホストに動的に登録することもできます。

まずは準備知識を理解する

PMS プロセスは、クラス PackageParser を介して apk データを解析します。
クラス PackageParser にはメソッドがあります。public void Package parsePackage(File apkFile , int flags)
このメソッドには 2 つのパラメーターがあり、1 つは apk ファイル、もう 1 つはフィルター タグです。
つまり、このメソッドを呼び出すと、apk の情報をフィルターで除外し、情報を取得し、Package クラス オブジェクトを返す

したがって、フラグを PackageManager.GET_RECEIVERS に設定して、このメソッドを反射的に呼び出すことができます。
次に、apk によって androidmanifest に登録されたすべてのレシーバーが、Package クラス オブジェクトのレシーバー オブジェクトに配置されます。これは List であり
、 List のリフレクションとトラバーサルを通じて手動でホストに登録します。

コードは以下のように表示されます

 public static void registerPluginReceiver(Context context, File apkFile) {
        // 首先调用反射调用 PackageParser 类的 parsePackage方法,获取  Package类对象
        Object packageParser = RefInvoke.createObject("android.content.pm.PackageParser");
        Class[] p1 = {File.class, int.class};
        Object[] v1 = {apkFile, PackageManager.GET_RECEIVERS};
        Object packageObj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage", p1, v1);


        //从这个Package类对象反射获取她的 receiver 对象中。他是个List<Receiver> 
        List receivers = (List) RefInvoke.getFieldObject(packageObj, "receivers");

        //遍历在宿主注册每一个receiver
        for (Object receiver : receivers) {

            // 解析出 每个receiver 的所有IntentFilter,因为一个receiver会注册很多个IntentFilter
            //所以先反射获取每个receiver 的所有IntentFilter,在这里 receiver 的所有IntentFilter 就是 
            //receiver类对象里面intents
            List<? extends IntentFilter> filters = (List<? extends IntentFilter>) RefInvoke.getFieldObject(
                    "android.content.pm.PackageParser$Component", receiver, "intents");


            try {

                for (IntentFilter intentFilter : filters) {
                    //获取每个 IntentFilter 类里面的 ActivityInfo 类对象  info对象
                    ActivityInfo receiverInfo = (ActivityInfo) RefInvoke.getFieldObject(receiver, "info");
                    //反射获取 ActivityInfo的类里面的name 对象 这个name 对象其实才是真正的BroadcastReceiver
                    BroadcastReceiver broadcastReceiver = (BroadcastReceiver) RefInvoke.createObject(receiverInfo.name);
                    //在宿主中手动注册广播
                    context.registerReceiver(broadcastReceiver, intentFilter);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

次のように使用します。

public class MyApplication  extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        //将插件和宿主dex合并
        LoadPluginDex.mergeHostAndPluginDex(this,"/sdcard/plugin.apk");
        //将插件和宿主reource合并
        PluginAndHostResource.init(this);
        //把插件中的静态接受者手动动态注册到宿主上来
        registerPluginReceiver(this, new File("/sdcard/plugin.apk"));


    }

    public  void registerPluginReceiver(Context context, File apkFile) {
        // 首先调用反射调用 PackageParser 类的 parsePackage方法,获取  Package类对象
        Object packageParser = RefInvoke.createObject("android.content.pm.PackageParser");
        Class[] p1 = {File.class, int.class};
        Object[] v1 = {apkFile, PackageManager.GET_RECEIVERS};
        Object packageObj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage", p1, v1);


        //从这个Package类对象反射获取她的 receiver 对象中。他是个List<Receiver>
        List receivers = (List) RefInvoke.getFieldObject(packageObj, "receivers");

        //遍历在宿主注册每一个receiver
        for (Object receiver : receivers) {

            // 解析出 每个receiver 的所有IntentFilter,因为一个receiver会注册很多个IntentFilter
            //所以先反射获取每个receiver 的所有IntentFilter,在这里 receiver 的所有IntentFilter 就是
            //receiver类对象里面intents
            List<? extends IntentFilter> filters = (List<? extends IntentFilter>) RefInvoke.getFieldObject(
                    "android.content.pm.PackageParser$Component", receiver, "intents");


            try {

                for (IntentFilter intentFilter : filters) {
                    //获取每个 IntentFilter 类里面的 ActivityInfo 类对象  info对象
                    ActivityInfo receiverInfo = (ActivityInfo) RefInvoke.getFieldObject(receiver, "info");
                    //反射获取 ActivityInfo的类里面的name 对象 这个name 对象其实才是真正的BroadcastReceiver
                    BroadcastReceiver broadcastReceiver = (BroadcastReceiver) RefInvoke.createObject(receiverInfo.name);
                    //在宿主中手动注册广播
                    context.registerReceiver(broadcastReceiver, intentFilter);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

次に、ホストでブロードキャストを送信します

  Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("zjs", "onClick:plugin");
                Intent intent = new Intent();
                intent.setAction("com.plugin.zjs");
                sendBroadcast(intent);

            }
        });

プラグインの androidmanifest に静的レシーバーを登録する

 <receiver android:name=".PluginReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="com.plugin.zjs"/>
            </intent-filter>
        </receiver>

public class PluginReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("zjs", "onReceive: "+intent.getAction());

    }
}

このように、ホストはプラグインの PluginReceiver クラスをロードし、プラグインに登録されている androidmanifest の静的レシーバーを手動でホストに登録し、対応するブロードキャストがホストで送信されると、その PluginReceiver のプラグインはそれを受け取ることができます。
ここに画像の説明を挿入

上記のソリューションの欠点は、静的登録を動的登録に変更することです。これにより、静的の本質が失われます。動的に変更すると、登録前にコードを実行して実行する必要があるため、別の解決策があります。

ホストの androidmanifest に SubRecevier を静的に登録し、プラグインのすべてのアクションをホストの SubRecevier に設定することで、ブロードキャストを受信すると、SubRecevier が最初に受信され、その後、 SubRecevier は、このアクションに従って動的になります。プラグインの受信者を登録してから、プラグインの受信者がそれを受信できるように、SubRecevier でアクション ブロードキャストを送信します。つまり、SubRecevier は受信のみを担当します。すべてのアクション ブロードキャストを受信し、対応するアクションに従って対応するプラグイン ブロードキャストを登録し、対応するアクション ブロードキャストを送信し、対応するプラグイン ブロードキャストがそれを受信するようにします。このプログラムはここには書かれていません。興味がある場合は、自分で試してみてください。

コンテンツプロバイダー

データ プロバイダーは ContentProvider であり、ContentProvider のアドレスを証明するために URL を指定する必要があります. データ ユーザーである ContentResolver は、データを取得する ContentProvider を示すために URL を指定する必要があります. 2 つは匿名の共有メモリを介してデータを送信します. . . ContentResolver は ContentProvider にアクセスしてデータを取得しようとしており、このメモリ アドレスにデータを書き込むように ContentProvider に指示します。あるアドレスから別のアドレスへのコピーは非常に効率的です。

ContentProvider も androidmanifest に登録する必要があるため、ContentProvider プラグインの実装は実際には Receiver と似ています。つまり、
プラグイン androidmanifest ですべての ContentProvider を取得してから、手動でホストに登録します。


まず、プラグイン androidmanifest ですべての ContentProvider を取得しましょう

受信機の手前では、PMS プロセスが PackageParser クラスを介して apk データを解析すると言われ

PackageParser クラスにメソッドがあります。public void Package parsePackage(File apkFile , int flags)
このメソッドには 2 つのパラメーターがあり、1 つは apk ファイル、もう 1 つはフィルター タグです。
つまり、このメソッドを呼び出すと、apk の情報をフィルターで除外し、情報を取得し、Package クラス オブジェクトを返します。
次に、このときに渡されるフラグは PackageManager.GET_PROVIDERS です。プラグインの androidmanifest にあるすべての ContentProvider を
Package のプロバイダー リストに入れることができます。
次に、リフレクションでこのリストプロバイダーを取得します


    public static List<ProviderInfo> parseProviders(File apkFile) throws Exception {

        //获取PackageParser对象实例
        Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
        Object packageParser = packageParserClass.newInstance();

        // 调用PackageParser 类里面的 parsePackage方法,传入flag  =  PackageManager.GET_PROVIDERS
        // 把插件的 androidmanifest中的所有的ContentProvider放入到Package package类对象里面的 providers  list 中 。
        Class[] p1 = {File.class, int.class};
        Object[] v1 = {apkFile, PackageManager.GET_PROVIDERS};
        Object packageobj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage",p1, v1);

        //  反射 获取到这个Package类对象里面的 providers 对象 他是个 List providers
        List providers = (List) RefInvoke.getFieldObject(packageobj, "providers");



        // 后面就是 调用PackageParser 类里面的 generateProviderInfo 方法, 把每一个provider 转换成ProviderInfo

        //准备generateProviderInfo方法所需要的参数
        Class<?> packageParser$ProviderClass = Class.forName("android.content.pm.PackageParser$Provider");
        Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
        Object defaultUserState = packageUserStateClass.newInstance();
        int userId = (Integer) RefInvoke.invokeStaticMethod("android.os.UserHandle", "getCallingUserId");
        Class[] p2 = {packageParser$ProviderClass, int.class, packageUserStateClass, int.class};

        //最终存储的list 返回值
        List<ProviderInfo> ret = new ArrayList<>();

        // 调用PackageParser 类里面的 generateProviderInfo 方法, 把每一个provider 转换成ProviderInfo,并保存起来
        for (Object provider : providers) {
            Object[] v2 = {provider, 0, defaultUserState, userId};
            ProviderInfo info = (ProviderInfo) RefInvoke.invokeInstanceMethod(packageParser, "generateProviderInfo",p2, v2);
            ret.add(info);
        }

        return ret;
    }

プラグイン androidmanifest ですべての ContentProviders を取得し、それらをホストに手動で登録してインストールできます。
実際には、リフレクションを呼び出して、ActivityThread クラスの installContentProviders メソッドを呼び出します。


  public  void installPluginContentProviders(Context context, File apkFile) {

        try {
            //先获取插件androidmanifest中的所有的ContentProvider
            List<ProviderInfo> providerInfos = parseProviders(apkFile);
            Log.d("zjs", "providerInfos " + providerInfos.toString());

            for (ProviderInfo providerInfo : providerInfos) {
                providerInfo.applicationInfo.packageName = context.getPackageName();
            }

            //获取ActivityThread实例对象
            Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");

            Class[] p1 = {Context.class, List.class};
            Object[] v1 = {context, providerInfos};

            //反射调用ActivityThread类里面的 installContentProviders方法
            RefInvoke.invokeInstanceMethod(currentActivityThread, "installContentProviders", p1, v1);
        }catch ( Exception e){
            Log.d("zjs", "installPluginContentProviders: ",e);
        }

    }

では、プラグインの ContentProviders をホストにインストールするのはいつですか?

プラグインの ContentProvider は、ホストだけでなく、他のアプリケーションでも使用される可能性があると思われます.プラグインの ContentProvider がホスト アプリにインストールされていない場合、サードパーティ アプリこの ContentProvider を呼び出します, それはそれほど悪くないので,
プラグインの ContentProvider をインストールするプロセスが早ければ早いほど良い. 実際, 最も速いのはホストのアプリケーション プロセスを開始することです, その後、インストール ロジックを実行します. . ホスト自体の ContentProvider、つまり ActivityThread が
installContentProviders メソッドを実行します. アプリ プロセスが開始されるとすぐに、アプリケーションの onCreate 関数より前に実行されますが、アプリケーションの attachBaseContent メソッドより少し遅れて実行されます.
つまり、アプリケーションのAttachBaseContent》ActivityThreadは、アプリケーションのinstallContentProviders》onCreateを実行します。

attachBaseContent メソッドでインストールすることを選択できます。

最後に次のように使用します。

public class MyApplication  extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);


        //将插件和宿主dex合并
        LoadPluginDex.mergeHostAndPluginDex(this,"/sdcard/plugin.apk");
        //将插件和宿主reource合并
        PluginAndHostResource.init(this);

        //安装插件ContentProviders
        installPluginContentProviders(this, new File("/sdcard/plugin.apk"));



    }

    public  void installPluginContentProviders(Context context, File apkFile) {

        try {
            //先获取插件androidmanifest中的所有的ContentProvider
            List<ProviderInfo> providerInfos = parseProviders(apkFile);
            Log.d("zjs", "providerInfos " + providerInfos.toString());

            for (ProviderInfo providerInfo : providerInfos) {
                providerInfo.applicationInfo.packageName = context.getPackageName();
            }

            //获取ActivityThread实例对象
            Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");

            Class[] p1 = {Context.class, List.class};
            Object[] v1 = {context, providerInfos};

            //反射调用ActivityThread类里面的 installContentProviders方法
            RefInvoke.invokeInstanceMethod(currentActivityThread, "installContentProviders", p1, v1);
        }catch ( Exception e){
            Log.d("zjs", "installPluginContentProviders: ",e);
        }

    }

    public static List<ProviderInfo> parseProviders(File apkFile) throws Exception {

        //获取PackageParser对象实例
        Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
        Object packageParser = packageParserClass.newInstance();

        // 调用PackageParser 类里面的 parsePackage方法,传入flag  =  PackageManager.GET_PROVIDERS
        // 把插件的 androidmanifest中的所有的ContentProvider放入到Package package类对象里面的 providers  list 中 。
        Class[] p1 = {File.class, int.class};
        Object[] v1 = {apkFile, PackageManager.GET_PROVIDERS};
        Object packageobj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage",p1, v1);

        //  反射 获取到这个Package类对象里面的 providers 对象 他是个 List providers
        List providers = (List) RefInvoke.getFieldObject(packageobj, "providers");



        // 后面就是 调用PackageParser 类里面的 generateProviderInfo 方法, 把每一个provider 转换成ProviderInfo

        //准备generateProviderInfo方法所需要的参数
        Class<?> packageParser$ProviderClass = Class.forName("android.content.pm.PackageParser$Provider");
        Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
        Object defaultUserState = packageUserStateClass.newInstance();
        int userId = (Integer) RefInvoke.invokeStaticMethod("android.os.UserHandle", "getCallingUserId");
        Class[] p2 = {packageParser$ProviderClass, int.class, packageUserStateClass, int.class};

        //最终存储的list 返回值
        List<ProviderInfo> ret = new ArrayList<>();

        // 调用PackageParser 类里面的 generateProviderInfo 方法, 把每一个provider 转换成ProviderInfo,并保存起来
        for (Object provider : providers) {
            Object[] v2 = {provider, 0, defaultUserState, userId};
            ProviderInfo info = (ProviderInfo) RefInvoke.invokeInstanceMethod(packageParser, "generateProviderInfo",p2, v2);
            ret.add(info);
        }

        return ret;
    }


}

ホストで:

  Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("zjs", "onClick:plugin");

                // 根据插件ContentProviders的URI,插入数据
                ContentResolver resolver =  getContentResolver();
                ContentValues values = new ContentValues();
                Uri uri = Uri.parse("content://com.example.plugin.zjs");
                resolver.insert(uri,values);
            }
        });

プラグイン

 <provider
            android:authorities="com.example.plugin.zjs"
            android:name=".PluginProvider"/>

次のように表示されます。
ここに画像の説明を挿入

Receiver に似た別のソリューションがあり、これもホストに SubContentProvider を作成し、プラグインのすべての ContentProvider のすべての URI をホスト SubContentProvider に配置し、サード パーティがホストの SubContentProvider を呼び出します
。 The uri is distributed to the ContentProvider in the対応するプラグイン. このソリューションはここには示されていません. 興味がある場合は、自分で実装できます.

おすすめ

転載: blog.csdn.net/weixin_43836998/article/details/125656350