美文网首页
Android PathClassLoader 和 DexCla

Android PathClassLoader 和 DexCla

作者: ModestStorm | 来源:发表于2020-06-12 18:16 被阅读0次
    一般说起 PathClassLoader 和 DexClassLoader ,大家都会说,前者只能加载内存中已经安装的apk中的dex,而后者可以加载sd卡中的apk/jar ,因此 DexClassLoader 是热修复和插件化的基础。但是具体为什么DexClassLoader能加载sd卡中的类?

    1.PathClassLoader&DexClassLoader

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
    
     public DexClassLoader(String dexPath, String optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
    
    //对比发现两者都是继承自BaseDexClassLoader,唯一的不同是DexClassLoader多传了一个optimizedDirectory dex的优化目录。
    
    2.BaseDexClassLoader
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
               String librarySearchPath, ClassLoader parent) {
       super(parent);
       //1.仅仅初始化了一个变量
       this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
    }
    
    //2.BaseDexClassLoader查找类的方法,实际上内部调用的是pathList.findClass方法
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
       List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
       //3.从pathList的Element数组中找类,找不到就报ClassNotFoundException
       Class c = pathList.findClass(name, suppressedExceptions);
       if (c == null) {
           ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
           for (Throwable t : suppressedExceptions) {
               cnfe.addSuppressed(t);
           }
           throw cnfe;
       }
       return c;
    }
    

    3.DexPathList

    public DexPathList(ClassLoader definingContext, String dexPath,
                String librarySearchPath, File optimizedDirectory) {
        //...略
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        //DexPathList 通过 makeDexElements 得到了一个 Element[]类型的 
        //dexElements对象数数组里面存放了app的所有dex相关信息。
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions,definingContext);
        //...略
    }
    
    // files是一个ArrayList<File>列表,它对应的就是apk/dex/jar文件,因为我们可以指定多个文件。
    // optimizedDirectory是前面传入dex的输出路径
    // suppressedExceptions为一个异常列表
    private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                             ArrayList<IOException> suppressedExceptions) {
        ArrayList<Element> elements = new ArrayList<Element>();
        /*
         * Open all files and load the (direct or contained) dex files
         * up front.
         */
        for (File file : files) {
            File zip = null;
            DexFile dex = null;
            String name = file.getName();
            
            // 如果是一个dex文件
            if (name.endsWith(DEX_SUFFIX)) {
                // Raw dex file (not inside a zip/jar).
                try {
                                    //这里调用了loadDexFile方法
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
                    System.logE("Unable to load dex file: " + file, ex);
                }
            // 如果是一个apk或者jar或者zip文件
            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                    || name.endsWith(ZIP_SUFFIX)) {
                zip = file;
    
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException suppressed) {
                    /*
                     * IOException might get thrown "legitimately" by the DexFile constructor if the
                     * zip file turns out to be resource-only (that is, no classes.dex file in it).
                     * Let dex == null and hang on to the exception to add to the tea-leaves for
                     * when findClass returns null.
                     */
                    suppressedExceptions.add(suppressed);
                }
            } else 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 {
                System.logW("Unknown file type for: " + file);
            }
    
            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, false, zip, dex));
            }
        }
    
        return elements.toArray(new Element[elements.size()]);
    }
    
    private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
                                         Element[] elements)
                throws IOException {
            if (optimizedDirectory == null) {
              //1.根据 optimizedDirectory 参数是否为空,执行的方法不同,
             //PathClassLoader 执行的是 new DexFile(),
             // 而 DexClassLoader 执行的是 DexFile.loadDex() , 
              //然而 loadDex 最终也还是会调用 new DexFile() 来创建实例。
                return new DexFile(file, loader, elements);
            } else {
                String optimizedPath = optimizedPathFor(file, optimizedDirectory);
                return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
            }
        }
    
    //每个Element对象里面包含一个DexFile对象成员,它对应的就是dex文件。
    static class Element {
        private final File file; 
        private final boolean isDirectory; 
        private final File zip;
        private final DexFile dexFile;
        ......
    }
    
    

    4.DexFile

    static DexFile loadDex(String sourcePathName, String outputPathName,
            int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
        //最终都会执行new DexFile()创建对象
        return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
    }
    private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,DexPathList.Element[] elements) throws IOException {
            mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
            mFileName = sourceName;
    }
    private static Object openDexFile(String sourceName, String outputName, int flags,
                ClassLoader loader, DexPathList.Element[] elements) throws IOException {
        return openDexFileNative(new File(sourceName).getAbsolutePath(),
                                     (outputName == null)
                                         ? null
                                         : new File(outputName).getAbsolutePath(),
                                     flags,
                                     loader,
                                     elements);
    }
    //真正的加载逻辑是在openDexFileNative内部,也是在native层实现了dex优化为odex的工作耗时
    private static native Object openDexFileNative(String sourceName, String outputName, int flags,
                ClassLoader loader, DexPathList.Element[] elements);
    

    5.Native层源码跟踪:

    static jint DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {    
        //...略
        const DexFile* dex_file;    
        // 如果outputName为空,则dex_file由sourceName确定
        if (outputName.c_str() == NULL) {
            dex_file = linker->FindDexFileInOatFileFromDexLocation(dex_location, dex_location_checksum); 
        } else {
            //如果outputName不为空,则在outputName目录中去寻找dex_file
            std::string oat_location(outputName.c_str());    
            dex_file = linker->FindOrCreateOatFileForDexLocation(dex_location, dex_location_checksum, oat_location);  
        }    
        //...略
        return static_cast<jint>(reinterpret_cast<uintptr_t>(dex_file));    
    }
    

    判断传入的 outputName 是否为空,分别执行不同的方法,这个 outputName 就是 BaseDexClassLoader 构造方法中传入的 optimizedDirectory 参数, 当 outputName 不为空时【DexClassLoader】 执行FindOrCreateOatFileForDexLocation函数,通过 outputName拿到 oat_location即apk优化后的目录odex文件 ,然后尝试调用 FindDexFileInOatLocation 从 oat_location 中寻找到 dex ,这就是我们经常用到到热修复的原理了,通过在sd卡中存放新的补丁dex/jar/apk替代旧的,来实现更新。
    `

    const DexFile* ClassLinker::FindOrCreateOatFileForDexLocation(const std::string& dex_location,uint32_t dex_location_checksum,const std::string& oat_location) {
        WriterMutexLock mu(Thread::Current(), dex_lock_);   // 互锁
        return FindOrCreateOatFileForDexLocationLocked(dex_location, dex_location_checksum, oat_location);
    }
    
    const DexFile* ClassLinker::FindOrCreateOatFileForDexLocationLocked(const std::string& dex_location,uint32_t dex_location_checksum,const std::string& oat_location) {
        const DexFile* dex_file = FindDexFileInOatLocation(dex_location,dex_location_checksum,oat_location);
        if (dex_file != NULL) {
            // 如果顺利打开,则返回
            return dex_file;
        }
        const OatFile* oat_file = OatFile::Open(oat_location, oat_location, NULL,!Runtime::Current()->IsCompiler());
        if (oat_file == NULL) {
            return NULL;
        }
        const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, &dex_location_checksum);
        if (oat_dex_file == NULL) {
            return NULL;
        }
        const DexFile* result = oat_dex_file->OpenDexFile();
        return result;
    }
    

    当 outputName 为空时【PathClassLoader】 执行 FindDexFileInOatFileFromDexLocation 函数,从 dex_location 中拿到 dex 文件,这个 dex_location 也就是 BaseDexClassLoader 的 dexPath 参数中分割出来的某个存放文件的路径。在 Android 中,系统使用 PathClassLoader 来加载apk中的dex存放到Element数组中,因此apk中的classes.dex都是通过它来加载的。

    概述

    Android 中,apk 安装时,系统会使用 PathClassLoader 来加载apk文件中的dex,PathClassLoader的构造方法中,调用父类的构造方法,实例化出一个 DexPathList ,DexPathList 通过 makePathElements 在所有传入的dexPath 路径中,找到DexFile,存入 Element 数组,在应用启动后,所有的类都在 Element 数组中寻找,不会再次加载。

    DexPathList.png

    在热更新时,实现 DexClassLoader 子类,传入要更新的dex/apk/jar补丁文件路径(如sd卡路径中存放的patch.jar),通过反射拿到 DexPathList,得到补丁 Element 数组,再从Apk原本安装时使用的 PathClassLoader 中拿到旧版本的 Element 数组,合并新旧数组,将补丁放在数组最前面,这样一个类一旦在补丁 Element 中找到,就不会再次加载,这样就能替换旧 Element 中的旧类,实现热更新.


    hotfix

    相关文章

      网友评论

          本文标题:Android PathClassLoader 和 DexCla

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