美文网首页
Android热修复实现原理以及方法

Android热修复实现原理以及方法

作者: 出云月 | 来源:发表于2017-08-31 14:03 被阅读0次

    现在主要由两大方法
    1.阿里AndFix,主要是采用Ndk实现对方法指针的替换
    2.腾讯Tinker
    现在主要说的是tinker的实现方法:
    一. 首先介绍下两个概念:
    public class PathClassLoader extends BaseDexClassLoader {
    用来加载已安装应用程序的dex

    public class DexClassLoader extends BaseDexClassLoader {
    可以加载指定的某个dex文件。(限制:必须要在应用程序的目录下面,所以下载下来需要复制到本目录里)

    二. 很明显我们需要使用DexClassLoader来实现需求

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

    上面创建了一个DexPathList

        public DexPathList(ClassLoader definingContext, String dexPath,
                String libraryPath, File optimizedDirectory) {
            ……
            this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
        }
    
        private static Element[] makeDexElements(ArrayList<File> files,
                File optimizedDirectory) {
            ArrayList<Element> elements = new ArrayList<Element>();
            for (File file : files) {
                ZipFile zip = null;
                DexFile dex = null;
                String name = file.getName();
                if (name.endsWith(DEX_SUFFIX)) {
                    dex = loadDexFile(file, optimizedDirectory);
                } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                        || name.endsWith(ZIP_SUFFIX)) {
                    zip = new ZipFile(file);
                }
                ……
                if ((zip != null) || (dex != null)) {
                    elements.add(new Element(file, zip, dex));
                }
            }
            return elements.toArray(new Element[elements.size()]);
        }
    
        private static DexFile loadDexFile(File file, File optimizedDirectory)
                throws IOException {
            if (optimizedDirectory == null) {
                return new DexFile(file);
            } else {
                String optimizedPath = optimizedPathFor(file, optimizedDirectory);
                return DexFile.loadDex(file.getPath(), optimizedPath, 0);
            }
        }
    
        /**
         * Converts a dex/jar file path and an output directory to an
         * output file path for an associated optimized dex file.
         */
        private static String optimizedPathFor(File path,
                File optimizedDirectory) {
            String fileName = path.getName();
            if (!fileName.endsWith(DEX_SUFFIX)) {
                int lastDot = fileName.lastIndexOf(".");
                if (lastDot < 0) {
                    fileName += DEX_SUFFIX;
                } else {
                    StringBuilder sb = new StringBuilder(lastDot + 4);
                    sb.append(fileName, 0, lastDot);
                    sb.append(DEX_SUFFIX);
                    fileName = sb.toString();
                }
            }
            File result = new File(optimizedDirectory, fileName);
            return result.getPath();
        }
    
    optimizedDirectory是用来缓存我们需要加载的dex文件的,并创建一个DexFile对象,如果它为null,那么会直接使用dex文件原有的路径来创建DexFile
    对象。
    

    加载类的过程:
    ClassLoader通过loadClass来加载需要的类

     public Class<?> loadClass(String className) throws ClassNotFoundException {
            return loadClass(className, false);
        }
    
        protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
            Class<?> clazz = findLoadedClass(className);
            if (clazz == null) {
                ClassNotFoundException suppressed = null;
                try {
                    clazz = parent.loadClass(className, false);
                } catch (ClassNotFoundException e) {
                    suppressed = e;
                }
    
                if (clazz == null) {
                    try {
                        clazz = findClass(className);
                    } catch (ClassNotFoundException e) {
                        e.addSuppressed(suppressed);
                        throw e;
                    }
                }
            }
            return clazz;
        }
    

    最后还是调用的DexPathList的findClass

        public Class findClass(String name) {
            for (Element element : dexElements) {
                DexFile dex = element.dexFile;
                if (dex != null) {
                    Class clazz = dex.loadClassBinaryName(name, definingContext);
                    if (clazz != null) {
                        return clazz;
                    }
                }
            }
            return null;
        }
    

    从上面代码看出如果element里面找到了class,for循环就会退出
    所以我们只要修改dexElements这个集合就行了,把我们创建的dexElements跟原始的dexElements合并。
    dexA:原app的dex文件
    dexB:补丁dex文件,可以是多个
    1.先把下载下来的dex文件用file形式保存到一个集合中

            //遍历所有的修复的dex
            File fileDir = context.getDir(MyConstants.DEX_DIR,Context.MODE_PRIVATE);
            File[] listFiles = fileDir.listFiles();
            for(File file:listFiles){
                if(file.getName().startsWith("classes")&&file.getName().endsWith(".dex")){
                    loadedDex.add(file);//存入集合
                }
            }
    

    2.使用反射获取dexA,dexB的classLoader,dexA的classLoader使用PathClassLoader获取,dexB的classLoader由dexClassLoader获取

                PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
    
                for (File dex : loadedDex) {
                    //2.加载指定的修复的dex文件。
                    DexClassLoader classLoader = new DexClassLoader(
                            dex.getAbsolutePath(),//String dexPath,
                            fopt.getAbsolutePath(),//String optimizedDirectory,
                            null,//String libraryPath,
                            pathLoader//ClassLoader parent
                    );
                }   
    

    3.通过classloader获取dexA和dexB的pathList

                    Object dexObj = getPathList(classLoader);
                    Object pathObj = getPathList(pathLoader);
    

    4.通过pathList获取dexA和dexB的dexElements,并把这两个合并

                    Object mDexElementsList = getDexElements(dexObj);
                    Object pathDexElementsList = getDexElements(pathObj);
                    //合并完成
                    Object dexElements = combineArray(mDexElementsList,pathDexElementsList);
    

    5.把合并之后的dexElements赋值给dexA的classLoader的pathList中的dexElements

                    Object mDexElementsList = getDexElements(dexObj);
                    Object pathDexElementsList = getDexElements(pathObj);
                    //合并完成
                    Object dexElements = combineArray(mDexElementsList,pathDexElementsList);
                                    //重写给PathList里面的lement[] dexElements;赋值
                    Object pathList = getPathList(pathLoader);
                    setField(pathList,pathList.getClass(),"dexElements",dexElements);
    

    那么具体步骤就已经完成了。

    6.怎么才能打出一个dex包?
    通过dx.bat这个系统提供的工具就可以

    1.找到MyTestClass.class
        com\app\build\intermediates\bin\MyTestClass.class
    2.配置dx.bat的环境变量
        Android\sdk\build-tools\23.0.3\dx.bat
    3.命令
    dx --dex --output=D:\Users\jiang\Desktop\dex\classes2.dex D:\Users\jiang\Desktop\dex
    命令解释:
        --output=D:\Users\jiang\Desktop\dex\classes2.dex   指定输出路径
        D:\Users\jiang\Desktop\dex    最后指定去打包哪个目录下面的class字节文件(注意要包括全路径的文件夹,也可以有多个class)
    

    代码地址如下:

    相关文章

      网友评论

          本文标题:Android热修复实现原理以及方法

          本文链接:https://www.haomeiwen.com/subject/bojqjxtx.html