插件化开发详解

1:替换DexElements流程:

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

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

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

 

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

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

 

 

启动插件的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(通过修改intent)。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的对象和方法:谷歌提供给外面使用的,一般不会怎么修改。

 

5:hook代码:以下选择hookIActivityManager以及Handler callback:

package com.example.hotfixhost;

import android.content.Intent;
import android.os.Handler;
import android.os.Message;

import androidx.annotation.NonNull;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author yanjim
 * @Date 2023/3/15
 */
public class HookUtil {

    private static final String ORIGINAL_INTENT_INFO = "original_intent_info";

    public static void hookAMS() {
        //动态代理,替换IActivityManager

        try {
            //获取singleton对象
            Class<?> clazz = Class.forName("android.app.ActivityManager");
            Field iActivityManagerSingletonField = clazz.getDeclaredField("IActivityManagerSingleton");
            Object singleton = iActivityManagerSingletonField.get(null);

            //mInstance对象---》IActivityManager对象
            Class<?> singletonClass = Class.forName("android.util.Singleton");
            Field mInstanceField = singletonClass.getDeclaredField("mInstance");
            Object mInstance = mInstanceField.get(singleton);

            Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
            Object mInstanceProxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class[]{iActivityManagerClass}, new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {


                            if ("startActivity".equals(method.getName())) {
                                int index = 0;
                                for (int i = 0; i < args.length; i++) {
                                    if (args[i] instanceof Intent) {
                                        index = i;
                                        break;
                                    }
                                }
                                Intent intent = (Intent) args[index];

                                //启动代理的Intent
                                Intent intentProxy = new Intent();
                                //宿主定义的用于欺骗AMS的Activity类
                                intentProxy.setClassName("packagename", "className");

                                //将插件的intent信息保存起来,供后续重新拿出来使用
                                intentProxy.putExtra(ORIGINAL_INTENT_INFO, index);

                                args[index] = intentProxy;
                            }
                            //第一个参数,系统的IActivity对象
                            return method.invoke(mInstance, args);
                        }
                    });


            //用代理对象替换掉系统的IActivityManager
            mInstanceField.set(singleton, mInstanceProxy);

        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public static void hookHandler() {

        try {
            Class<?> clazz = Class.forName("android.app.ActivityThread");
            Field sCurrentActivityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);
            Object activityThread = sCurrentActivityThreadField.get(null);

            //mh对象
            Field mHField = clazz.getDeclaredField("mH");
            mHField.setAccessible(true);
            Handler mH = (Handler)mHField.get(activityThread);


            Class<?> handlerClass = Class.forName("android.os.Handler");
            Field mCallbackField = handlerClass.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);


           Handler.Callback callback = new Handler.Callback() {
                @Override
                public boolean handleMessage(@NonNull Message msg) {
                    switch (msg.what) {
                        case 100:
                            //拿到了message
                            //ActivityClientRecord的对象   msg.obj
                            try {
                                Field intentField = msg.obj.getClass().getDeclaredField("intent");
                                intentField.setAccessible(true);

                                //启动代理类
                                Intent intentProxy = (Intent) intentField.get(msg.obj);

                                Intent intent = intentProxy.getParcelableExtra(ORIGINAL_INTENT_INFO);
                                if (intent != null) {
                                    intentField.set(msg.obj, intent);
                                }
                            } catch (NoSuchFieldException | IllegalAccessException e) {
                                e.printStackTrace();
                            }
                            break;
                    }
                    return false;
                }
            };
           //系统的Callback替换成自己创建的callback对象
            mCallbackField.set(mH, callback);
        } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

 

加载插件的资源:

1:rwa文件夹和assets文件夹的区别:

1)Android会自动为这个文件中的所有文件资源生成一个ID,可以容易访问,XML也可以访问,并且速度是最快的。

2)不生成ID,只能通过AsserManager访问,xml不能访问,范文速度慢,但是操作方便。

 

2:读取Asserts下的文件:

AssetManager assetManager = context.getAssets();
InputStream inputStream= assetManager.open(“filename”);

Resource类也是通过AssertManager来访问那些被编译过的资源文件的:

public String getString(@StringRes int id) throws NotFoundException {
        return getText(id).toString();
    }


@NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
        CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
        if (res != null) {
            return res;
        }
        throw new NotFoundException("String resource ID #0x"
                + Integer.toHexString(id));
    }

 

posted @ 2023-03-14 08:18  蜗牛攀爬  阅读(345)  评论(0编辑  收藏  举报