Comprehensive analysis of android plug-in

Plug-in learning preparation knowledge

classloader class loading mechanism

Binder,AIDL,IPC

The difference between plug-in and componentization

Component-based development is to divide an app into multiple modules, and each module is a component. During the development process, we can make these components depend on each other or debug components separately, but when the final release is made, these components will be merged into an apk Release, and the plug-in is divided into one host and multiple plug-in apk. The high cost of the plug-in is to adapt to the android version. The source code implementation of each android version is different. When each new version comes out, you have to read the source code and then modify it. This source code is adapted.

Loading classes from outside the plugin

We know that loading a class depends on the classloader, so if we want to load the plug-in class in the host, there are two options

The first method is to obtain the classloader of each plug-in, then use the class loader of the plug-in to load the class of the plug-in, and then obtain the information of the class through reflection

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

        }

    }

The second method:
Merge the Element【】element in the dexpathlist of the plug-in classloader and the Element【】element in the dexpathlist of the host classLoader into a new Element【】element and
replace the host classLoader with this new Element【】element Element [] element in the dexpathlist
so that the host can directly use the host's classLoader to load any class of the plug-in.
Of course, you can replace the Element [] element in the dexpathlist of the plug-in classLoader with this new Element [] element,
so that you can directly use the plug-in classLoader to load any class of the plug-in in the plug-in.


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

    }

Use as follows:

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

This operation also has disadvantages.
When the plug-in and the host refer to different versions of the same library, it may cause program errors and special handling is required to avoid it: For example, in the above
figure
insert image description here
, the version of AppCompat of the host and the plug-in is different. The classes in the system are loaded by the PathClassLoader of the system, so the host must be loaded first, and due to the existence of the parent delegation mechanism, the classes that have already been loaded will not be loaded repeatedly, resulting in the AppCompat classes in the plug-in. Then when calling the difference code between v1.0 and v2.0, there may be problems.
In addition, when there are too many plug-ins, the size of the host's dexElements array will increase.

**

Workflow of the four components

The following source code analysis is based on android 7.0
**

Introduction

The working process of the four major components is actually the process communication process between the four major components and AMS.
Information about the four major components of each process will be registered in AMS.
Every time it is turned on, PMS will reinstall each application, and then read out the androidmanifest file of each application, and provide it to AMS for registration.

So how does AMS obtain the information of the four major components of each application?

Before looking at the workflow of the four major components

First of all, we must understand that the most important thread of a process is the main thread ActivityThread. This ActivityTherad
has two very important classes inside, one is called H class, which is a Handle class. The other is called ApplicationThread class, which is a binder, representing the Binder of the current process.
Their role and relationship is that ApplicationThread is responsible for receiving various messages from ams, and then uses the H class to send corresponding messages and then its own handlemessage to receive corresponding messages so as to use the corresponding method of ActivityThread.

Activity startup process

There are two types of start activity, one is the root activity start, and the launcher process clicks the desktop icon to start the process and start the first activity. All other cases are normal activities. The start activity we analyze below is a root activity

How an app process is started.

The visual effect is that we click an icon on the desktop and start the process.

Each of our app processes androidmanifest will mark the homepage activity

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

every time the system starts up. Pms will install the apk application every time, and the process will read the information in the androidmanifest of each app. Then pass this information to the desktop process launcher. The launcher will display the app icon and text based on this information, and mark the package information and the first activity for this.

When the user clicks on the icon.
The process after that is. The launcher process communicates with the ams process and the ams communicates with the application process.
Basically, the launcher tells ams which process to start to start that activity, and ams starts which process. And tell the application which page to start, and then the application will start which page.
insert image description here

Server workflow

There are two kinds of Server workflows,
one is context.startServer(intent)
and the other is context.bindSever(intent)
insert image description here

broadcast

The broadcast is the receiver, who takes the ID card Intentfiler to register with AMS
and then sends a broadcast corresponding to the identity to AMS. AMS finds the corresponding receiver and then sends the corresponding broadcast to the process, and finally the receiver receives the information.

The following is about dynamic registration. Static registration means that PMS obtains information from AndroidMasnifest to register with AMS every time it is installed.
insert image description here

Contentprovider

The essence of Contentprovider is that data is stored in the SQLite database
. Different data has different data tables. Contentprovider just encapsulates SQLite.

If you want to operate Contentprovider, you have to use ContentResolver. ContentResolver needs to specify a uri to indicate which table of Contentprovider it wants to manipulate.

insert image description here

After reading the loading process of the four major components, let's continue to look at resources

The loading process of resource resources, how to obtain plug-in resources and realize plug-in skinning

Proxy mode

Agents are divided into static agents and dynamic agents:

Static proxy , for example

A class Class1 has a method doSomething()

Then we want to make a proxy class for Class1 at this time, we first implement an interface ClassInterface, the interface method is doSomething()
and then let Class1 implement this interface,
then we create a proxy class, Class2, to implement ClassInterface as well , and rewrite doSomething, the most important thing is to have
the class of Class1 in Class2, and then call the doSomething of Class1 in the doSomething method of Class2, so that the doSomething of Class2 is realized to realize the doSomething of Class1, which is to realize the class 2 to replace class 1

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

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

Why do you need to embed such a proxy? In fact, the advantage is that you can add the logic you need in class2 without affecting the original function of class1.

Dynamic proxy :

The purpose of dynamic proxy is also to create a proxy class for a class to add the required logic while keeping the logic of the original class unchanged.

The dynamic class relies on
the newProxyInstance method of a Proxy class, and its declaration is as follows:

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

The newProxyInstance method of the Proxy class can create a proxy object for each interface type object.
For example, class class1 implements a ClassInterface interface, then we can use the
newProxyInstance method of the Proxy class to create a proxy object for this class1

The first parameter is the classloader of the target object, and
the second parameter is the interface class type of the target object. We can use reflection to obtain the interface class type. The
third parameter is a class object that implements the InvocationHandler interface. We pass it The constructor
injects the target object.

Specific examples are as follows:

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

So when creating a custom InvocationHandler class, you must use the invoke method, method.invoke(target, objects). Only in this way can the original logical operation be restored.

After understanding the dynamic proxy, let's learn how to hook a certain class object of the system. Hook is to create a proxy object for a certain class object of the system, and then use reflection to replace the original object with the proxy object. Next, let's create a proxy object for the binder object of AMS in the application process, and use this proxy object to replace the binder object in the source code.

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

How to find the right hook point and the principle of finding the hook point

Try to find static variables or singleton objects
Try to find public methods and objects

The simplest apk plug-in implementation

Through the previous study, we know how to load all the classes of the plug-in in the host, and obtain all the resources of the plug-in, but there is one most critical thing, which is the 4 major components of the plug-in. If only the 4 major components Ordinary classes, of course we have learned and loaded them, but the four major components are not just ordinary classes, they need to be declared on anroidmainfest, and then deal with the system, if not declared, the system will not recognize them of.
So there is the simplest apk plug-in implementation, that is, to declare the four major components of all plug-ins on the host androidmainfest, so that
starting the four major components of the plug-in in the host is nothing like opening the four major components of your own host. The difference, but this has actually lost the meaning of the plug-in. If there are thousands of plug-ins and thousands of components, they must be declared on a host androidmainfest, so there is a placeholder, and one solution to tens of millions of ideas.
The following explains the plug-in implementation of the four major components

Activity plug-in implementation

Let me talk about the idea of ​​​​the implementation
first. The first thing to achieve is to enable the activity of the plug-in in the host.

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

And if you write this in the host, you will definitely report an error, because you did not declare this com.example.plugin.MainActivity in the host’s androidmainfest, because AMS does not know the activity of the plugin, so the first step is to do it in the
host The androidmainfest adds a puppet SubActivity.

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

Then when the host calls startActivity(pluginActivity); through Hook, tell AMS to start SubActivity, because AMS knows this SubActivity, and then start SubActivity after AMS notification, we Hook again, and start our pluginActivity.
And the above said that a SubActivity should deal with tens of millions of activities in the plug-in, that is, no matter which activity we need to open the plug-in in the host, we will deceive AMS to start the SubActivity. The key is to put the activity data to be opened into the SubActivity and save it. , then when the AMS notification starts the SubActivity and comes back,
we take it out from the SubActivity and actually start the realActivity

Well, when the above idea comes out, we will start to realize it.

The first is to start the activity of the plug-in in the host

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

Declare a puppet SubActivity in the host

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

Tell AMS to start SubActivity through Hook deception, and store the activity Intent information originally to be started in SubActivity

We have learned the workflow of activity before, and know that the proxy object of AMS in the application process is AMP, that is, the process tells AMS what to do through AMP, so we can Hook AMP, that is, according to what we learned earlier

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

In the last step, when AMS tells the application process to start the SubActivity, start the readActivity in the SubActivity instead.

From the source code of the previous activity workflow, we can know
that when AMS tells the application process to start the SubActivity, it will pass through the Handle class H in the ActivityThread of the application process. This H will send a message called LAUNCHER_ACTIVITY = 100, and then the dipathchMessage in Handle H () method, through the source code of this method, we can know that
we can set a proxy CallbackProxy for this Handle H, and let our CallbackProxy handle the 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;
        }
    }

In the end it is

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

To use is to start the plug-in activity in the host:

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

The effect is as follows:
insert image description here

Activity resource problem

Through our hook, we can already start the activity of the plug-in in the host. The activity is started, but the interface of the activity is not up. We have two solutions for the plug-in of resources. One is to combine the resources of the plug-in with the resources of the host
. Merge an allResource, but there is a disadvantage here that if a resource id of the plug-in is the same as a resource id of the host, there will be no resource id of the plug-in in allResource. The solution is to use appt to modify the resource id of the plug-in prefix.

Another solution is to separate the resources of the plug-in from the resources of the host, so there are several key points to note

Once the resource of the plug-in is obtained, should it be obtained in the host or the plug-in itself obtains its own resouces? In fact, it is better to use reflection in the plug-in to obtain its own resource. If you use reflection in the host to obtain the pluginResource of the plug-in, The code in the plugin has to refer to the pluginResource object in the host to obtain resources.

2. We said earlier that we merge the dex of the plug-in into the host, and then according to the parental delegation mechanism, the loaded classes will not be loaded again, and the dex of the host is prior to the dex of the plug-in, that is, everything is based on the host. Master, then for the host, the MainActivity of the plug-in is actually an ordinary class, it does not have any special

in the 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. Pay attention to whether both the host and the plug-in have system resources or the resources of the referenced third library will conflict because of the same naming

For example. If the MainActivity of your plugin inherits AppCompatActivity, this AppCompatActivity references the third-party android x library in both the host and the plugin. There is an id R.id.deco_content_parent in the view loading process in AppCompatActivity. When compiling the plugin apk. Let's say its id is 0x7f07004d .
And when the host's apk is compiled, its id is 0x7f07004e. Then, since the conetxt in the plug-in is the host, when the plug-in wants to find the id 0x7f07004d, it finds that only 0x7f07004e cannot be found, so it will report an error.

So the solution idea is that we just need to change the idea to, for the host, the MainActivity class of the plug-in is actually an ordinary class inside the host. It has nothing special, but only executes a corresponding MainActivity class in the host. method, then
we reflect in the plug-in MainActivity class to obtain our own pluginResource, in MainActivity, create a Context, set the pluginResource to this Context through reflection, and obtain the view from this Context

Subsequent MainActivity class takes resources from this context.

code show as below:

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

    }

}

The context we created is a ContextThemeWrapper, and the imported package is ContextThemeWrapper under android x
insert image description here

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

life cycle

Finally, you will consider that AMS starts SubActiviy and we replace it with RealActivity. Will the life cycle be affected? In fact, it will not. We know through the above activity source code process

Both AMS and the application process will maintain a mActivitys, which is an ArrayMap<IBinder,ActivityClientRecord>

According to this key, AMS sends a message to the ActivityClientRecord corresponding to the key
. The application process tells AMS what to do with the ActivityClientRecord corresponding to the key based on this key.

At first, we hooked to deceive AMS and start subactivity, then AMS mActivitys, one of its token keys corresponds to subactivity.

Then when AMS tells the application process to start subactivity according to this key.
The process of receiving this message by the application process is as follows.

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

In performlaunchActivity method.
mActivitys.put(r.token, r)
saves the current ActivityClientRescord and token of the application process.

And what we hook is LAUNCH ACTIVITY = 100 "handleLaunchActivity". In this step, we change the component of the Intent of ActivityClientRescord from the original subactivity to the RealActivity we want. At the step of performlaunchActivity, mActivitys.put(r.token, r)
stores RealActivity for this token.
Therefore, the mActivities of the application process are different from the same token key in the mActivities of AMS.
The RealActivity corresponding to the application process, and the subactivity corresponding to the AMS.

The process is as follows:
When the application process needs to call the onpause method of an activity, the process will run to Instrumentation and use the binder to tell AMS to call onpause for
the activity corresponding to the token. At this time, the activity is RealActivity, and after AMS receives the message According to this token, it thinks that I am calling onpause of subactivity. After the application process receives the message, it thinks that it is calling onpause of RealActivity according to this token.

Server plug-in

In fact, the implementation of sercer has many similarities with activity, but it should be noted that when the server is turned on, startServer needs to be called multiple times, and only one instance will be opened instead of multiple.

Post the code directly here:

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

The above code is actually similar to the activity. It hooks the two key points back and forth, but only modifies the logic of the two proxy classes.

The final use is to start the service in the host

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

            }
        });

The effect is as follows
insert image description here

Broadcast plug-in

The broadcast process is divided into registering a receiver and sending a broadcast.

There are static registration and dynamic registration for registering a receiver. Since the dynamic registration of a receiver does not need to be registered on the androidmanifest, the dynamic registration of a receiver class in the plug-in does not require any processing. It is an ordinary class, as long as the host can Just load the plugin class.

There are two implementations for the accepting class of the static registration of the plug-in:

1 Since PMS can read the static receiver on the androidmanifest of the host app, we can also manually control the PMS to read the static receiver on the androidmanifest of the plug-in through reflection and then dynamically register it on the host.

First understand the preparation knowledge

The PMS process parses the apk data through the class PackageParser, and
there is a method in the class PackageParser. public void Package parsePackage(File apkFile , int flags)
This method has two parameters, one is the apk file, and the other is the filter tag,
that is, calling this method, you can filter out the information of the apk, encapsulate the information and return a Package class object

So we can call this method reflectively, with flags set to PackageManager.GET_RECEIVERS.
Then all the receivers registered by the apk on the androidmanifest will be put into the receiver object in the Package class object. It is a List
, and we manually register it in the host through reflection and traversal of the List.

code show as below

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

        }
    }

Use as follows:

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

        }
    }
}

Then send the broadcast in the host

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

            }
        });

Register the static receiver on the plugin's 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());

    }
}

In this way, the host can load the PluginReceiver class of the plug-in, and manually register the registered static receiver of the androidmanifest in the plug-in to the host, then when the corresponding broadcast is sent in the host, the PluginReceiver of the plug-in can receive it.
insert image description here

The disadvantage of the above solution is that we change the static to dynamic registration, which loses the essence of static, and changing to dynamic means that the code needs to run and run before registering, so there is another solution.

Statically register a SubRecevier in the androidmanifest of the host, and then set all the actions of the plug-in into the SubRecevier of the host. In this way, when a broadcast is received, the SubRecevier is the first to be received, and then the SubRecevier will be dynamic according to this action. Register the Recevier of the plug-in, and then send an action broadcast in SubRecevier, so that the Recevier of the plug-in can receive it, which means that SubRecevier is only responsible for receiving all action broadcasts, and then register the corresponding plug-in broadcast according to the corresponding action. Then send the corresponding action broadcast, and let the corresponding plug-in broadcast receive it. This program is not written here, if you are interested, you can try it yourself

ContentProvider

The data provider is the ContentProvider, which needs to specify a url to prove the address of the ContentProvider. The data user, the ContentResolver, needs to specify the url to indicate which ContentProvider to get the data. The two transmit data through anonymous shared memory. . ContentResolver wants to go to ContentProvider to get data, tell ContentProvider to write the data at this memory address, ContentProvider puts the data at the specified memory address, and then ContentResolver can go directly to get it, this is anonymous shared memory, data does not need Copying from one address to another is very efficient.

ContentProvider also needs to be registered in the androidmanifest, so the implementation of the ContentProvider plug-in is actually similar to Receiver, that is, to obtain
all the ContentProviders in the plug-in androidmanifest, and then manually register in the host.

First, let's get
all the ContentProviders in the plug-in androidmanifest

In front of the receiver, it is said that
the PMS process parses the apk data through the class PackageParser, and
there is a method in the class PackageParser. public void Package parsePackage(File apkFile , int flags)
This method has two parameters, one is the apk file, and the other is the filter tag,
that is, calling this method, you can filter out the information of the apk, encapsulate the information and return a Package class object.
Then the flags passed in this time is PackageManager.GET_PROVIDERS, you can put all the ContentProviders in the androidmanifest of the plug-in
into the providers list in the Package.
Then we get this List providers in reflection


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

We can get all the ContentProviders in the plug-in androidmanifest, and then manually register and install them in the host.
In fact, call reflection to call the installContentProviders method in the ActivityThread class.


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

    }

So when is the time to install the ContentProviders of the plug-in in the host?

You think that the ContentProvider of your plug-in may not only be used by the host, but may also be used by other applications. If the ContentProvider in the plug-in has not been installed in the host App, the third-party App will call this ContentProvider, that is Not too bad,
so the sooner the process of installing the plug-in ContentProvider, the better. In fact, the fastest is to start the application process of the host, and then we will run our installation logic. The ContentProvider of the host itself, that is, the
ActivityThread executes the installContentProviders method. Execute immediately when the App process starts, earlier than Application's onCreate function, but slightly later than Application's attachBaseContent method.
That is, AttachBaseContent of Application 》ActivityThread executes installContentProviders 》onCreate of Application

We can choose to install in the attachBaseContent method.

Finally use as follows:

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


}

In the host:

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

plug-in

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

Displayed as follows:
insert image description here

There is another solution, similar to Receiver, which is also to create a SubContentProvider in the host, put all the URIs of all the ContentProviders of the plug-in, the host
SubContentProvider, and the third party calls the SubContentProvider of the host, and then the SubContentProvider according to the corresponding The uri is distributed to the ContentProvider in the corresponding plug-in. This solution is not shown here. If you are interested, you can implement it yourself.

Guess you like

Origin blog.csdn.net/weixin_43836998/article/details/125656350