Android热修复之替换原有classes.dex的源代码分析

我们在服务端下载修复bug后的classes.dex并且动态替换原有的classes.dex这个过程的源代码的分析是我们今天要讨论的。

文末给出所有源码。


Context家族的classloader就是Android默认的加载器PathClassLoader。所以我们很简单一句话就可以获得了。

ClassLoader pathClassLoader = context.getClassLoader();

PathClassLoader只能加载已安装的apk,dex class loader则没有这个限制。所以我们需要创建一个dex class loader

ClassLoader classLoader = new DexClassLoader(patchFile.getAbsolutePath(),// dexPath
        oDexFile.getAbsolutePath(),// optimizedDirectory
        null,
        pathClassLoader
);

oDexFile.getAbsolutePath()是odex的路径,他相当于是classes.dex的helper,我们约定俗成在创建dex class loader需要传入他的路径。

patchFile.getAbsolutePath(),他就是我们的服务端传来的用于替换原来bug的dex的dex了。

这样声明以后,这个新的dex中所有的类,我们都可以通过dex class loader去访问了哦!!


然后我们就是分别获取PathClassLoader和dex class loader的element数组,然后把dex class loader的element插入到前者的某一位上即可。PathClassLoader中每一位都是一个dex,如果你不分包的话,他只有一个dex,所以我们想要热修复,自然是需要分包的。


我们看一下从classloader中获取element数组的方法

private static Object getDexElementByClassLoader(ClassLoader classLoader) throws Exception {
    Class<?> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
    Field pathListField = classLoaderClass.getDeclaredField("pathList");
    pathListField.setAccessible(true);
    Object pathList = pathListField.get(classLoader);

    Class<?> pathListClass = pathList.getClass();
    Field dexElementsField = pathListClass.getDeclaredField("dexElements");
    dexElementsField.setAccessible(true);
    Object dexElements = dexElementsField.get(pathList);

    return dexElements;
}

我们用反射去拿到了classloader中的pathlist,再用反射去拿到pathlist中的element数组,就这么简单


我们看一下我们的dex class loader的element数组是怎么插入到前者的

private static Object combineArray(Object arrayLhs, Object arrayRhs) {
    Class<?> localClass = arrayLhs.getClass().getComponentType();
    int i = Array.getLength(arrayLhs);
    int j = i + Array.getLength(arrayRhs);
    Object result = Array.newInstance(localClass, j);
    for (int k = 0; k < j; ++k) {
        if (k < i) {
            Array.set(result, k, Array.get(arrayLhs, k));
        } else {
            Array.set(result, k, Array.get(arrayRhs, k - i));
        }
    }
    return result;
}

lhs是dex。。。的,rhs是path。。。的。我们可以看到,前面的部分,都用dex。。。的,后面的部分,才用path。。。相应位上的。我觉得这里的代码不是很对,因为这个element数组,你怎么知道,你要替换的类是前多少个?所以这里的代码还有待考究。(但是如果你仅仅有2个包的话,那就没有问题了。因为分包的时候,一个包是作为启动包的,他肯定会被率先加载,然后 被cache起来。所以哪怕被覆盖了,也不要紧)


然后再把最新生成的数组,重新使用反射的方式,设置给pathclassloader了,这和上面某个代码是类似的。


全部代码给出

package com.example.myapplication;

import android.app.Application;
import android.content.Context;
import android.os.Environment;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;

import dalvik.system.DexClassLoader;

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        try {
            injectDexElements(this, Environment.getExternalStorageDirectory().getAbsolutePath()+"/classes2.dex");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void injectDexElements(Context context, String patchFilePath) throws Exception {
        ClassLoader pathClassLoader = context.getClassLoader();

        /**dex, 优化后的路径, 必须在要App data目录下, 否则会没有权限*/
        File oDexFile = new File(context.getDir("odex", Context.MODE_PRIVATE).getAbsolutePath());
        /**dex 补丁文件路径(文件夹)*/
        File patchFile = new File(patchFilePath);

//        if (!patchFile.exists()) {
//            patchFile.mkdirs();
//        }

        // 合并成一个数组
        Object applicationDexElement = getDexElementByClassLoader(pathClassLoader);

//        for (File dexFile : patchFile.listFiles()) {
        ClassLoader classLoader = new DexClassLoader(patchFile.getAbsolutePath(),// dexPath
                oDexFile.getAbsolutePath(),// optimizedDirectory
                null,
                pathClassLoader
        );
        // 获取这个classLoader中的Element
        Object classElement = getDexElementByClassLoader(classLoader);
        //Log.e("TAG", classElement.toString());
        applicationDexElement = combineArray(classElement, applicationDexElement);
//        }

        // 注入到pathClassLoader        injectDexElements(pathClassLoader, applicationDexElement);
    }

    /**
     * dexElement注入到已运行classLoader     *
     * @param classLoader
     * @param dexElement
     * @throws Exception
     */
    private static void injectDexElements(ClassLoader classLoader, Object dexElement) throws Exception {
        Class<?> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListField = classLoaderClass.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object pathList = pathListField.get(classLoader);

        Class<?> pathListClass = pathList.getClass();
        Field dexElementsField = pathListClass.getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        dexElementsField.set(pathList, dexElement);
    }

    /**
     * 合并两个dexElements数组
     *
     * @param arrayLhs
     * @param arrayRhs
     * @return
     */
    private static Object combineArray(Object arrayLhs, Object arrayRhs) {
        Class<?> localClass = arrayLhs.getClass().getComponentType();
        int i = Array.getLength(arrayLhs);
        int j = i + Array.getLength(arrayRhs);
        Object result = Array.newInstance(localClass, j);
        for (int k = 0; k < j; ++k) {
            if (k < i) {
                Array.set(result, k, Array.get(arrayLhs, k));
            } else {
                Array.set(result, k, Array.get(arrayRhs, k - i));
            }
        }
        return result;
    }

    /**
     * 获取classLoader中的DexElement
     *
     * @param classLoader ClassLoader
     */
    private static Object getDexElementByClassLoader(ClassLoader classLoader) throws Exception {
        Class<?> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListField = classLoaderClass.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object pathList = pathListField.get(classLoader);

        Class<?> pathListClass = pathList.getClass();
        Field dexElementsField = pathListClass.getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        Object dexElements = dexElementsField.get(pathList);

        return dexElements;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_36523667/article/details/80330485