插件化开发详解

Posted 蜗牛攀爬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了插件化开发详解相关的知识,希望对你有一定的参考价值。

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的)
这其中关系到两次的进程间通信:
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的对象和方法:谷歌提供给外面使用的,一般不会怎么修改。

 

以上是关于插件化开发详解的主要内容,如果未能解决你的问题,请参考以下文章

如何将Android程序做成插件化的形式?详解插件化实现原理

插件化框架解读之Android 资源加载机制详解

Shadow插件化系列简单详解

Android热修复与插件化实践之路

Android 插件化“ 插桩式 “ 插件化框架 ( 代理 Activity 组件开发 )

Classloader、插件化开发(结合Presto)