最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

每日看点!插件化开发详解

来源:博客园

1:替换DexElements流程:

插件化原理:https://www.cnblogs.com/wnpp/p/16053088.html


(资料图片仅供参考)

插件生成apk,宿主通过反射机制和类加载器(传入插件apk),获取到插件的dexElements,并将dexElements合并到宿主的类加载器的dexElements,

这样插件所有的class都位于宿主的类加载器里面,达到宿主可以启动插件的目的。

2:启动插件普通类代码流程:

1)Plugin module:

public class Test {    public int add(int a, int b){        return a + b;    };}

编译生成plugin.apk,放到sdk目录下

2)Host module:

public class LoadUtil {    private static final String apkpath = "/sdcard/plugin.apk";    public static void loadClass(Context context) {        //反射流程        //1)获取class        //2)获取class中我们需要的那个属性Filed        //3)Field.get(实例化对象),得到属性对应的那个实例        //4)通过以上方法分别获取host的dexElements对象和plugin的dexElements        //两层:classLoader得到pathList实例,pathList实例得到DexPathList实例        //BaseDexClassLoader->pathList->DexPathList        try {            // 获取DexPathList的class            Class dexPathListClass = Class.forName("dalvik.system.DexPathList");            //获取DexPathList的dexElements属性            Field dexElementField = dexPathListClass.getDeclaredField("dexElements");            //将dexElements属性设置为public            dexElementField.setAccessible(true);            //获取BaseDexClassLoader的class            Class classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");            //获取pathList属性            Field pathListField = classLoaderClass.getDeclaredField("pathList");            pathListField.setAccessible(true);            //获取数组的类加载器,get(实例化对象)可以获取到对象的值            //1.获取宿主的类加载器            ClassLoader pathClassLoader = context.getClassLoader();            //通过BaseClassLoader的实例化对象获取到pathList的实例化对象            Object hostPathList = pathListField.get(pathClassLoader);            //通过pathList的实例得到elements的对象            Object[] hostDexElements = (Object[]) dexElementField.get(hostPathList);            //2.插件            ClassLoader pluginClassLoader = new DexClassLoader(apkpath, context.getCacheDir().getAbsolutePath(), null,                    pathClassLoader);            //通过BaseClassLoader的实例化对象获取到pathList的实例化对象            Object pluginPathList = pathListField.get(pluginClassLoader);            //通过pathList的实例得到elements的对象            Object[] pluginDexElements = (Object[]) dexElementField.get(pluginPathList);            //合并            //new Elements[]            Object[] newElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), hostDexElements.length+pluginDexElements.length);            System.arraycopy(hostDexElements, 0, newElements, 0, hostDexElements.length);            System.arraycopy(pluginDexElements, 0, newElements, hostDexElements.length, pluginDexElements.length);            //赋值到宿主的dexElements            //hostDexElements = newElemnts            dexElementField.set(hostPathList, newElements);        } catch (ClassNotFoundException | IllegalAccessException e) {            e.printStackTrace();        } catch (NoSuchFieldException e) {            e.printStackTrace();        }    }}

Application启动:

public class MyApplication extends Application {    @Override    public void onCreate() {        super.onCreate();        LoadUtil.loadClass(this);    }}

启动插件:

try {                    Class clazz = Class.forName("com.example.hotfixplugin.Test");                    Method add = clazz.getMethod("add");                    Object obj = add.invoke(clazz.newInstance(), 1, 2);                    Log.d("test", obj.toString());                } catch (ClassNotFoundException | NoSuchMethodException | java.lang.InstantiationException e) {                    e.printStackTrace();                } catch (InvocationTargetException e) {                    e.printStackTrace();                } catch (IllegalAccessException e) {                    e.printStackTrace();                }
3:启动插件的Activity流程:(启动插件的Activity和普通类最主要的区别是,启动Activity的时候,AMS会对Activity是否注册进行校验,而正常情况下,我们宿主是没有注册插件的Activity的)启动Activity流程:https://i.cnblogs.com/posts/edit;postId=16684049这其中关系到两次的进程间通信:1)应用调用AMS系统的服务都是实现了Binder的服务端,应用进程要想和它通信需要获取它的代理端。2)AMS到应用

在创建一个新的应用进程之后,系统首先会启动ActivityThread,ActivityThread是应用进程的主线程,在ActivityThread创建的时候会创建一个ApplicationThread的对象。这个ApplicationThread实现了一个Binder的服务端。新的进程创建完成之后通知AMS服务的之后同时把自己进程的ApplicationThread的代理端送给AMS服务。AMS服务中保存了所有应用进程的ApplicationThread的代理对象。所以AMS要想给应用进程发送消息,只需要得到目标应景进程的ApplicationThread的代理端对象即可。

滴滴插件化方案:https://github.com/didi/VirtualAPK

Activity:假设要启动插件中的Activity1,我们伪装一个Activity2骗过系统,预先注册在AndroidManifest.xml中,占个坑;

1)创建一个VasIinstruentation,通过反射机制和代理模式,替换掉系统中的Instrumentation,所有经过Instrumentation的操作都会到VasInstumentaion替代掉。

2)这时startActivity是在VasInstrumentation中执行,startActivity实际会调用到AMS中执行,因为AMS会对要启动的Activity1是否注册过进行校验。我们先保存Activity1的信息,然后告诉AMS我们要启动的是startActivity2。AMS看到启动的是Activity2,就通过校验。

3)AMS的作用:

a:对Activity的注册进行校验

b:栈的调度

c:AMS作为服务端,进行生命周期的管理,Client端的ActivityThread负责响应各个生命周期

4)AMS启动Activity2之后,根据上面流程图可知,最终会回到应用的mInstrumentation.newActivity(),newActivity通过类加载器生成实际上的Activity对象,我们的VasInstrumentation就可以对该方法进行重写,把原来实际要启动的Activity1的信息重新提取出来,替换掉当前的Activity2,生成Activity2对象,就完成了正常的Activiyt1启动。

4:查找Hook点的原则:

1)尽量静态变量或者单例对象:有利于反射和动态代理,反射的时候,如果不是静态的,就需要往前面找,直到可以得到一个类的对象为止。

2)尽量Hook public的对象和方法:谷歌提供给外面使用的,一般不会怎么修改。

关键词: