Android Multidex原理和基于Multidex的热修复

这是一个简单的multidex实现热修复的demo
通过这个demo学习multidex和其热修复的原理及java反射知识

基础概念

  • class
  • dex
  • dalvik
  • arm
  • .java
  • .class
  • .dex

dex文件是.class去冗余,合并相同代码区后的产物,一个dex是多个class的集合 dex文件中方法的索引从0到65535

方法数超限

dex unable to execute dex.method ID not in 65536

解决方案:多dex解决,使用multiDex

multiDex

使用方法

安卓版本使用
21以上在defaultConfig里 使用multiDexEnable = true
21以下在defaultConfig里 使用multiDexEnable = true 引用multiDex支持库 然后配置Application 为MultiDexApplication的子类 或者在attachBaseContext中调用MultiDex.install

MultiDex原理

编译期

.class 通过 dx命令 转为 classes.dex

multi-dex参数用于控制生成多个dex文件

MultiDex.install流程

  1. 判断虚拟机是否支持MultiDex
  2. 解压获取待安装的dex文件列表
  3. 把dex安装到classLoader中

运行期

虚拟机判断

虚拟机版本流程
dalvik的jitjava.vm.version < 2.0.0apk install .dex 启动 jit(启动慢没有缓存,运行慢) 原生指令 运行
art的aotjava.vm.version >= 2.0.0apk install(aot) 原生指令 启动 执行

dex解压

apk中的dex安装包会解压到应用的内置目录data,解压是耗时任务

dex安装

.class 类加载 运行

multidex原理

  • pathClassLoader中有一个pathList变量,其中是dexElement数组,类加载器会按照顺序加载dex文件
  • MultiDex.install
  • 反射获取到PathClassLoader的pathList
  • 然后生成dex文件对应的element数组
  • 将生成的element数组插入到原有的dexelements数组后面

热修复

原理

参考multidex,但是需要将修复包插入到数组的前面

生成代码补丁包

运行时注入代码补丁

通过javac 单独编译要编译的java文件 然后通过dx命令生成一个dex文件 然后插入pathList的element数组的第一个

补丁包生成流程

1、新建一个lib 拷贝原工程,然后删除除要修复的文件外的所有文件

2、javac java文件,生成.class文件

3、使用dx命令生成dex文件
.java javac .class dx classes.dex(多个)

反射工具类

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

public class ReflectUtil {
    /**
     * 获取某个属性
     *
     * @param instance
     * @param name
     * @return
     */
    public static Field findField(Object instance, String name) throws NoSuchFieldException {
        Class clazz = instance.getClass();
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(name);
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                return field;
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchFieldException("No Such Field :" + name);
    }

    public static Method findMethod(Object instance,
                                    String name,
                                    Class<?>... parameterTypes) throws NoSuchMethodException {
        Class clazz = instance.getClass();
        while (clazz != null) {
            Method method = null;
            try {
                method = clazz.getDeclaredMethod(name);
                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }
                return method;
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchMethodException("No Such Method :" + name);

    }
}


注入类

import android.content.Context;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

class HotFixManager {
    public static void installFixDex(Context context) {
        try {
            File fixFile = new File("");
            if (!fixFile.exists()) {
                return;
            }
            Field pathListField = ReflectUtil.findField(context.getClassLoader(), "pathList");
            Object dexPathList = pathListField.get(context.getClassLoader());

            /**
             * get method makeDexElements
             */
            Method makeDexElements = ReflectUtil.findMethod(
                    dexPathList,
                    "makeDexElements",
                    List.class,
                    File.class,
                    List.class,
                    ClassLoader.class);

            ArrayList<File> filesToBeInstall = new ArrayList<>();
            filesToBeInstall.add(fixFile);

            File operationDirectory = new File(context.getFilesDir(), "");
            ArrayList<IOException> suppressedException = new ArrayList<>();

            Object[] extraElements = (Object[]) makeDexElements.invoke(dexPathList,
                    filesToBeInstall, operationDirectory, suppressedException, context.getClassLoader());

            /**
             * 获取原始字段
             */
            Field dexElements = ReflectUtil.findField(dexPathList, "dexElements");
            Object[] originElements = (Object[]) dexElements.get(dexPathList);

            Object[] combinedElements = (Object[]) Array.newInstance(
                    originElements.getClass().getComponentType(),
                    originElements.length + extraElements.length
            );

            /**
             * insert
             */

            System.arraycopy(extraElements, 0, combinedElements, 0, extraElements.length);
            System.arraycopy(originElements, 0, combinedElements, extraElements.length, originElements.length);


            /**
             * swap
             */
            dexElements.set(dexPathList, combinedElements);


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

multidex引起的ANR问题

multidex会先从apk中解压dex文件存放在应用的data目录下 接着会将dex注入到PathClassLoader中 然后通过dexopt优化为.odex文件 然后加载类的时候加载

如果解压或者dexopt的时间太长,就会引起主线程的卡顿引起anr

注:这个只会出现在应用安装后第一次启动过程

优化方案

不在主线程中进行操作 启动一个新的进程操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值