美文网首页
DexClassLoader和PathClassLoader的区

DexClassLoader和PathClassLoader的区

作者: 嘟嘟赌起 | 来源:发表于2018-03-21 15:29 被阅读0次

    先说结论

    1、DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk
    2、PathClassLoader只能加载系统中已经安装过的apk

    PathClassLoader 源码

    以下源码全部来自Android6.0.1

    package dalvik.system;
    
    public class PathClassLoader extends BaseDexClassLoader {
    
        /** 有兴趣的可以看看注释,故意没删
        * Creates a {@code PathClassLoader} that operates on a given list of files
        * and directories. This method is equivalent to calling
        * {@link #PathClassLoader(String, String, ClassLoader)} with a
        * {@code null} value for the second argument (see description there).
        *
        * @param dexPath the list of jar/apk files containing classes and
        * resources, delimited by {@code File.pathSeparator}, which
        * defaults to {@code ":"} on Android
        * @param parent the parent class loader
        */
        public PathClassLoader(String dexPath, ClassLoader parent) {
            super(dexPath, null, null, parent);
        }
        /**
        * Creates a {@code PathClassLoader} that operates on two given
        * lists of files and directories. The entries of the first list
        * should be one of the following:
    JAR/ZIP/APK files, possibly containing a "classes.dex" file as
        * well as arbitrary resources.
        *
    Raw ".dex" files (not inside a zip file).
        *
        *
        * The entries of the second list should be directories containing
        * native library files.
        *
        * @param dexPath the list of jar/apk files containing classes and
        * resources, delimited by {@code File.pathSeparator}, which
        * defaults to {@code ":"} on Android
        * @param libraryPath the list of directories containing native
        * libraries, delimited by {@code File.pathSeparator}; may be
        * {@code null}
        * @param parent the parent class loader
        */
        public PathClassLoader(String dexPath, String libraryPath,
                ClassLoader parent) {
            super(dexPath, null, libraryPath, parent);
        }
    }
    

    DexClassLoader 源码

    package dalvik.system;
    
    import java.io.File;
    
    /**
     * A class loader that loads classes from {@code .jar} and {@code .apk} files
     * containing a {@code classes.dex} entry. This can be used to execute code not
     * installed as part of an application.
     *  * <p>This class loader requires an application-private, writable directory to
     * cache optimized classes. Use {@code Context.getCodeCacheDir()} to create
     * such a directory: <pre>   {@code
     *   File dexOutputDir = context.getCodeCacheDir();
     * }</pre>
     *  * <p><strong>Do not cache optimized classes on external storage.</strong>
     * External storage does not provide access controls necessary to protect your
     * application from code injection attacks.
     */
    public class DexClassLoader extends BaseDexClassLoader {
        /**
         * Creates a {@code DexClassLoader} that finds interpreted and native
         * code.  Interpreted classes are found in a set of DEX files contained
         * in Jar or APK files.
         *
         * <p>The path lists are separated using the character specified by the
         * {@code path.separator} system property, which defaults to {@code :}.
         *
         * @param dexPath the list of jar/apk files containing classes and
         *     resources, delimited by {@code File.pathSeparator}, which
         *     defaults to {@code ":"} on Android
         * @param optimizedDirectory directory where optimized dex files
         *     should be written; must not be {@code null}
         * @param libraryPath the list of directories containing native
         *     libraries, delimited by {@code File.pathSeparator}; may be
         *     {@code null}
         * @param parent the parent class loader
         */
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }
    }
    

    父类BaseDexClassLoader源码

    /**
     * Base class for common functionality between various dex-based
     * {@link ClassLoader} implementations.
     */
    public class BaseDexClassLoader extends ClassLoader {
        private final DexPathList pathList;
    
        /**
         * Constructs an instance.
         *
         * @param dexPath the list of jar/apk files containing classes and
         * resources, delimited by {@code File.pathSeparator}, which
         * defaults to {@code ":"} on Android
         * @param optimizedDirectory directory where optimized dex files
         * should be written; may be {@code null}
         * @param libraryPath the list of directories containing native
         * libraries, delimited by {@code File.pathSeparator}; may be
         * {@code null}
         * @param parent the parent class loader
         */
        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);
            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;
        }
    
        @Override
        protected URL findResource(String name) {
            return pathList.findResource(name);
        }
    
        @Override
        protected Enumeration<URL> findResources(String name) {
            return pathList.findResources(name);
        }
    
        @Override
        public String findLibrary(String name) {
            return pathList.findLibrary(name);
        }
    
        /**
         * Returns package information for the given package.
         * Unfortunately, instances of this class don't really have this
         * information, and as a non-secure {@code ClassLoader}, it isn't
         * even required to, according to the spec. Yet, we want to
         * provide it, in order to make all those hopeful callers of
         * {@code myClass.getPackage().getName()} happy. Thus we construct
         * a {@code Package} object the first time it is being requested
         * and fill most of the fields with dummy values. The {@code
         * Package} object is then put into the {@code ClassLoader}'s
         * package cache, so we see the same one next time. We don't
         * create {@code Package} objects for {@code null} arguments or
         * for the default package.
         *
         * <p>There is a limited chance that we end up with multiple
         * {@code Package} objects representing the same package: It can
         * happen when when a package is scattered across different JAR
         * files which were loaded by different {@code ClassLoader}
         * instances. This is rather unlikely, and given that this whole
         * thing is more or less a workaround, probably not worth the
         * effort to address.
         *
         * @param name the name of the class
         * @return the package information for the class, or {@code null}
         * if there is no package information available for it
         */
        @Override
        protected synchronized Package getPackage(String name) {
            if (name != null && !name.isEmpty()) {
                Package pack = super.getPackage(name);
    
                if (pack == null) {
                    pack = definePackage(name, "Unknown", "0.0", "Unknown",
                            "Unknown", "0.0", "Unknown", null);
                }
    
                return pack;
            }
    
            return null;
        }
    
        /**
         * @hide
         */
        public String getLdLibraryPath() {
            StringBuilder result = new StringBuilder();
            for (File directory : pathList.getNativeLibraryDirectories()) {
                if (result.length() > 0) {
                    result.append(':');
                }
                result.append(directory);
            }
    
            return result.toString();
        }
    
        @Override public String toString() {
            return getClass().getName() + "[" + pathList + "]";
        }
    }
    

    原因

    DexClassLoader构造函数

        //dexPath :dex路径
        //optimizedDirectory :制定输出dex优化后的odex文件,可以为null
        //libraryPath:动态库路径(将被添加到app动态库搜索路径列表中)
        //parent:制定父类加载器,以保证双亲委派机制从而实现每个类只加载一次。
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }
    

    PathClassLoader构造函数

        //dexPath :dex路径
        //optimizedDirectory :制定输出dex优化后的odex文件,可以为null
        //libraryPath:动态库路径(将被添加到app动态库搜索路径列表中)
        //parent:制定父类加载器,以保证双亲委派机制从而实现每个类只加载一次。
        public PathClassLoader(String dexPath, ClassLoader parent) {
            super(dexPath, null, null, parent);
        }
    
        public PathClassLoader(String dexPath, String libraryPath,
                ClassLoader parent) {
            super(dexPath, null, libraryPath, parent);
        }
    

    DexClassLoader 与 PathClassLoader 构造函数区别就是多了个optimizedDirectory参数。

    继续查看父类构造函数:

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

    和关键方法findClass

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
            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;
        }
    

    Class c = pathList.findClass(name, suppressedExceptions);中的pathList是在构造函数中new出来的,继续看DexPathList的findClass方法:

        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;
                    }
                }
            }
            if (dexElementsSuppressedExceptions != null) {
                suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
            }
            return null;
        }
    

    DexPathList会执行findClass时,会先将所有的dex包进行遍历,从每个包查找相对应name的class,只要找到,马上返回。

    这里有关于热修复实现的知识点,就是将补丁 dex 文件放到 dexElements 数组靠前位置,这样在加载 class 时,优先找到补丁包中的 dex 文件,加载到 class 之后就不再寻找,从而原来的 apk 文件中同名的类就不会再使用,从而达到修复的目的

    至于这个dexElements怎么创造出来的呢,在DexPathList构造方法里面创造的:

    public DexPathList(ClassLoader definingContext, String dexPath,
                String libraryPath, File optimizedDirectory) {
            //.....
            //省略其他不重要代码
            
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            // save dexPath for BaseDexClassLoader
            this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
    
            //.....
            //省略部分代码
        }
    

    继续看 makePathElements 方法:

        private static Element[] makePathElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions) {
            List<Element> elements = new ArrayList<>();
            for (File file : files) {
                File zip = null;
                File dir = new File("");
                DexFile dex = null;
                String path = file.getPath();
                String name = file.getName();
                if (path.contains(zipSeparator)) {
                    String split[] = path.split(zipSeparator, 2);
                    zip = new File(split[0]);
                    dir = new File(split[1]);
                } else if (file.isDirectory()) {
                    // We support directories for looking up resources and native libraries.
                    // Looking up resources in directories is 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(dir, false, zip, dex));
                }
            }
    
            return elements.toArray(new Element[elements.size()]);
        }
    

    通过遍历files(就是传入的dexPath),执行loadDexFile转变为dex,然后添加到elements中。
    到这里,依然没有看出有和无optimizedDirectory这个参数的区别,那就继续看loadDexFile这个方法:

        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);
            }
        }
    

    尼玛,终于找到区别的了...

     1. optimizedDirectory如果为null,loadDexFile返回的是new DexFile(file)
     2. optimizedDirectory 不为null,返回的是DexFile.loadDex(file.getPath(), optimizedPath, 0)
    

    继续查DexFile,看这两个的区别,先是new DexFile(File):

        public DexFile(File file) throws IOException {
            this(file.getPath());
        }
        public DexFile(String fileName) throws IOException {
            mCookie = openDexFile(fileName, null, 0);
            mFileName = fileName;
            guard.open("close");
            //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
        }
    
        private DexFile(String sourceName, String outputName, int flags) throws IOException {
            if (outputName != null) {
                try {
                    String parent = new File(outputName).getParent();
                    if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
                        throw new IllegalArgumentException("Optimized data directory " + parent
                                + " is not owned by the current user. Shared storage cannot protect"
                                + " your application from code injection attacks.");
                    }
                } catch (ErrnoException ignored) {
                    // assume we'll fail with a more contextual error later
                }
            }
    
            mCookie = openDexFile(sourceName, outputName, flags);
            mFileName = sourceName;
            guard.open("close");
            //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
        }
    

    最终指向了DexFile(String sourceName, String outputName, int flags)这个方法。

    继续看上面的DexFile.loadDex(file.getPath(), optimizedPath, 0)方法:

    static public DexFile loadDex(String sourcePathName, String outputPathName,
            int flags) throws IOException {
            return new DexFile(sourcePathName, outputPathName, flags);
        }
    

    同样指向的是new DexFile(sourcePathName, outputPathName, flags);
    上面两者区别是,outputPathName参数为不为空。

    好了,现在PathClassLoader 和 DexClassLoader的区别就变成了看这个outputPathName参数为不为空的区别了。
    而outputPathName为不为空,结果会有什么影响呢?继续看DexFile中构造函数openDexFile的源码:

        private static Object openDexFile(String sourceName, String outputName, int flags) throws IOException {
            return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null) ? null : new File(outputName).getAbsolutePath(), flags);
        }
    

    openDexFileNative是个Native方法,在源码art/runtime/native/dalvik_system_DexFile.cc中找到(不要问我怎么找到的,我也是看别人才找到的):

    static jobject DexFile_openDexFileNative(
        JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
      ScopedUtfChars sourceName(env, javaSourceName);
      ScopedUtfChars sourceName(env, javaSourceName);
      if (sourceName.c_str() == nullptr) {
        return 0;
      }
      NullableScopedUtfChars outputName(env, javaOutputName);
      if (env->ExceptionCheck()) {
        return 0;
      }
      ClassLinker* linker = Runtime::Current()->GetClassLinker();
      std::vector<std::unique_ptr<const DexFile>> dex_files;
      std::vector<std::string> error_msgs;
      dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
        //省略部分代码
        //......
      }
    }
    

    代码太多我只列出了追踪javaOutputName这个参数的关键代码,javaOutputName被转换成了NullableScopedUtfChars类型的 outputName

    NullableScopedUtfChars outputName(env, javaOutputName);
    

    然后这个outputName被linker的对象执行了OpenDexFilesFromOat方法使用:

    ClassLinker* linker = Runtime::Current()->GetClassLinker();
    //省略部分代码
    //.....
    dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
    

    有兴趣的可以在art\runtime\class_linker.cc文件中查找OpenDexFilesFromOat方法:

    std::vector<std::unique_ptr<const DexFile>> ClassLinker::OpenDexFilesFromOat(
        const char* dex_location, const char* oat_location,
        std::vector<std::string>* error_msgs) {
     //代码太多,只列出部分....
    //注意这里传入了oat_location,生成了OatFileAssistant 对象
    
      OatFileAssistant oat_file_assistant(dex_location, oat_location, kRuntimeISA,
         !Runtime::Current()->IsAotCompiler());
         
    //省略部分代码...
    
      // If we didn't have an up-to-date oat file open, try to load one from disk.
      //source_oat_file就是从已经加载过的ota文件中查找到的目标dex文件
      if (source_oat_file == nullptr) {
        // Update the oat file on disk if we can. This may fail, but that's okay.
        // Best effort is all that matters here.
        //source_oat_file为空的话,会通过oat_file_assistant.MakeUpToDate(&error_msg)方法在其他路径尝试加载
        if (!oat_file_assistant.MakeUpToDate(&error_msg)) {
          LOG(WARNING) << error_msg;
        }
    
        // Get the oat file on disk.
        std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
        if (oat_file.get() != nullptr) {
          // Take the file only if it has no collisions, or we must take it because of preopting.
          bool accept_oat_file = !HasCollisions(oat_file.get(), &error_msg);
    
    //省略部分代码
    
      // Fall back to running out of the original dex file if we couldn't load any
      // dex_files from the oat file.
      if (dex_files.empty()) {
        if (oat_file_assistant.HasOriginalDexFiles()) {
          if (Runtime::Current()->IsDexFileFallbackEnabled()) {
            if (!DexFile::Open(dex_location, dex_location, &error_msg, &dex_files)) {
              LOG(WARNING) << error_msg;
              error_msgs->push_back("Failed to open dex files from " + std::string(dex_location));
            }
          } else {
            error_msgs->push_back("Fallback mode disabled, skipping dex files.");
          }
        } else {
          error_msgs->push_back("No original dex files found for dex location "
              + std::string(dex_location));
        }
      }
      return dex_files;
    }
    

    主要看核心代码oat_file_assistant.MakeUpToDate,在oat_file_assistant.cc这个文件里:

    bool OatFileAssistant::MakeUpToDate(std::string* error_msg) {
      switch (GetDexOptNeeded()) {
        case kNoDexOptNeeded: return true;
        case kDex2OatNeeded: return GenerateOatFile(error_msg);
        case kPatchOatNeeded: return RelocateOatFile(OdexFileName(), error_msg);
        case kSelfPatchOatNeeded: return RelocateOatFile(OatFileName(), error_msg);
      }
      UNREACHABLE();
    }
    

    网上很多分析都不是基于Android6.0,外加因为c不太熟,这个方法分析理解如下(说的不对请指出啊):
    case kNoDexOptNeeded: return true;是指已经在中加载过的dex文件,只要apk安装过,运行过,都可以直接可以找到,不需要再加载了;
    case kDex2OatNeeded: return GenerateOatFile(error_msg);内存中没有的,就进行第一次加载dex,转换成oat文件。这个时候,如果之前传入的outputName为空的话,就不能加载了。
    下面两个case就看不太懂了。不过文章标题的问题似乎已经找到答案了。

    再次总结

    1、DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk 
    2、PathClassLoader只能加载系统中已经安装过的apk

    相关文章

      网友评论

          本文标题:DexClassLoader和PathClassLoader的区

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