admin 管理员组

文章数量: 887007

android 插件化全面解析

插件学习 准备知识

classloader 类加载机制

Binder,AIDL,IPC

插件化与组件化区别

组件化开发就是将一个app分成多个模块,每个模块都是一个个组件,开发的过程中我们可以让这些组件相互依赖或者单独调试组件,但是最终发布的时候是将这些组件并成一个apk发布,而插件话 是分为一个宿主 和多个插件apk ,插件话成本高就是 适配 android版本,每个android版本的源码实现都不同,每个新版本出来,你就得去看源码然后 对这个源码做适配。

加载插件外部中的类

我们知道了加载一个类就是靠的 classloader,那么如果我们想在宿主中加载插件的类,就有两种方案

第一种 获取每个插件的classloader 然后利用插件的classloader 去加载插件的类,然后反射获取类的信息

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

第二种:
把插件classloader的dexpathlist里的 Element【】element 和 宿主的 classLoader的dexpathlist里的 Element【】element 合并一个新的Element【】element
然后用这个新的Element【】element 替换掉 宿主的 classLoader的的dexpathlist里 Element【】element
这样在宿主中就可以直接用 宿主的 classLoader去加载 插件的任何一个类。
当然你可以把 用这个新的Element【】element 替换掉 插件的 classLoader的的dexpathlist里 Element【】element
这样在插件中就可以直接用 插件的 classLoader去加载 插件的任何一个类。

public void mergeHostAndPluginDex(Context context,String pluginPath){if (TextUtils.isEmpty(pluginPath)) {throw new IllegalArgumentException("插件路径不能拿为空!");}try {Class<?> clazz  = Class.forName("dalvik.system.BaseDexClassLoader");Field pathListField = clazz.getDeclaredField("pathList");pathListField.setAccessible(true);Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");Field dexElements = dexPathListClass.getDeclaredField("dexElements");dexElements.setAccessible(true);// 1.获取宿主的ClassLoader中的 dexPathList 在从 dexPathList 获取 dexElementsClassLoader pathClassLoader = context.getClassLoader();Object dexPathList = pathListField.get(pathClassLoader);Object[] hostElements = (Object[]) dexElements.get(dexPathList);// 2.获取插件的 dexElementsDexClassLoader 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 换成 allElementsdexElements.set(dexPathList, allElements);} catch (Exception e) {Log.d("zjs", "e" , e);e.printStackTrace();}}

使用如下:

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

这种操作也有缺点,
当插件和宿主 引用的 同一个库的不同版本时,可能会导致程序出错,需要进行特殊处理规避:
比如

以上图为例,宿主和插件的 AppCompat 的版本不同,由于这个包中的类是系统的 PathClassLoader 进行加载的,那么一定是先加载了宿主的,而由于双亲委托机制的存在,已经加载过的类不会重复加载,导致插件中的 AppCompat 的类就不会加载,那么调用到 v1.0 与 v2.0 的差异代码时,就可能出现问题。
此外,当插件数量过多时,会造成宿主的 dexElements 数组体积增大。

**

四大组件的工作流程

以下源码分析都是在android 7.0 身上
**

简介

四大组件的工作过程其实就是,四大组件与AMS的进程通信过程。
每个进程的四大组件信息 都会在AMS 中进行注册。
每次开机的时候,PMS都会去重新再次安装每一个应用,然后把每个应用的 androidmanifest文件读取出来,提供给AMS 注册。

那么AMS是如何拿到每个应用的四大组件信息的呢

在看四大组件工作流程之前

先要懂得,一个进程最重要的线程就是主线程 ActivityThread,这个ActivityTherad,
他的内部有两个很关键的类,一个叫H类,是个Handle 类。另外一个叫ApplicationThread 类,它是个binder,代表当前进程的Binder。
他们的作用和关系是 , ApplicationThread 是负责收到ams的各种消息,然后在 利用 H类 发送 对应的消息 然后自身的handlemessage 去接受对应的消息从而掉用ActivityThread对应的方法。

Activity 启动流程

start activity 启动有两种,一个是根activity启动,也是由launcher 进程点击 桌面图标,启动了进程,并且启动了首activity。 其他情况的都是 普通 activity 。下面我们分析的start activity 一个根activity

一个app进程是如何启动的。

视觉效果是我们在桌面上点击 一个图标 然后就开启了这个进程。

我们的 每个app 进程 androidmanifest 中 都会 标明了首页activity

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

每次系统开机启动的的时候。pms每次都会去安装 apk应用 ,过程 会去读取 每个app的 androidmanifest 中的 信息。然后把这些信息给 桌面进程 launcher。launcher 就会根据这个信息 显示 app图标和文字,并且为此标记 包信息和首activity。

当用户去点击这个图标的时候。
之后的流程就是 。launcher 进程 与 ams 的进程通信 以及 ams 与 应用 进程通信。
大体就是 launcher告诉 ams 要启动哪个进程启动那个actvity,ams 就启动哪个进程。并且告诉应用要启动哪个页面,然后应用就启动哪个页面。

Server 工作流程

Server 工作流程有两种
一种是 context.startServer(intent)
一种 context.bindSever(intent)

广播

广播就是 接受者receiver,拿着 身份证Intentfiler去注册到AMS
然后发送 对应身份的 广播 到AMS ,AMS 搜到对应的 接受者 然后 在发送对应的广播给进程,最后receiver接收到信息。

下面讲的是动态注册,静态注册是每次安装的时候PMS从 AndroidMasnifest中获取信息去注册到AMS

Contentprovider

Contentprovider的本质就是数据存储在SQLite数据库中
,不同的数据不同的数据表,Contentprovider只是对SQLite进行了一套封装。

想要操作Contentprovider就是得利用ContentResolver,ContentResolver 需要指定一个uri,表明它要去操控哪个Contentprovider的哪一张表

看完四大组件加载流程,我们继续看 资源

resource 资源的加载流程 ,如何 获取 插件 的资源 以及实现 插件化换肤

代理模式

代理分为静态代理和动态代理:

静态代理,打个比方

一个 类 Class1 有个方法 doSomething()

然后我们这个时候想要去 为了 Class1 做了一个代理类,我们 先实现一个接口 ClassInterface,接口方法是 doSomething()
然后 让Class1 去实现这个接口 ,
接着我们 创建 一个代理类,Class2,也一样去实现ClassInterface,并且重写doSomething,最关键的就是 在Class2中 需要有
Class1的类,然后在 Class2的doSomething 方法中 调用 Class1 的doSomething,这样,就实现了 Class2 的doSomenthing 去实现了 Class1的 doSomething ,就是实现了 class 2 去替代 class 1

public class Class2 implements ClassInterface {
Class1 class1 = new Class1 ();
@Override
public void doSomething () {//在调用class1.doSomething()之前做一些你需要的逻辑class1.doSomething() ;//在调用class1.doSomething()之后做一些你需要的逻辑}}

为什么需要在嵌一套这样的代理呢,其实好处就是,你可以在 class2中,在不影响class1的原来的功能下,添加你需要的逻辑。

动态代理

动态代理的目的也是为一个类去创建一个代理类去 保有原来类逻辑不变的情况下 添加需要的逻辑

动态类靠的是一个
Proxy类的newProxyInstance 方法,它的声明如下所示 :

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

Proxy类的newProxyInstance 方法能够为每个接口类型的对象 创造一个代理对象
比如说类 class1 它实现了一个ClassInterface的接口,那么我们就可以 为这个 class1 去利用
Proxy类的newProxyInstance 方法 创造个代理对象

第一个参数是目标对象的classloader,
第二个参数是目的对象的接口类类型,我们可以用反射来获得接口类类型
第三个参数是 是一个 实现了InvocationHandler接口的类对象,我们通过它的构造函数
把目标对象 注入。

具体实例如下:

ClassInterface class1 = new Class1();
ClassInterface classlProxy = (Classllnterface) Proxy .newProxylnstance(
class1. getClass () .getClassLoader (), 
class1.getClass () .getlnterfaces (),
new InvocationHandlerForTest(class1));
public class InvocationHandlerForTest implements InvocationHandler { //这个target 其实就是传进来原来对象
private Object target ;public InvocationHandlerForTest (Object target) {this.target = target;}@Override
public Object invoke (Object o, Method method, Object [] objects) throws. Throwable {
//method.invoke(target , objects)这个方法就是还原目标对象的原来的方法
Object obj =method.invoke(target , objects) ; 
return obj ;}}

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

所以说在创建 自定义 InvocationHandler类的时候一定要在 invoke方法 ,method.invoke(target , objects)。这样才能还原原来的逻辑操作 。

懂了动态代理,我们来学习下如何hook 系统的某个类对象,hook 就是 为系统的某个类对象制造代理对象,然后利用反射 拿代理对象 替代 原来的对象 。下面我们就来为 AMS在 应用进程的binder对象 ,创造一个代理对象,拿这个 代理对象 替换掉 源码里的binder 对象 。

public class HookAMP {//android 8.0public  static  void hookAMP(Context context){try {//先获取ActivityManager类里面的静态变量 IActivityManagerSingletonClass activityManagerClass = Class.forName("android.app.ActivityManager");Field fieldActivityManagerSingleton  = activityManagerClass.getDeclaredField("IActivityManagerSingleton");fieldActivityManagerSingleton.setAccessible(true);Object IActivityManagerSingleton = fieldActivityManagerSingleton.get(null);//从这个 IActivityManagerSingleton获取他的mInstance对象。这个对象就是AMPClass 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替换为 proxymInstanceField.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 对象就是AMPClass 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替换为 proxymInstanceField.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;}@Overridepublic 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);}}
}

如何找合适的Hook点,找到Hook点的原则

尽量找静态变量或者单例对象
尽量找public方法和对象

最简单的apk插件化实现

通过前面的学习,我们知道了如何在宿主中加载插件在所有的类,以及获取插件的都的所有的资源,但是有个最关键的,就是插件的4大组件,如果单单把4大组件当中普通的类看待,那我们当然已经学会并做到了加载他们,但是四大组建并不是单单普通的类,他是需要在anroidmainfest上声明,然后跟系统打交道的,如果不声明,系统是不认识他们的。
所以就有了最简单的apk的插件话实现,也就是把所有插件的四大组件都声明在宿主 androidmainfest上,这样
在宿主中开始插件的四大组件,就跟自家人宿主开启四大组件没什么区别,但是这种其实已经失去了插件的意义。如果有成千上万的 插件,有成千上万个 组件,都要声明在一个宿主androidmainfest上,所以就有一个叫做 占位,以1个 应对千万个 思想方案。
下面讲解四大组件的插件化实现

Activity插件化实现

这里直接先说下实现的思路
首先要实现的目的就是,在宿主中 开启 插件的activity

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

而你这么写在宿主中绝对会报错,因为你没有在宿主的androidmainfest中声明这个com.example.plugin.MainActivity,因为AMS就不认识插件的这个acticity,
那么要做的第一步就是在 宿主中的androidmainfest添加一个傀儡SubActivity.

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

然后 在宿主调用 startActivity(pluginActivity);的时候,通过Hook 告诉AMS 启动的是 SubActivity,因为 AMS 认识这个SubActivity,然后在AMS 通知启动 SubActivity,我们再次Hook,启动的是 我们的 pluginActivity。
而上面的说 一个 SubActivity 应对 插件千万个activity ,就是 我们 不管需要在宿主中开启插件哪个acticity,我们都是欺骗AMS 启动的是SubActivity,关键是吧真正要开启的活动数据 放入SubActivity 中保存起来 ,那么再 AMS 通知启动 SubActivity回来的时候,
我们再从SubActivity取出真正要开启realActivity

好上面的思想方案出来了我们就开始实现。

首先是 在宿主中开启 插件的activity

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

在宿主中声明一个傀儡SubActivity

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

通过Hook 欺骗告诉AMS 要启动的是 SubActivity,并把原本要启动的activity Intent 信息存在SubActivity中

前面我们学了 activity的工作流程,知道了 AMS的在应用进程的代理对象是AMP,也就是进程是通过AMP告诉AMS要做什么事情的,所以我们就可以HookAMP,也就是根据前面学到的

    //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 对象就是AMPClass 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替换为 proxymInstanceField.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;}@Overridepublic 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;}}//从参数中获得实际上要启动activityIntent realActivity = (Intent) objects[index];//从这里判断这个activity 的包名是不是 不是 宿主的,不是才需要 创建个傀儡subActivity,然后把真正要启动到 realActivity放入subActivity中//最好偷梁换柱,把原本的 变量realActivity 换成 subActivityif (!("com.example.myapplication".equals(realActivity.getComponent().getPackageName()))) {Intent subActivity = new Intent();subActivity.setComponent(new ComponentName("com.example.myapplication", "com.example.myapplication.SubActivity"));subActivity.putExtra("plugin", realActivity);objects[index] = subActivity;}}}catch (Exception e){Log.d("zjs", "invoke: ",e);}//这里依旧是还源变量应该做的事情,return method.invoke(mBase,objects);}}

最后一步,在AMS 告诉应用进程启动SubActivity的时候,换成启动 SubActivity内中的readActivity

从前面activity 工作流程源码我们可以知道
当AMS 告诉应用进程启动SubActivity的时候会经过应用进程的ActivityThread 里面的Handle 类H ,这个H会发送一个消息叫做 LAUNCHER_ACTIVITY = 100的消息,然后由 Handle H 里面的 dipathchMessage()方法,通过这个方法的源码我们可以知道
我们可以对这个 Handle H 设置一个我们的代理 CallbackProxy,让我们这个 CallbackProxy 去处理消息

 public static  void hookHandleCallback(){try {//先获取ActivityThread类里面的静态变量 sCurrentActivityThreadClass 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要去启动的ActivityIntent raw = (Intent) intentField.get(obj);//我们对这个Activity进行判断 如果它里面存有插件活动,则证明这个Activity是个SubActivity//那么我们就需要 把这个activity 设置 realActivityIntent 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);}}@Overridepublic boolean handleMessage(@NonNull Message message) {final int LAUNCH_ACTIVITY = 100;switch (message.what){case LAUNCH_ACTIVITY:handleLauncherActivity(message);}//还原原本操作mH.handleMessage(message);return true;}}

最终就是

public class MyApplication  extends Application {@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);//将插件和宿主dex合并LoadPluginDex.mergeHostAndPluginDex(this,"/sdcard/plugin.apk")//告诉AMS启动的SubActivityHook.hookAMP(this);//回来启动的realActivityHook.hookHandleCallback();}
}

使用就是在宿主中开启插件的活动:

  Button button = findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic 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);}});

效果如下:

Activity资源问题

前面通过我们hook我们已经可以把在宿主中开启插件的activity了,activity是启动了,但是activity的界面没有起来,资源的插件化我们是有两种方案,
一种就是把插件的资源跟宿主的给合并一个allResource,但是这里有个缺点就是如果插件的某个资源id 跟宿主的 某个资源id 一样,allResource里面就不会有插件的这个资源id ,解决方案就是 利用appt去 修改插件的 资源id前缀。

还有一种方案就是在插件的resource 跟宿主的资源 分开出来,那么有几个关键注意点

一 获取插件的resource是要放在宿主中获取呢还是插件自己去获取自己的resouces,其实最好还是,在插件中 自己利用反射去获取自己的resource,如果在宿主中 去反射获取插件的pluginResource,插件中的代码还得去引用宿主里的pluginResource对象 来获取资源。

二 前面我们说了我们把插件的dex 合并到宿主中来,再根据双亲委派机制,加载过的类不会再加载,并且宿主的dex 是优先于插件的dex,也就是一切都是以宿主为主,那么对于宿主来说,插件的MainActivity其实就是一个普通的类,它没有任何特殊的

在插件中

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

三 注意下 宿主和插件双方的都有系统资源或者引用的第三库的资源是否会因为命名一致发生冲突

比如说。如果你插件的 MainActivity 继承是的 AppCompatActivity,这个AppCompatActivity在 宿主跟插件都是引用了第三方android x 库。AppCompatActivity 里面的view 加载流程内 有个 id R.id.deco_content_parent 。 在 插件apk 编译的时候。假如它的id 0x7f07004d 。
而在宿主的apk 编译的时候 它的id 0x7f07004e 。那么由于 插件中的conetxt 是宿主的,那么当插件要去找 这个 id 为0x7f07004d 的时候,发现找不到 只有0x7f07004e所以就会报错。

所以解决思想就是,我们只要把思想转变为 ,对于宿主来说,插件的MainActivity类 其实就是宿主内部的一个普通的类,它没有任何特殊的,只是在宿主中单单的在执行一个MainActivity类的对应方法而已,那么
我们在插件MainActivity类中自己反射自己获取自己的pluginResource,在MainActivity中,创建一个Context ,通过反射把pluginResource 设置到 这个Context中,从这个Context获取view

后续MainActivity这个类 拿资源我们都是从这个context 中拿。

代码如下:

插件中:

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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//在这个类里先获取插件的pluginResourcespreloadResource(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创造viewView view = LayoutInflater.from(pluginContext).inflate(R.layout.activity_main, null);setContentView(view);AppCompatButton button = view.findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Log.d("zjs", "plugin MainActivity button: ");}});Log.d("zjs", "plugin MainActivity onCreate: ");}@Overrideprotected void onPause() {super.onPause();Log.d("zjs", "plugin MainActivity onPause: ");}//验证插件中是否能获取宿主的资源@Overrideprotected void onResume() {super.onResume();Log.d("zjs", "plugin MainActivity onResume: ");}@Overrideprotected 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();}}}

我们这创造的这个context ,是个 ContextThemeWrapper ,导入的包是android x 下的 ContextThemeWrapper

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=""xmlns:app=""xmlns:tools=""android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><androidx.appcompat.widget.AppCompatButtonandroid: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.AppCompatTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintBottom_toTopOf="@id/button"android:text="@string/plugin_button"/></androidx.constraintlayout.widget.ConstraintLayout>

生命周期

最后你会考虑,AMS启动的是SubActiviy,被我们换成RealActivity,那么生命周期会不会有影响,其实是不会的,通过上面的activity源码流程我们知道

AMS跟应用进程内部都会维护一个mActivitys,它是个ArrayMap<IBinder,ActivityClientRecord>

AMS就是根据这个key 对应的,来给key对应的 哪个ActivityClientRecord发消息
应用进程就是根据这个key,告诉AMS 要对key对应的ActivityClientRecord做什么事情

最开始我们hook 欺骗AMS,启动subactivity ,那么 AMS的 mActivitys,它的其中一个 token key 对应的是 subactivity 。

那么 当AMS 根据这个key 告诉应用进程说要启动 subactivity 的时候 。
应用进程的收到这个消息的 流程如下 。

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

在 performlaunchActivity 方法中 。
mActivitys.put(r.token,r)
把对 应用进程的 当前的ActivityClientRescord跟token存起来。

而我们hook的是 LAUNCH ACTIVITY = 100》handleLaunchActivity 这个步骤,我们 把 ActivityClientRescord的Intent 的componet 从原本的 subactivity 跟换为我们要的 RealActivity。到了 performlaunchActivity 这一步的时候, mActivitys.put(r.token,r)
对这个token,存放的是 RealActivity。
所以 应用进程 的 mActivitys,它跟 AMS 的 mActivitys 中的 相同 token key 对应的是 不同的 。
应用进程对应的 RealActivity,AMS对应的subactivity。

流程如下 :
当应用进程的 需要调用某个activity 的onpause 方法,流程就会跑到Instrumentation 利用binder 告诉 AMS 要 对
这个token 对应的 activity 调用onpause ,这个时候这个 activity 是 RealActivity ,而在AMS 收到消息后,它根据这个token 认为 我调用的是 subactivity的onpause,应用进程收到消息后,根据这个token 认为 要调用的是 RealActivity的onpause。

Server插件化

sercer 的实现其实跟activity有很多相似的地方,但是要注意,server开启掉要startServer 多次调用,只会开启一个实例,而不是开启 多个。

这里直接贴出代码:

public class MyApplication  extends Application {@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);//将插件和宿主dex合并LoadPluginDex.mergeHostAndPluginDex(this,"/sdcard/plugin.apk");//将插件和宿主reource合并PluginAndHostResource.init(this);//告诉AMS启动的SubServiceHook.hookAMP(this);//回来启动的realServiceHook.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 对象就是AMPClass 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替换为 proxymInstanceField.set(gDefault,proxy);} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {Log.d("zjs", "hookAMP: ",e);e.printStackTrace();}}public static  void hookHandleCallback(){try {//先获取ActivityThread类里面的静态变量 sCurrentActivityThreadClass 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;}@Overridepublic 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其实是个CreateServiceDataObject obj = message.obj;//从CreateServiceData获取ServiceInfo  info  对象,info对象里面的name 就是要开启的server name,//我们只需要跟换这个name就可以了换成我们要的启动的realServiceServiceInfo 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);}}@Overridepublic boolean handleMessage(@NonNull Message message) {final int CREATE_SERVICE = 114;switch (message.what){case CREATE_SERVICE:handleCreateService(message);}//还原原本操作mH.handleMessage(message);return true;}}
}

上面的代码其实跟activity 差不多,都是对关键来回两个点进行hook,只是对2个代理类 的逻辑进行了修改而已

最后使用就是在宿主中 开启服务

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

效果如下

广播插件化

广播流程 分为 注册 一个接受者 和 发送 一个广播。

注册 一个接受者 有 静态注册和 动态 注册,由于动态注册一个接受者 不需要在androidmanifest上注册,所以插件中 动态注册 一个接设者类 不需要做任何 处理,他就是个普通的类 ,只要宿主能够加载插件类就可以了。

插件 静态注册 的接受类 我们的实现方案有两种:

1 既然PMS 能够读取宿主app的 androidmanifest上的 静态接受者,那么我们也可以通过反射 手动控制 PMS 读取插件的 androidmanifest上的 静态接受者 然后动态注册在 宿主身上。

首先先了解下准备知识

PMS 进程 是通过 类PackageParser 来解析apk 的数据的,
类PackageParser 里面有个方法。public void Package parsePackage(File apkFile ,int flags)
这个方法有两个参数 ,一个是apk文件,,一个是过滤标签
也就是调用这个 方法,就可以过滤出 apk 的信息 ,并把信息封装还会一个 Package类对象

所以我们可以反射调用这个方法,flags 设置为 PackageManager.GET_RECEIVERS 。
那么 就会把 apk 在 在androidmanifest上注册的所有receiver 放入到 Package类对象 里面的 receiver 对象中。他是个List
然后我们在 反射对 List遍历 手动注册在宿主中

代码如下

 public static void registerPluginReceiver(Context context, File apkFile) {// 首先调用反射调用 PackageParser 类的 parsePackage方法,获取  Package类对象Object packageParser = RefInvoke.createObject("android.content.pm.PackageParser");Class[] p1 = {File.class, int.class};Object[] v1 = {apkFile, PackageManager.GET_RECEIVERS};Object packageObj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage", p1, v1);//从这个Package类对象反射获取她的 receiver 对象中。他是个List<Receiver> List receivers = (List) RefInvoke.getFieldObject(packageObj, "receivers");//遍历在宿主注册每一个receiverfor (Object receiver : receivers) {// 解析出 每个receiver 的所有IntentFilter,因为一个receiver会注册很多个IntentFilter//所以先反射获取每个receiver 的所有IntentFilter,在这里 receiver 的所有IntentFilter 就是 //receiver类对象里面intentsList<? 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 对象其实才是真正的BroadcastReceiverBroadcastReceiver broadcastReceiver = (BroadcastReceiver) RefInvoke.createObject(receiverInfo.name);//在宿主中手动注册广播context.registerReceiver(broadcastReceiver, intentFilter);}} catch (Exception e) {e.printStackTrace();}}}

使用如下:

public class MyApplication  extends Application {@Overrideprotected 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");//遍历在宿主注册每一个receiverfor (Object receiver : receivers) {// 解析出 每个receiver 的所有IntentFilter,因为一个receiver会注册很多个IntentFilter//所以先反射获取每个receiver 的所有IntentFilter,在这里 receiver 的所有IntentFilter 就是//receiver类对象里面intentsList<? 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 对象其实才是真正的BroadcastReceiverBroadcastReceiver broadcastReceiver = (BroadcastReceiver) RefInvoke.createObject(receiverInfo.name);//在宿主中手动注册广播context.registerReceiver(broadcastReceiver, intentFilter);}} catch (Exception e) {e.printStackTrace();}}}
}

然后在宿主中发送广播

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

在插件的androidmanifest上注册静态接受者

 <receiver android:name=".PluginReceiver"android:exported="true"><intent-filter><action android:name="com.plugin.zjs"/></intent-filter></receiver>public class PluginReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {Log.d("zjs", "onReceive: "+intent.getAction());}
}

这样,宿主能加载插件的PluginReceiver类,也把插件中的androidmanifest的注册静态接受者手动注册到宿主中去,那么当在宿主中发送对应个广播时候,插件的PluginReceiver就可以接受到。

上面的方案的缺点就是,我们把静态的给改为动态注册,这就是失去了静态的本质了,改为动态也就是需要代码跑起来运行起来才会去注册,那么还有一种解决方案就是。

在宿主的androidmanifest 静态注册一个SubRecevier ,然后把插件所有的action 都设置进去这个宿主的SubRecevier,这样子,当收到广播的时候,第一个收到的是 SubRecevier,然后在SubRecevier根据这个action 去动态注册插件的 Recevier,然后再在SubRecevier 中 发送一条 action的广播,让插件的Recevier接收到,也就是相当于SubRecevier只是个负责收到所有的action的广播,然后再根据对应的action 注册对应插件广播,再 发送对应的action广播,让对应的插件广播收到。这个方案就不在这里写了,有兴趣可以自己去试试

ContentProvider

数据提供者 也就是 ContentProvider,它需指定一个url,证明这个 ContentProvider的地址,数据使用者ContentResolver ,想要获取数据需要指定 url表示要去哪个ContentProvider拿数据,二者 是通过匿名共享内存来传输数据的。ContentResolver想要去ContentProvider拿数据,告诉ContentProvider,把数据写在这个内存地址 上,ContentProvider就把数据放到指定的内存地址上,然后 ContentResolver就可以直接去拿了,这就是匿 名共享内存,数据不需要从一个地址复制到另一个地址,效率很高。

ContentProvider也是需要在androidmanifest注册,那么ContentProvider插件化的实现方案其实跟Receiver差不多,也就是获取插件
androidmanifest中的所有的ContentProvider,然后在宿主中手动注册。

首先我们先获取 获取插件
androidmanifest中的所有的ContentProvider

前面receiver 中说了
PMS 进程 是通过 类PackageParser 来解析apk 的数据的,
类PackageParser 里面有个方法。public void Package parsePackage(File apkFile ,int flags)
这个方法有两个参数 ,一个是apk文件,,一个是过滤标签
也就是调用这个 方法,就可以过滤出 apk 的信息 ,并把信息封装还会一个 Package类对象。
那么这次 传入的flags 是PackageManager.GET_PROVIDERS ,就可以 把插件的 androidmanifest中的所有的ContentProvider
放入到Package里面的 providers list 中 。
然后我们在反射 获取到这个 List providers

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 providersList providers = (List) RefInvoke.getFieldObject(packageobj, "providers");// 后面就是 调用PackageParser 类里面的 generateProviderInfo 方法, 把每一个provider 转换成ProviderInfo//准备generateProviderInfo方法所需要的参数Class<?> packageParser$ProviderClass = Class.forName("android.content.pm.PackageParser$Provider");Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");Object defaultUserState = packageUserStateClass.newInstance();int userId = (Integer) RefInvoke.invokeStaticMethod("android.os.UserHandle", "getCallingUserId");Class[] p2 = {packageParser$ProviderClass, int.class, packageUserStateClass, int.class};//最终存储的list 返回值List<ProviderInfo> ret = new ArrayList<>();// 调用PackageParser 类里面的 generateProviderInfo 方法, 把每一个provider 转换成ProviderInfo,并保存起来for (Object provider : providers) {Object[] v2 = {provider, 0, defaultUserState, userId};ProviderInfo info = (ProviderInfo) RefInvoke.invokeInstanceMethod(packageParser, "generateProviderInfo",p2, v2);ret.add(info);}return ret;}

我们能获取到插件androidmanifest中的所有的ContentProvider,然后在宿主中手动注册安装。
其实就调用反射调用ActivityThread类里面的 installContentProviders方法。

public  void installPluginContentProviders(Context context, File apkFile) {try {//先获取插件androidmanifest中的所有的ContentProviderList<ProviderInfo> providerInfos = parseProviders(apkFile);Log.d("zjs", "providerInfos " + providerInfos.toString());for (ProviderInfo providerInfo : providerInfos) {providerInfo.applicationInfo.packageName = context.getPackageName();}//获取ActivityThread实例对象Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");Class[] p1 = {Context.class, List.class};Object[] v1 = {context, providerInfos};//反射调用ActivityThread类里面的 installContentProviders方法RefInvoke.invokeInstanceMethod(currentActivityThread, "installContentProviders", p1, v1);}catch ( Exception e){Log.d("zjs", "installPluginContentProviders: ",e);}}

那什么时候时机去在宿主中去安装插件的的ContentProviders呢?

你想 你插件的ContentProvider 又不单单只有可能给 宿主中用,还有可能给其他应用进程用, 如果 插件中的 ContentProvider 还都没安装到宿主 App 中,第三方App 就来调用这个 ContentProvider,那是不是不太好,
所以安装插件 ContentProvider 的过程越早越好,其实最快也是 在 宿主的应用进程启动 ,才会去跑我们的安装逻辑,宿主自身的
ContentProvider,也就是 ActivityThread执行 installContentProviders 方法, 会在 App进程启动时立刻执行,比 Application的 onCreate函数还要早,但是略晚于 Application的attachBaseContent方法。
也就是 Application的attachBaseContent 》ActivityThread执行 installContentProviders 〉Application的 onCreate

我们可以选择在attachBaseContent方法中去安装。

最后使用如下:

public class MyApplication  extends Application {@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);//将插件和宿主dex合并LoadPluginDex.mergeHostAndPluginDex(this,"/sdcard/plugin.apk");//将插件和宿主reource合并PluginAndHostResource.init(this);//安装插件ContentProvidersinstallPluginContentProviders(this, new File("/sdcard/plugin.apk"));}public  void installPluginContentProviders(Context context, File apkFile) {try {//先获取插件androidmanifest中的所有的ContentProviderList<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 providersList providers = (List) RefInvoke.getFieldObject(packageobj, "providers");// 后面就是 调用PackageParser 类里面的 generateProviderInfo 方法, 把每一个provider 转换成ProviderInfo//准备generateProviderInfo方法所需要的参数Class<?> packageParser$ProviderClass = Class.forName("android.content.pm.PackageParser$Provider");Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");Object defaultUserState = packageUserStateClass.newInstance();int userId = (Integer) RefInvoke.invokeStaticMethod("android.os.UserHandle", "getCallingUserId");Class[] p2 = {packageParser$ProviderClass, int.class, packageUserStateClass, int.class};//最终存储的list 返回值List<ProviderInfo> ret = new ArrayList<>();// 调用PackageParser 类里面的 generateProviderInfo 方法, 把每一个provider 转换成ProviderInfo,并保存起来for (Object provider : providers) {Object[] v2 = {provider, 0, defaultUserState, userId};ProviderInfo info = (ProviderInfo) RefInvoke.invokeInstanceMethod(packageParser, "generateProviderInfo",p2, v2);ret.add(info);}return ret;}}

在宿主中:

  Button button = findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic 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);}});

插件中

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

显示如下:

还有一种方案就是类似Receiver一样,也是在宿主中弄个SubContentProvider,把插件的 所有ContentProvider的所有uri放在,
宿主SubContentProvider,第三者去调用都是去调用宿主的SubContentProvider,然后再由SubContentProvider根据对应的uri 分发给对应的插件中的ContentProvider,这个方案也不在这里展示了,有兴趣可自己去实现。

本文标签: android 插件化全面解析