当前位置:   article > 正文

架构师学习--插件化之Hook方式(5.0版本~9.0版本)_9|too|s-hook

9|too|s-hook

首选,当我们启动一个activity,在没有在配置文件注册的前提下,会抛出以下异常:
在这里插入图片描述
以下分析基于9.0源码,兼容代码可自行下载完整代码。

一、异常分析

那么首先我们需要分析这个异常是在哪里出现的?源码跟踪如下:
在这里插入图片描述
在checkStartActivityResult()方法中就能看到上面的异常信息。那么如何才能不让系统报这个异常呢,那就需要在这个方法执行之前做些手脚了。查看系统源码,在执行checkStartActivityResult()方法之前,会执行AMS检查的方法,并把检查结果传给了这个方法,如下:
在这里插入图片描述

二、hook系统AMS检查

有了上面的异常分析,我们得到这样的结论,只要AMS检查通过,就不会有下面的异常,怎么样才能让它检查通过呢?继续分析源码

1 、ActivityManager.getService()是什么

对于不同该系统版本,被定义的文件类型不一样,在26版本之前,它是一个java文件。26及之后版本它是一个aidl文件,无论如何它都是一个接口,里面都会有startActivity()方法。

2、startActivity()参数分析

最重要的一个参数就是第3个参数,携带我们的跳转信息。那么我们就可以在执行检查的时候拦截原来没有注册的activity替换成已经注册的代理activity,重新封装成intent,作为参数。这样就会检查通过。那么只有动态代理才能在系统执行之前,进行方法拦截。

三、hook系统EXECUTE_TRANSACTION消息处理

当完成了ams的检查,下一步就是在系统启动activity之前,我们要把intent信息替换成我们目标intent。这样才能跳转到我们想要的界面。继续分析源码

1 、拦截什么?

ActivityThread源码1806行就是我们activity即将要启动的时刻,我们不能在方法中拦截,而是在方法之前拦截,这是一个handler消息回调方法。所以可以在消息分发的地方进行拦截我们需要的EXECUTE_TRANSACTION消息。
在这里插入图片描述

2 、handler消息分发

进入Hander源码97行开始,最终发现是通过mCallback进行消息分发的,也就是说只要我们将mCallback替换成自己的Callback,就可以拦截系统的消息,拿到系统消息后进一步做处理。
在这里插入图片描述

四、hook实现

1 、hook系统的AMS检查
 private void hookAMS_up_26() throws Exception {

        //1-------------拿到IActivityManager类 需要hook的类
        Class<?> IActivityManagerClass = Class.forName("android.app.IActivityManager");

        //2-------------拿到ActivityManager
        Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");

        //以下为了拿到IActivityManager对象 ,因为后续仍然需要它进行invoke   public static IActivityManager getService()
        Method getServiceMethod = activityManagerClass.getMethod("getService");
        getServiceMethod.setAccessible(true);
        final Object activityManagerObj = getServiceMethod.invoke(null);
        
        //本质就是IActivityManager
        Object activityManagerProxy = Proxy.newProxyInstance(this.getClassLoader(),
                new Class[]{IActivityManagerClass}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        if ("startActivity".equals(method.getName())) {
                            //跳转到代理activity
                            Intent intent = new Intent();
                            intent.setClass(BaseApp.this, ProxyActivity.class);
                            //并且将TestActivity信息携带过去

                            Intent preIntent = (Intent) args[2];
                            intent.putExtra("preIntent", preIntent);

                            //第三个参数就是intent
                            args[2] = intent;
                        }
                        //继续执行系统的方法
                        return method.invoke(activityManagerObj, args);
                    }
                });

        //拿到ActivityManager类
        Class<?> activityManagerClass2 = Class.forName("android.app.ActivityManager");

        //3-------------拿到类中的IActivityManagerSingleton属性
        Field iActivityManagerSingleton = activityManagerClass2.getDeclaredField(
                "IActivityManagerSingleton");
        iActivityManagerSingleton.setAccessible(true);
        //拿到属性所属的类对象---》Singleton
        Object singletonObj = iActivityManagerSingleton.get(null);
        //4------------拿到Singleton类
        Class<?> singletonClass = Class.forName("android.util.Singleton");
        //拿到类中的mInstance属性,mInstance就是系统的IActivityManager对象
        Field mInstanceField = singletonClass.getDeclaredField("mInstance");
        //授权
        mInstanceField.setAccessible(true);
        //5-------------最终是将IActivityManagerClass类对象替换成我们的代理对象
        //第一个参数,要操作的类对象 第二个参数替换的类对象
        mInstanceField.set(singletonObj, activityManagerProxy);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

2、hook系统消息回调

/**
     * 8.0及以上
     */
    private class Callback_Up_26 implements Handler.Callback {

        private Handler mH;

        public Callback_Up_26(Handler mH) {
            this.mH = mH;
        }

        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case EXECUTE_TRANSACTION:
                    try {
                        /*
                            1、mIntent所属对象在ClientTransaction
                            源码: private List<ClientTransactionItem> mActivityCallbacks;
                         */
                        Class<?> clientTransactionClass = Class.forName("android.app" +
                                ".servertransaction" +
                                ".ClientTransaction");
                        Field mActivityCallbacksField = clientTransactionClass.getDeclaredField(
                                "mActivityCallbacks");
                        mActivityCallbacksField.setAccessible(true);

                        /*
                            2、拿到mActivityCallbacks所属对象,就是msg.obj;
                            源码:final ClientTransaction transaction = (ClientTransaction) msg.obj;
                         */
                        Object transactionObj = msg.obj;

                        //数组中的第1个就是LaunchActivityItem对象
                        List mActivityCallbacksObj =
                                (List) mActivityCallbacksField.get(transactionObj);

                        if (mActivityCallbacksObj == null || mActivityCallbacksObj.size() == 0) {
                            return false;
                        }

                        Class<?> launchActivityItemClass = Class.forName("android.app" +
                                ".servertransaction" +
                                ".LaunchActivityItem");
                        Field mIntentField = launchActivityItemClass.getDeclaredField("mIntent");
                        mIntentField.setAccessible(true);


                        //判断是不是launchActivityItem类型
                        if (!launchActivityItemClass.isInstance(mActivityCallbacksObj.get(0))) {
                            return false;
                        }
                        //3、要替换的intent对象
                        Intent intentNow = (Intent) mIntentField.get(mActivityCallbacksObj.get(0));
                        //我们的目标intent
                        Intent preIntent = intentNow.getParcelableExtra("preIntent");
                        if (preIntent != null) {
                            mIntentField.set(mActivityCallbacksObj.get(0), preIntent);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    break;

            }
            return false;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

最后 return false 的意义是继续交给系统进行消息分发,不能返回true。完成以上两步骤,运行发现,跳转到本类没有在配置文件中注册的activity是没有任何问题的。当我们尝试跳转到插件的activity,发现抛出如下异常:
在这里插入图片描述

五、dex合并

1、异常产生原因

进入ActivityThread源码2829行开始,伪代码如下:
在这里插入图片描述
这里还有两个操作,首先拿到类加载器,然后通调用newActivity方法,通过类加载器创建activity对象。

(1)类加载器
跟踪getClassLoader()方法,最终可在源码ClassLoader,224行返回PathClassLoader()对象。

(2)newActivity()

跟踪该方法:
在这里插入图片描述
最终进入父类的findClass方法,代码如下:
在这里插入图片描述

没错,这里就发现了我们之前的异常。当pathList中没有找到这个类,就会抛出异常,也就是DexPathList才是问题的关键。

2、DexPathList是什么?

在这里插入图片描述
它里面有一个dexElements数组,类里面会调用findClass()
在这里插入图片描述
遍历dexElements,没有找到就返回null。记住这是宿主的dexElements。每一个apk都会有dexElements数组,这是因为java虚拟机会将java文件编译成class文件,并且打包成dex文件。那么当我们需要加载类文件的时候就需要去执行dex文件,所以dexElements就是dex文件在内存中表现形式,真正可执行的文件是Element元素中的DexFile文件。既然知道了报错的原因是dexElements没有插件的dexElements。那么我们可不可以将插件的也添加进来,和宿主的组合成新的dexElements。

3、代码实现
/**
     * 合并宿主和插件的dex文件
     * 源码分心dexElements中包含插件和宿主的dex即可
     */
    private void combinedDex() throws Exception {

        //1、拿到宿主的dexElements []

        //最终调用ContextImpl的getClassLoader,返回PathClassLoader
        PathClassLoader classLoader = (PathClassLoader) getClassLoader();

        //拿到 private final DexPathList pathList;
        Class mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListClass = mBaseDexClassLoaderClass.getDeclaredField("pathList");
        pathListClass.setAccessible(true);
        Object pathListObj = pathListClass.get(classLoader);

        //拿到DexPathList中的Element[]数组 private Element[] dexElements;
        Field appElementsField = pathListObj.getClass().getDeclaredField("dexElements");
        appElementsField.setAccessible(true);
        Object appElementsObj = appElementsField.get(pathListObj);

        //2、拿到插件的dexElements []

        /*
            通过DexClassLoader加载插件apk的dex文件
            参数1:插件包路径
            参数2:使用DexClassLoader记载需要一个缓存路径 /data/data/插件包名/dexDir
            参数3:需要的支持库
            参数4:加载器

         */
        String apkPath =
                Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator +
                        "q1.apk";

        File file = this.getDir("dexDir", Context.MODE_PRIVATE);
        DexClassLoader pluginClassLoader = new DexClassLoader(apkPath, file.getAbsolutePath(), null,
                getClassLoader());
        //拿到 private final DexPathList pathList;
        Class mPluginBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        Field dexPathListClass = mPluginBaseDexClassLoaderClass.getDeclaredField("pathList");
        dexPathListClass.setAccessible(true);
        Object dexPathListObj = dexPathListClass.get(pluginClassLoader);

        //拿到DexPathList中的Element[]数组 private Element[] dexElements;
        Field dexElementsField = dexPathListObj.getClass().getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        Object dexElementsObj = dexElementsField.get(dexPathListObj);

        //3、合并成新的Elements[]

        int appElementLength = Array.getLength(appElementsObj);
        int pluginElementLength = Array.getLength(dexElementsObj);

        int totalLength = appElementLength + pluginElementLength;

        Object newElement = Array.newInstance(appElementsObj.getClass().getComponentType(),
                totalLength);
        for (int i = 0; i < totalLength; i++) {
            if (i < appElementLength) {

                //填充宿主的element
                Array.set(newElement, i, Array.get(appElementsObj, i));
            } else {
                //填充插件的element
                Array.set(newElement, i, Array.get(dexElementsObj, i - appElementLength));

            }
        }

        //4、将新的数组设置给宿主的Elements[]
        dexElementsField.set(pathListObj, newElement);

        //5、将插件资源添加到AssetManager中
        loadAssetAndResource(apkPath);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
4、加载插件资源

上面已经完成了插件class文件的加载,但是还需要加载插件的资源文件。才能在资源管理中找到插件的布局文件
在这里插入图片描述
对外提供getResource()方法:
在这里插入图片描述
并在插件module的基类BaseActivity中重写getResources()方法
在这里插入图片描述
这样就完成了插件的加载。

测试的话需要将插件apk放在sdCard目录下,并且不要忘记在宿主配置文件中添加访问权限。

完整代码:

代码做了5.0~9.0的兼容
hook式插件兼容代码传送门

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/240126
推荐阅读
相关标签
  

闽ICP备14008679号