Android 使用类加载器原理实现热修复 - Go语言中文社区

Android 使用类加载器原理实现热修复


本篇热修复的原理是通过类加载器加载修复好的class文件来实现。

源码分析

项目在编译的时候会将java文件翻译成class文件,而class类在程序安装的时候会打包成dex文件,Android 通过dalvik虚拟机运行dex文件,从而实现类的加载。

所以我们先来看看这里会用到两个类加载器:DexClassLoader和PathClassLoader。

public class DexClassLoader extends BaseDexClassLoader {
        /**
         * DexClassLoader用于找到被翻译过的和本地代码
         * 翻译过的类放置在Dex集合文件中,Dex集合被包含在Jar或apk的压缩文件中
         * <p>
         *
         * @param dexPath            含有classes和资源文件的Jar或者Apk文件列表,使用":"分割
         * @param optimizedDirectory 解压后dex的文件放置的目录,不可为空
         * @param libraryPath        包含本地库的列表目录,可为空
         * @param parent             父加载类
         */
        public DexClassLoader(String dexPath, String optimizedDirectory,
                              String libraryPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }
    }


    public class PathClassLoader extends BaseDexClassLoader {
        /**
         * @param dexPath 含有classes和资源文件的Jar或者Apk文件列表,使用":"分割
         * @param parent  父加载类
         */
        public PathClassLoader(String dexPath, ClassLoader parent) {
            super(dexPath, null, null, parent);
        }

        /**
         * @param dexPath     含有classes和资源文件的Jar或者Apk文件列表,使用":"分割
         * @param libraryPath 包含本地库的列表目录,可为空
         * @param parent      父加载类
         */
        public PathClassLoader(String dexPath, String libraryPath,
                               ClassLoader parent) {
            super(dexPath, null, libraryPath, parent);
        }
    }    

可以看出这两个类都是BaseDexClassLoader的子类,都在构造方法里调用了父类构造方法。而代码上最大的区别在于DexClassLoader会传入zip/jar/apk文件解压后的放置的目录,而PathClassLoader传的是null。

来看看父类BaseDexClassLoader里做了什么:

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                              String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        ...        
        return c;
    }

    ...
}

这里最主要的是两个方法,一个构造方法,一个findClass的方法,构造方法主要创建了一个DexPathList对象,而findClass方法主要是调用DexPathList对象的findClass方法

那再来看看DexPathList的代码

final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private final Element[] dexElements;
    ...

    public DexPathList(ClassLoader definingContext, String dexPath,
                       String libraryPath, File optimizedDirectory) {
        ...
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                suppressedExceptions);
        ...
    }


    /**
     * Makes an array of dex/resource path elements, one per element of
     * the given array.
     */
    private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                             ArrayList<IOException> suppressedExceptions) {
        ArrayList<Element> elements = new ArrayList<Element>();
        for (File file : files) {
            File zip = null;
            DexFile dex = null;
            String name = file.getName();

            if (file.isDirectory()) {
                // We support directories for looking up resources.
                // This is only useful for running libcore tests.
                elements.add(new Element(file, true, null, null));
            } else if (file.isFile()) {
                if (name.endsWith(DEX_SUFFIX)) {
                    // Raw dex file (not inside a zip/jar).
                    try {
                        dex = loadDexFile(file, optimizedDirectory);
                    } catch (IOException ex) {
                        System.logE("Unable to load dex file: " + file, ex);
                    }
                } else {
                    zip = file;

                    try {
                        dex = loadDexFile(file, optimizedDirectory);
                    } catch (IOException suppressed) {
                        suppressedExceptions.add(suppressed);
                    }
                }
            } else {
                System.logW("ClassLoader referenced unknown path: " + file);
            }

            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, false, zip, dex));
            }
        }

        return elements.toArray(new Element[elements.size()]);
    }

    ...

    public Class findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;

            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        ...
        return null;
    }
}

重点来了,dexPath是由多个dex路径以”:”为分隔的一串字符串(如”/data/dexdir1:/data/dexdir2:…”),DexPathList调用makeDexElements时,dexPath被分割成多个dex文件添加进一个列表,然后作为参数传入makeDexElements方法。makeDexElements方法中使用for循环将每个dex文件构造一个Element对象,然后将构造出的Element对象都加到一个elements数组里,因此dex文件和Element是一对一的关系。而findClass则是从由makeDexElements方法返回的Element数组中取出dex文件,然后将dex中和name匹配的class返回。

思路

总结下来,DexClassLoader可以加载任意目录下的dex/jar/apk/zip文件,PathClassLoader加载的是系统/data/app目录下的apk文件。所以我们的思路就是将我们修复的补丁(dex文件)放置到Storage目录下,用DexClassLoader加载补丁文件,然后用PathClassLoader加载app的dex文件,接着我们分别把补丁dex对应的Element列表和app dex对应的Element列表取出来,将补丁的Element列表合并到app Element列表前面,从而使系统在从Element列表中取dex时先取到我们的补丁,这样来进行热修复。

实践

原理和思路都掌握了,就实际操作一下,这里只讲解mac下的操作

  1. 将bug修复后在Android Studio上Rebuild Project

  2. 找到app/build/intermediates/classes/debug/包名/XXX.class
    连带路径”包名/XXX.class”复制到桌面新建的dex文件夹下,这个.class文件对应修复的.java文件
    版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
    原文链接:https://blog.csdn.net/u010198148/article/details/78627473
    站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

  • 发表于 2021-06-14 01:17:07
  • 阅读 ( 512 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢