Analyse complète du plug-in Android

Connaissances de préparation à l'apprentissage par plug-in

mécanisme de chargement de classe classloader

Classeur,AIDL,IPC

La différence entre plug-in et composantisation

Le développement basé sur les composants consiste à diviser une application en plusieurs modules, et chaque module est un composant. Au cours du processus de développement, nous pouvons faire en sorte que ces composants dépendent les uns des autres ou déboguer des composants séparément, mais lorsque la version finale sera publiée, ces composants seront être fusionné dans une version apk, et le plug-in est divisé en un hôte et plusieurs apk de plug-in. Le coût élevé du plug-in est de s'adapter à la version Android. L'implémentation du code source de chaque version Android est différente A chaque nouvelle version, il faut lire le code source puis le modifier, ce code source est adapté.

Charger des classes depuis l'extérieur du plugin

Nous savons que le chargement d'une classe dépend du chargeur de classe, donc si nous voulons charger la classe du plug-in dans l'hôte, il y a deux options

La première méthode consiste à obtenir le chargeur de classe de chaque plug-in, puis à utiliser le chargeur de classe du plug-in pour charger la classe du plug-in, puis à obtenir les informations de la classe par réflexion

    //获取每个插件的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();

        }

    }

La deuxième méthode :
fusionner l'élément Element【】dans la dexpathlist du plug-in classloader et l'élément Element】dans la dexpathlist du classLoader hôte dans un nouvel élément Element【】et remplacer le classLoader hôte par ce nouvel Element【
】 element Element [] élément dans la dexpathlist
afin que l'hôte puisse directement utiliser le classLoader de l'hôte pour charger n'importe quelle classe du plug-in.
Bien sûr, vous pouvez remplacer l'élément Element [] dans la dexpathlist du plug-in classLoader par ce nouvel élément Element [],
de sorte que vous puissiez directement utiliser le plug-in classLoader pour charger n'importe quelle classe du plug-in dans le brancher.


    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();
        }

    }

Utilisez comme suit :

    @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();
        }
    }

Cette opération présente également des inconvénients.
Lorsque le plug-in et l'hôte font référence à des versions différentes de la même bibliothèque, cela peut entraîner des erreurs de programme et une manipulation spéciale est nécessaire pour l'éviter : par exemple, dans la figure ci-dessus, la
version
insérez la description de l'image ici
d'AppCompat de l'hôte et le plug-in sont différents. Les classes du système sont chargées par le PathClassLoader du système, donc l'hôte doit être chargé en premier, et en raison de l'existence du mécanisme de délégation parent, les classes qui ont déjà été chargées ne sera pas chargé à plusieurs reprises, ce qui entraînera les classes AppCompat dans le plug-in. Ensuite, lors de l'appel du code de différence entre v1.0 et v2.0, il peut y avoir des problèmes.
De plus, lorsqu'il y a trop de plug-ins, la taille du tableau dexElements de l'hôte augmente.

**

Flux de travail des quatre composants

L'analyse de code source suivante est basée sur Android 7.0
**

Introduction

Le processus de travail des quatre composants principaux est en fait le processus de communication du processus entre les quatre composants principaux et l'AMS.
Les informations sur les quatre principales composantes de chaque processus seront enregistrées dans l'AMS.
Chaque fois qu'il est activé, PMS réinstalle chaque application, puis lit le fichier androidmanifest de chaque application et le fournit à AMS pour enregistrement.

Alors, comment AMS obtient-il les informations des quatre principaux composants de chaque application ?

Avant d'examiner le flux de travail des quatre composants principaux

Tout d'abord, nous devons comprendre que le thread le plus important d'un processus est le thread principal ActivityThread.Cet ActivityTherad
contient deux classes très importantes, l'une s'appelle la classe H, qui est une classe Handle. L'autre s'appelle la classe ApplicationThread, qui est un classeur, représentant le classeur du processus en cours.
Leur rôle et leur relation sont que ApplicationThread est responsable de la réception de divers messages d'ams, puis utilise la classe H pour envoyer les messages correspondants, puis son propre handlemessage pour recevoir les messages correspondants afin d'utiliser la méthode correspondante de ActivityThread.

Processus de démarrage de l'activité

Il existe deux types d'activité de démarrage, l'un est le démarrage de l'activité racine et le processus de lancement clique sur l'icône du bureau pour démarrer le processus et démarrer la première activité. Tous les autres cas sont des activités normales. L'activité de démarrage que nous analysons ci-dessous est une activité racine

Comment un processus d'application est démarré.

L'effet visuel est que nous cliquons sur une icône sur le bureau et démarrons le processus.

Chacun de nos processus d'application androidmanifest marquera l'activité de la page d'accueil

  <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>

à chaque démarrage du système. Pms installera l'application apk à chaque fois, et le processus lira les informations dans le androidmanifest de chaque application. Transmettez ensuite ces informations au lanceur de processus de bureau. Le lanceur affichera l'icône de l'application et le texte en fonction de ces informations, et marquera les informations sur le package et la première activité pour cela.

Lorsque l'utilisateur clique sur l'icône.
Le processus après cela est. Le processus de lancement communique avec le processus ams et l'ams communique avec le processus d'application.
Fondamentalement, le lanceur indique à ams quel processus démarrer pour démarrer cette activité, et ams démarre quel processus. Et dites à l'application quelle page démarrer, puis l'application démarrera quelle page.
insérez la description de l'image ici

Flux de travail du serveur

Il existe deux types de workflows de serveur,
l'un est context.startServer(intent)
et l'autre est context.bindSever(intent)
insérez la description de l'image ici

diffuser

La diffusion est le récepteur, qui s'enregistre avec la carte d'identité Intentfiler à AMS
, puis envoie une diffusion correspondant à l'identité à AMS. AMS trouve le récepteur correspondant, puis envoie la diffusion correspondante au processus, et enfin le récepteur reçoit les informations.

Ce qui suit concerne l'enregistrement dynamique. L'enregistrement statique signifie que PMS obtient des informations d'AndroidMasnifest pour s'enregistrer auprès d'AMS à chaque installation.
insérez la description de l'image ici

Fournisseur de contenu

L'essence de Contentprovider est que les données sont stockées dans la base de données SQLite
. Différentes données ont différentes tables de données. Contentprovider encapsule simplement SQLite.

Si vous voulez faire fonctionner Contentprovider, vous devez utiliser ContentResolver. ContentResolver doit spécifier un uri pour indiquer quelle table de Contentprovider il veut manipuler.

insérez la description de l'image ici

Après avoir lu le processus de chargement des quatre composants principaux, continuons à regarder les ressources

Le processus de chargement des ressources de ressources, comment obtenir des ressources de plug-in et réaliser le skinning de plug-in

Mode mandataire

Les agents sont divisés en agents statiques et agents dynamiques :

Proxy statique , par exemple

Une classe Class1 a une méthode doSomething()

Ensuite, nous voulons créer une classe proxy pour Class1 à ce moment, nous implémentons d'abord une interface ClassInterface, la méthode d'interface est doSomething(),
puis nous laissons Class1 implémenter cette interface,
puis nous créons une classe proxy, Class2, pour implémenter ClassInterface également , et réécrire doSomething, le plus important est d'avoir
la classe de Class1 dans Class2, puis d'appeler le doSomething de Class1 dans la méthode doSomething de Class2, de sorte que le doSomething de Class2 soit réalisé pour réaliser le doSomething de Class1, qui est pour réaliser la classe 2 pour remplacer la classe 1

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

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

Pourquoi avez-vous besoin d'intégrer un tel proxy ? En fait, l'avantage est que vous pouvez ajouter la logique dont vous avez besoin dans class2 sans affecter la fonction d'origine de class1.

Proxy dynamique :

Le but du proxy dynamique est également de créer une classe proxy pour une classe afin d'ajouter la logique requise tout en conservant la logique de la classe d'origine inchangée.

La classe dynamique s'appuie sur
la méthode newProxyInstance d'une classe Proxy, et sa déclaration est la suivante :

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

La méthode newProxyInstance de la classe Proxy peut créer un objet proxy pour chaque objet de type interface.
Par exemple, la classe class1 implémente une interface ClassInterface, nous pouvons alors utiliser la
méthode newProxyInstance de la classe Proxy pour créer un objet proxy pour cette classe1

Le premier paramètre est le chargeur de classe de l'objet cible et
le deuxième paramètre est le type de classe d'interface de l'objet cible. Nous pouvons utiliser la réflexion pour obtenir le type de classe d'interface. Le
troisième paramètre est un objet de classe qui implémente l'interface InvocationHandler. Nous pass it Le constructeur
injecte l'objet cible.

Des exemples spécifiques sont les suivants :

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

Ainsi, lors de la création d'une classe InvocationHandler personnalisée, vous devez utiliser la méthode d'invocation, method.invoke(target, objects). Ce n'est qu'ainsi que l'opération logique d'origine peut être restaurée.

Après avoir compris le proxy dynamique, apprenons à accrocher un certain objet de classe du système. Le crochet consiste à créer un objet proxy pour un certain objet de classe du système, puis à utiliser la réflexion pour remplacer l'objet d'origine par l'objet proxy. Ensuite, créons un objet proxy pour l'objet binder d'AMS dans le processus d'application, et utilisons cet objet proxy pour remplacer l'objet binder dans le code source.

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

Comment trouver le bon point de crochet et le principe de trouver le point de crochet

Essayez de trouver des variables statiques ou des objets singleton
Essayez de trouver des méthodes et des objets publics

L'implémentation de plug-in apk la plus simple

Grâce à l'étude précédente, nous savons comment charger toutes les classes du plug-in dans l'hôte, et obtenir toutes les ressources du plug-in, mais il y a une chose la plus critique, qui est les 4 composants majeurs du plug -in. Si seulement les 4 composants majeurs des classes ordinaires, bien sûr, nous les avons appris et chargés, mais les quatre composants majeurs ne sont pas seulement des classes ordinaires, ils doivent être déclarés sur anroidmainfest, puis traiter avec le système, s'ils ne sont pas déclarés , le système ne les reconnaîtra pas.
Il existe donc l'implémentation de plug-in apk la plus simple, c'est-à-dire déclarer les quatre composants principaux de tous les plug-ins sur l'hôte androidmainfest, de sorte que le démarrage des quatre composants principaux
du plug-in dans l'hôte n'a rien à voir avec l'ouverture du quatre composants principaux de votre propre hôte.La différence, mais cela a en fait perdu le sens du plug-in. S'il y a des milliers de plug-ins et des milliers de composants, ils doivent être déclarés sur un hôte androidmainfest, il y a donc un espace réservé et une solution à des dizaines de millions d'idées.
Ce qui suit explique la mise en œuvre du plug-in des quatre principaux composants

Implémentation du plug-in d'activité

Permettez-moi de parler d'abord de l'idée de la mise en œuvre.La
première chose à réaliser est d'activer l'activité du plug-in dans l'hôte.

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

Et si vous écrivez ceci dans l'hôte, vous signalerez certainement une erreur, car vous n'avez pas déclaré ce com.example.plugin.MainActivity dans l'androidmainfest de l'hôte, car AMS ne connaît pas l'activité du plugin, donc la première étape est pour le faire dans l'
hôte L'androidmainfest ajoute une sous-activité de marionnette.

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

Ensuite, lorsque l'hôte appelle startActivity (pluginActivity); via Hook, dites à AMS de démarrer SubActivity, car AMS connaît cette SubActivity, puis démarrez SubActivity après la notification AMS, nous crochetons à nouveau et démarrons notre pluginActivity.
Et ce qui précède a dit qu'une sous-activité devrait traiter des dizaines de millions d'activités dans le plug-in, c'est-à-dire que quelle que soit l'activité dont nous avons besoin pour ouvrir le plug-in dans l'hôte, nous tromperons AMS pour démarrer la sous-activité. la clé est de mettre les données d'activité à ouvrir dans la sous-activité et de les enregistrer. , puis lorsque la notification AMS démarre la sous-activité et revient,
nous la retirons de la sous-activité et démarrons réellement la realActivity

Eh bien, lorsque l'idée ci-dessus sortira, nous commencerons à la réaliser.

La première consiste à démarrer l'activité du plug-in dans l'hôte

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

Déclarer une sous-activité de marionnette dans l'hôte

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

Dites à AMS de démarrer la sous-activité via la déception de crochet et de stocker les informations d'intention d'activité à l'origine à démarrer dans la sous-activité

Nous avons déjà appris le flux de travail de l'activité et savons que l'objet proxy d'AMS dans le processus de candidature est AMP, c'est-à-dire que le processus indique à AMS quoi faire via AMP, afin que nous puissions accrocher AMP, c'est-à-dire selon ce que nous appris plus tôt

    //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);
        }
    }

Dans la dernière étape, lorsque AMS indique au processus d'application de démarrer la sous-activité, démarrez plutôt la readActivity dans la sous-activité.

D'après le code source du workflow d'activité précédent, nous pouvons savoir
que lorsque AMS indique au processus d'application de démarrer la sous-activité, il passera par la classe Handle H dans l'ActivityThread du processus d'application. Ce H enverra un message appelé LAUNCHER_ACTIVITY = 100, puis la méthode dipathchMessage dans la méthode Handle H (), via le code source de cette méthode, nous pouvons savoir que
nous pouvons définir un proxy CallbackProxy pour ce Handle H, et laisser notre CallbackProxy gérer le message

 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;
        }
    }

Au final c'est

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();
    }
}

L'utilisation consiste à démarrer l'activité du plug-in dans l'hôte :

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

L'effet est le suivant :
insérez la description de l'image ici

Problème de ressource d'activité

Grâce à notre crochet, nous pouvons déjà démarrer l'activité du plug-in dans l'hôte. L'activité est démarrée, mais l'interface de l'activité n'est pas en place. Nous avons deux solutions pour le plug-in des ressources. L'une consiste à combiner les ressources du plug-in avec les ressources de l'hôte
Fusionner un allResource, mais il y a un inconvénient ici que si un identifiant de ressource du plug-in est le même qu'un identifiant de ressource de l'hôte, il n'y aura pas de ressource id du plug-in dans allResource. La solution consiste à utiliser appt pour modifier l'ID de ressource du préfixe du plug-in.

Une autre solution consiste à séparer les ressources du plug-in des ressources de l'hôte, il y a donc plusieurs points clés à noter

Une fois la ressource du plug-in obtenue, doit-elle être obtenue dans l'hôte ou le plug-in lui-même obtient-il ses propres ressources ? En fait, il est préférable d'utiliser la réflexion dans le plug-in pour obtenir sa propre ressource. Si vous utilisez la réflexion dans l'hôte pour obtenir le pluginResource du plug-in. Le code du plugin doit faire référence à l'objet pluginResource dans l'hôte pour obtenir des ressources.

2. Nous avons dit précédemment que nous fusionnions le dex du plug-in dans l'hôte, puis selon le mécanisme de délégation parentale, les classes chargées ne seront pas rechargées, et le dex de l'hôte est antérieur au dex du plug-in, c'est-à-dire que tout est basé sur l'hôte. Maître, puis pour l'hôte, la MainActivity du plug-in est en fait une classe ordinaire, elle n'a pas de classe spéciale

dans le plugin

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. Faites attention à savoir si l'hôte et le plug-in ont des ressources système ou si les ressources de la troisième bibliothèque référencée seront en conflit en raison de la même dénomination

Par exemple. Si la MainActivity de votre plugin hérite de AppCompatActivity, cette AppCompatActivity fait référence à la bibliothèque Android x tierce à la fois dans l'hôte et dans le plugin. Il existe un identifiant R.id.deco_content_parent dans le processus de chargement de la vue dans AppCompatActivity. Lors de la compilation du plugin apk. Disons que son identifiant est 0x7f07004d .
Et lorsque l'apk de l'hôte est compilé, son identifiant est 0x7f07004e. Ensuite, puisque le conetxt dans le plug-in est l'hôte, lorsque le plug-in veut trouver l'id 0x7f07004d, il trouve que seul 0x7f07004e est introuvable, il signale donc une erreur.

Donc, l'idée de solution est que nous avons juste besoin de changer l'idée pour que, pour l'hôte, la classe MainActivity du plug-in soit en fait une classe ordinaire à l'intérieur de l'hôte. Elle n'a rien de spécial, mais exécute uniquement une classe MainActivity correspondante dans le host., puis
nous réfléchissons dans la classe MainActivity du plug-in pour obtenir notre propre pluginResource, dans MainActivity, créons un contexte, définissons le pluginResource sur ce contexte par réflexion et obtenons la vue à partir de ce contexte

La classe MainActivity suivante prend les ressources de ce contexte.

code afficher comme ci-dessous:

Dans le plugin :

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();
        }

    }

}

Le contexte que nous avons créé est un ContextThemeWrapper, et le package importé est ContextThemeWrapper sous android x
insérez la description de l'image ici

<?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>

cycle de vie

Enfin, vous considérerez qu'AMS démarre SubActiviy et nous le remplaçons par RealActivity. Le cycle de vie sera-t-il affecté ? En fait, ce ne sera pas le cas. Nous le savons grâce au processus de code source de l'activité ci-dessus.

AMS et le processus d'application maintiendront un mActivitys, qui est un ArrayMap<IBinder,ActivityClientRecord>

En fonction de cette clé, AMS envoie un message à l'ActivityClientRecord correspondant à la clé
Le processus d'application indique à AMS ce qu'il faut faire avec l'ActivityClientRecord correspondant à la clé en fonction de cette clé.

Au début, nous avons accroché pour tromper AMS et démarrer la sous-activité, puis AMS mActivitys, une de ses clés de jeton correspond à la sous-activité.

Ensuite, lorsque AMS indique au processus d'application de démarrer la sous-activité en fonction de cette clé.
Le processus de réception de ce message par le processus d'application est le suivant.

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

Dans la méthode performlaunchActivity.
mActivitys.put(r.token, r)
enregistre l'ActivityClientRescord et le jeton actuels du processus d'application.

Et ce que nous accrochons est LAUNCH ACTIVITY = 100 "handleLaunchActivity".Dans cette étape, nous changeons le composant de l'intention de ActivityClientRescord de la sous-activité d'origine à la RealActivity que nous voulons. À l'étape de performlaunchActivity, mActivitys.put(r.token, r)
stocke RealActivity pour ce jeton.
Par conséquent, les mActivities du processus de demande sont différentes de la même clé de jeton dans les mActivities d'AMS.
La RealActivity correspondant au processus de candidature et la sous-activité correspondant à l'AMS.

Le processus est le suivant :
lorsque le processus d'application doit appeler la méthode onpause d'une activité, le processus s'exécute sur Instrumentation et utilise le classeur pour indiquer à AMS d'appeler onpause pour l'activité correspondant
au jeton. À ce stade, l'activité est RealActivity, et après qu'AMS a reçu le message Selon ce jeton, il pense que j'appelle onpause of subactivity.Après que le processus d'application a reçu le message, il pense qu'il appelle onpause of RealActivity selon ce jeton.

Plugin serveur

En fait, l'implémentation de sercer présente de nombreuses similitudes avec l'activité, mais il convient de noter que lorsque le serveur est activé, startServer doit être appelé plusieurs fois et une seule instance sera ouverte au lieu de plusieurs.

Postez le code directement ici :

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;
        }
    }
}

Le code ci-dessus est en fait similaire à l'activité. Il relie les deux points clés dans les deux sens, mais ne modifie que la logique des deux classes proxy.

L'utilisation finale est de démarrer le service dans l'hôte

 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);

            }
        });

L'effet est le suivant
insérez la description de l'image ici

Module de diffusion

Le processus de diffusion comprend l'enregistrement d'un récepteur et l'envoi d'une diffusion.

Il existe un enregistrement statique et un enregistrement dynamique pour enregistrer un récepteur. Étant donné que l'enregistrement dynamique d'un récepteur n'a pas besoin d'être enregistré sur le manifeste Android, l'enregistrement dynamique d'une classe de récepteur dans le plug-in ne nécessite aucun traitement. C'est un classe ordinaire, tant que l'hôte peut simplement charger la classe du plugin.

Il existe deux implémentations pour la classe d'acceptation de l'enregistrement statique du plug-in :

1 Étant donné que PMS peut lire le récepteur statique sur le manifeste android de l'application hôte, nous pouvons également contrôler manuellement le PMS pour lire le récepteur statique sur le manifeste android du plug-in par réflexion, puis l'enregistrer dynamiquement sur l'hôte.

Comprendre d'abord les connaissances de préparation

Le processus PMS analyse les données apk via la classe PackageParser, et
il existe une méthode dans la classe PackageParser. public void Package parsePackage(File apkFile , int flags)
Cette méthode a deux paramètres, l'un est le fichier apk et l'autre est la balise de filtre,
c'est-à-dire qu'en appelant cette méthode, vous pouvez filtrer les informations de l'apk, encapsuler le informations et renvoyer un objet de classe Package

Nous pouvons donc appeler cette méthode de manière réflexive, avec des drapeaux définis sur PackageManager.GET_RECEIVERS.
Ensuite, tous les récepteurs enregistrés par l'apk sur androidmanifest seront placés dans l'objet récepteur dans l'objet de classe Package. C'est une List
, et nous l'enregistrons manuellement dans l'hôte par réflexion et traversée de la List .

spectacle de code comme ci-dessous

 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();
            }

        }
    }

Utilisez comme suit :

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();
            }

        }
    }
}

Ensuite, envoyez la diffusion dans l'hôte

  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);

            }
        });

Enregistrez le récepteur statique sur le androidmanifest du plugin

 <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());

    }
}

De cette façon, l'hôte peut charger la classe PluginReceiver du plug-in, et enregistrer manuellement le récepteur statique enregistré de l'androidmanifest dans le plug-in de l'hôte, puis lorsque la diffusion correspondante est envoyée dans l'hôte, le PluginReceiver de le plug-in peut le recevoir.
insérez la description de l'image ici

L'inconvénient de la solution ci-dessus est que nous changeons l'enregistrement statique en dynamique, ce qui perd l'essence de statique, et le passage à dynamique signifie que le code doit s'exécuter et s'exécuter avant l'enregistrement, il existe donc une autre solution.

Enregistrez statiquement un SubRecevier dans le manifeste android de l'hôte, puis définissez toutes les actions du plug-in dans le SubRecevier de l'hôte. De cette façon, lorsqu'une diffusion est reçue, le SubRecevier est le premier à être reçu, puis le SubRecevier sera dynamique en fonction de cette action. Enregistrez le Receiver du plug-in, puis envoyez une action broadcast dans SubRecevier, afin que le Receiver du plug-in puisse la recevoir, ce qui signifie que SubRecevier est uniquement responsable de la réception toutes les diffusions d'action, puis enregistrez la diffusion de plug-in correspondante en fonction de l'action correspondante.Ensuite, envoyez la diffusion d'action correspondante et laissez la diffusion de plug-in correspondante la recevoir. Ce programme n'est pas écrit ici, si vous êtes intéressé, vous pouvez l'essayer vous-même

Fournisseur de contenu

Le fournisseur de données est le ContentProvider, qui doit spécifier une URL pour prouver l'adresse du ContentProvider. L'utilisateur de données, le ContentResolver, doit spécifier l'URL pour indiquer quel ContentProvider doit obtenir les données. Les deux transmettent les données via une mémoire partagée anonyme . . ContentResolver veut aller à ContentProvider pour obtenir des données, dire à ContentProvider d'écrire les données à cette adresse mémoire, ContentProvider place les données à l'adresse mémoire spécifiée, puis ContentResolver peut aller directement pour les obtenir, c'est une mémoire partagée anonyme, les données ne le font pas besoin Copier d'une adresse à une autre est très efficace.

ContentProvider doit également être enregistré dans le androidmanifest, de sorte que l'implémentation du plug-in ContentProvider est en fait similaire à Receiver, c'est-à-dire pour obtenir
tous les ContentProviders dans le plug-in androidmanifest, puis s'enregistrer manuellement dans l'hôte.

Tout d'abord, récupérons
tous les ContentProviders dans le plug-in androidmanifest

Devant le récepteur, on dit que
le processus PMS analyse les données apk via la classe PackageParser, et
il existe une méthode dans la classe PackageParser. public void Package parsePackage(File apkFile , int flags)
Cette méthode a deux paramètres, l'un est le fichier apk et l'autre est la balise de filtre,
c'est-à-dire qu'en appelant cette méthode, vous pouvez filtrer les informations de l'apk, encapsuler le informations et renvoient un objet de classe Package.
Ensuite, les drapeaux passés à ce moment-là sont PackageManager.GET_PROVIDERS, vous pouvez mettre tous les ContentProviders dans le androidmanifest du plug-in
dans la liste des fournisseurs du package.
Ensuite, nous obtenons cette liste de fournisseurs en réflexion


    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;
    }

Nous pouvons obtenir tous les ContentProviders dans le plug-in androidmanifest, puis les enregistrer et les installer manuellement dans l'hôte.
En fait, appelez la réflexion pour appeler la méthode installContentProviders dans la classe ActivityThread.


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

    }

Alors, quand est-il temps d'installer les ContentProviders du plug-in dans l'hébergeur ?

Vous pensez que le ContentProvider de votre plug-in peut non seulement être utilisé par l'hôte, mais également par d'autres applications. Si le ContentProvider du plug-in n'a pas été installé dans l'application hôte, l'application tierce appellera ce ContentProvider, ce n'est pas trop mal,
donc plus tôt le processus d'installation du plug-in ContentProvider, mieux c'est. En fait, le plus rapide est de démarrer le processus d'application de l'hôte, puis nous exécuterons notre logique d'installation . Le ContentProvider de l'hôte lui-même, c'est-à-dire l'
ActivityThread, exécute la méthode installContentProviders. Exécuter immédiatement lorsque le processus App démarre, avant la fonction onCreate de l'Application, mais légèrement plus tard que la méthode attachBaseContent de l'Application.
Autrement dit, AttachBaseContent of Application 》ActivityThread exécute installContentProviders 》onCreate of Application

Nous pouvons choisir d'installer dans la méthode attachBaseContent.

Utilisez enfin comme suit :

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;
    }


}

Dans l'hébergeur :

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

brancher

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

Affiché comme suit :
insérez la description de l'image ici

Il existe une autre solution, similaire à Receiver, qui consiste également à créer un SubContentProvider dans l'hôte, mettre tous les URI de tous les ContentProviders du plug-in, l'hôte SubContentProvider, et le tiers appelle le SubContentProvider
de l'hôte, et puis le SubContentProvider selon le correspondant L'uri est distribué au ContentProvider dans le plug-in correspondant. Cette solution n'est pas présentée ici. Si vous êtes intéressé, vous pouvez l'implémenter vous-même.

Je suppose que tu aimes

Origine blog.csdn.net/weixin_43836998/article/details/125656350
conseillé
Classement