动态加载与热修复知识

是很久之前的技术了,之前也写过一篇类似的,有了一些新的理解看法,就写的详细一点。

学习这个之前,需要了解一下class文件到底是个啥。许多人可能写了这么多年java,但是根本就不知道.class文件是怎么组成的。大部分时候实现功能也用不上知道。所以很多时候你都只能浮于表面

.Class文件

因为JVM是加载.class文件,使得JVM的可移植性很强,因为它只管加载.class文件就行了,无需管你是什么语言编译出来的.class。

.class文件是由无符号数与表组成:

无符号数:属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数。

表:表是由多个无符号数或者其他表作为数据项构成的复合数据类型,class文件中所有的表都以“_info”结尾。

那表是什么东西呢,首先要先知道.class文件的组成结构

魔数是一个4个字节的固定值,JVM用来校验你这个文件是不是.class文件。这里只着重说一下常量池。

常量池有18种TAG用来表示表的类型

我只拿出几个比较用的多的类型举例。尤其说下String类型这张表。它是由1个字节的TAG以及2个字节的地址下标组成。

会指向一个UTF8_info类型的表。UTF8_info由1个字节的TAG,2个字节长度的length以及一个bytes数组组成。bytes就是存放的char值,长度就是length。

常量池后面的类型也是由各种表组成,感兴趣的朋友可以自行查阅相关文档。

我们的android虚拟机是一个特殊的JVM,JVM是用来加载.class文件,基于栈结构,Dalvik是加载.dex文件,是寄存器结构。

.dex文件对.class文件进行了优化,去除一些冗余的字段,而且字节码上也有进行优化。

类加载器

BootClassLoader:Android系统启动时会使用BootClassLoader来预加载常用类。

PathClassLoader:8.0之后和DexClassLoader没啥区别。用来加载Dex与Apk文件。

重要的是类加载的机制。

双亲委派机制

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。

2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

最简单的热修复方式就是借助这个机制。让被修复的类在后面加载,修复的类先被加载。5.0之后就不会有CLASS_ISPREVERIFIED标志了。所以只要你最小版本号高于5.0就无需去进行插桩,需要学习插桩看我另外一篇博客。

实现

public class HotFixUtil {
    private static HotFixUtil hotFixUtil = new HotFixUtil();
    private DexClassLoader dexClassLoader;

    public static HotFixUtil getInstance() {
        return hotFixUtil;
    }

    /**
     * 加载修复的dex
     * @param context
     * @param name
     */
    public void loadDex(Context context, String name) {
        File fileDir = context.getDir("dex", Context.MODE_PRIVATE);
        String dexPath = fileDir.getAbsoluteFile() + File.separator + name;
        File dexFile = new File(dexPath);
        if (!dexFile.exists()) {
            try {
                dexFile.createNewFile();
                copyFiles(context, name, dexFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //获取dexElements
    public void initDex(Context context) {
        File fileDir=context.getDir("dex", Context.MODE_PRIVATE);
        List<File> listDex = getList(fileDir);
        try {
            //获取pathList
            Field pathListField = getField(context.getClassLoader(), "pathList");
            Object pathList = pathListField.get(context.getClassLoader());
            //获取老的dexElements
            Field dexElementsField = getField(pathList, "dexElements");
            Object[] oldDexElements = (Object[]) dexElementsField.get(pathList);
            //获取 makeDexElements 方法
            Method makeDexElements = getMethod(pathList, "makeDexElements", List.class, File.class,
                    List.class, ClassLoader.class);
            //装载fix.dex
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            Object[] myDexElements = (Object[]) makeDexElements.invoke(pathList, listDex, fileDir, suppressedExceptions, context.getClassLoader());

            //合并
            Object[] newDexElements = (Object[]) Array.newInstance(oldDexElements.getClass().getComponentType(), oldDexElements.length + myDexElements.length);
            System.arraycopy(myDexElements,0,newDexElements,0,myDexElements.length);
            System.arraycopy(oldDexElements,0,newDexElements,myDexElements.length,oldDexElements.length);

            //设置
            dexElementsField.set(pathList,newDexElements);
            Toast.makeText(context,"装载完成",Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private List<File> getList(File fileDir){
        File[] files = fileDir.listFiles();
        List<File> list=new ArrayList<>();
        for (File file : files) {
            if(file.getName().endsWith(".dex")){
                list.add(file);
            }
        }
        return list;
    }


    public DexClassLoader getDexClassLoader() {
        return dexClassLoader;
    }

    public static Field getField(Object object, String name) throws NoSuchFieldException {
        Class<?> aClass = object.getClass();
        while (aClass != null) {
            try {
                Field declaredField = aClass.getDeclaredField(name);
                if (!declaredField.isAccessible()) {
                    declaredField.setAccessible(true);
                }
                return declaredField;
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
                aClass=aClass.getSuperclass();
            }
        }
        throw new NoSuchFieldException("找不到字段");
    }

    public static Method getMethod(Object object, String name,Class... value) throws NoSuchMethodException {
        Class<?> aClass = object.getClass();
        while (aClass != null) {
            try {
                Method declaredMethod = aClass.getDeclaredMethod(name, value);
                if (!declaredMethod.isAccessible()) {
                    declaredMethod.setAccessible(true);
                }
                return declaredMethod;
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
                aClass=aClass.getSuperclass();
            }
        }
        throw new NoSuchMethodException("找不到方法");
    }

    public static void copyFiles(Context context, String fileName, File desFile) {
        InputStream in = null;
        OutputStream out = null;

        try {
            in = context.getAssets().open(fileName);
            out = new FileOutputStream(desFile.getAbsolutePath());
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = in.read(bytes)) != -1)
                out.write(bytes, 0, len);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null)
                    in.close();
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

注释比较清晰了,代码也在git上,需要的自行下载吧~

代码地址:https://github.com/VjayYi/HotFixDemo

博客有借鉴一些其他博客内容,图片PPT均为自己所画。

 

 

 

.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章