Android热修复实现一

Android 专栏收录该内容
1 篇文章 0 订阅

1. 前言

前两天跟着享学的课学了一下热修复的原理

这里记录一下

2.  知识点

  • 反射Reflect
  • 类加载器ClassLoader
  • Gradle开发
  • dex文件打包

3. 原理

在Android中所有我们运行期间需要的类都是由ClassLoader(类加载器)进行加载。因此让ClassLoader加载全新的类替换掉出现Bug的类即可完成热修复。

所以我们需要作的工作

1. 将需要修复的.class文件打包成一个单独dex包

2.通过反射PathClassLoader将这个dex文件与原来的dex文件合并

具体来说

第一步:获取到当前应用的PathClassloader

第二步:反射获取到DexPathList属性对象pathList

第三步:反射修改pathList的dexElements

目的是将我们的补丁dex文件插入到dexElements

4. 代码实现

反射工具类


public class SharedReflectUtil {


    /**
     * 反射获取属性对象
     * @param object 类对象
     * @param fieldName 属性名称
     * @return 属性对象
     */
    public static Field getField(Object object, String fieldName) throws NoSuchFieldException {
        for (Class<?> cls = object.getClass(); cls != null; cls = cls.getSuperclass()) {

            System.out.println("getField.cls:" + cls.getName());

            Field[] declaredFields = cls.getDeclaredFields();
            System.out.println("fileds.size:" + declaredFields.length);
            for (Field field : declaredFields) {
                System.out.println(field.getName());
            }

            try {
                Field declaredField = cls.getDeclaredField(fieldName);
                //设置访问权限
                declaredField.setAccessible(true);
                return declaredField;
            } catch (NoSuchFieldException e) {
                //如果没找到,就要去父类找
            }
        }

        throw new NoSuchFieldException( object.getClass().getSimpleName() + " No Such Filed:" + fieldName);
    }


    public static Method getMethod(Object object, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {

        for (Class<?> cls = object.getClass(); cls != null; cls = cls.getSuperclass()) {

            System.out.println("getMethod.cls:" + cls.getName());

            Method[] declaredMethods = cls.getDeclaredMethods();
            for (Method method : declaredMethods) {
                System.out.println(method.getName());
            }

            try {
                Method declaredMethod = cls.getDeclaredMethod(methodName, parameterTypes);
                //设置访问权限
                declaredMethod.setAccessible(true);
                return declaredMethod;
            } catch (NoSuchMethodException e) {
                //如果没找到,就要去父类找
            }
        }

        throw new NoSuchMethodException( object.getClass().getSimpleName() + " No Such Method:" + methodName);
    }

}

补丁安装

public static void installPatch(Context context, String patchPath) {
        File patchFile = new File(patchPath);
        if (!patchFile.exists()) {
            Log.e(TAG, "installPatch: " + patchPath + " is not exists");
            return;
        }

        Log.d(TAG, "installPatch: " + patchPath);

        File cacheFile = context.getCacheDir();

        //PathClassLoader
        ClassLoader classLoader = context.getClassLoader();

        //获取pathList属性对象,这个对象存在于其父类 BaseDexClassLoader中
        try {
            Field pathListField = SharedReflectUtil.getField(classLoader, "pathList");
            Log.i(TAG, "installPatch: find field:" + pathListField.getName() + "!!!");

            //通过get得到pathList的示例对象Object DexPathList
            Object pathList = pathListField.get(classLoader);

            //获取DexPathList中的  dexElements的示例对象
            Field dexElementsField = SharedReflectUtil.getField(pathList, "dexElements");
            Log.i(TAG, "installPatch: find field:" + dexElementsField.getName() + "!!!");

            Object[] dexElements = (Object[]) dexElementsField.get(pathList);
            //执行DexPathList的makeDexElements,将我们的dex转换成Element[]

            ArrayList<IOException> suppressedExceptions = new ArrayList<>();
            File file = new File(patchPath);
            ArrayList<File> files = new ArrayList<>();
            files.add(file);
            Method makeDexElementsMethod;
            Object[] newElements;
            if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                //6.0以上
                makeDexElementsMethod = SharedReflectUtil.getMethod(pathList, "makePathElements",
                        List.class, File.class, List.class);
                newElements = (Object[]) makeDexElementsMethod.invoke(null, files, cacheFile, suppressedExceptions);
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                //4.4-6.0
                makeDexElementsMethod = SharedReflectUtil.getMethod(pathList, "makeDexElements",
                        ArrayList.class, File.class, ArrayList.class);
                newElements = (Object[]) makeDexElementsMethod.invoke(null, files, cacheFile, suppressedExceptions);
                //4.4 会有一个bug
                //Class ref in pre-verified class resolved to unexpected implementation
            } else {
                //4.0-4.3
                makeDexElementsMethod = SharedReflectUtil.getMethod(pathList, "makeDexElements",
                        ArrayList.class, File.class);
                newElements = (Object[]) makeDexElementsMethod.invoke(null, files, cacheFile);
            }

            //创建一个新的数组,将旧的Element数组跟newElement数组进行合并
            Object[] replaceDexElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(), dexElements.length + newElements.length);
            System.arraycopy(newElements, 0, replaceDexElements, 0, newElements.length);
            System.arraycopy(dexElements, 0, replaceDexElements, newElements.length, dexElements.length);

            //替换属性值
            dexElementsField.set(pathList, replaceDexElements);
            Log.i(TAG, "installPatch: SUCCESS");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

然后在Application中执行安装即可

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ClassLoader classLoader = getClassLoader();
        System.out.println(classLoader);

    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        HotFix.installPatch(this, "/sdcard/patch.jar");
    }
}

最后如何生成这个补丁jar文件呢

修复好bug后,编译一次

在下面目录中,找到修复后的.class文件

然后利用sdk中的dx工具,将class文件打包成jar或者dex文件

命令:  dx --dex --output=patch.jar MyUtils.class

然后拷贝到sdcard下就可以重启app开始修复了

但是这样打包补丁文件真的很蠢,万一一个app中有10几个文件需要修复

这样很没有效率

下篇文章将讲述如何使用Gradle自动化打包补丁

 

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值