基础概念

  • Android中的ClassLoader类型可分为系统ClassLoader自定义ClassLoader。其中系统ClassLoader包括3种分别是:

    • BootClassLoader,Android系统启动时会使用BootClassLoader来预加载常用类,与Java中的Bootstrap ClassLoader不同的是,它并不是由C/C++代码实现,而是由Java实现的。BootClassLoaderClassLoader的一个内部类。

      • PathClassLoader,只能加载系统中已经安装过的apk。
      • DexClassLoader,是一个可以从包含classes.dex实体的.jar或.apk文件中加载classes的类加载器。可以用于实现dex的动态加载、代码热更新等等。可以加载jar/apk/dex,可以从SD卡中加载未安装的apk。
      • PathClassLoaderDexClasLoader都是继承自 BaseDexClassLoader,它们的类加载逻辑全部写在BaseDexClassLoader中。

clipboard.png

源码分析

DexClassLoader分析

DexClassLoader.java

位置:~/android6.0.1_r66/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
继承BaseDexClassLoader
public class DexClassLoader extends BaseDexClassLoader {   
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

参数:

  • dexpath:jar或apk文件目录
  • optimizedDirectory:优化dex缓存目录
  • libraryPath:包含native lib的目录路径
  • parent:父类加载器

BaseDexClassLoader.java

位置:~/android6.0.1_r66/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
继承ClassLoader
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);
    }
    ...
}

首先调用父类的构造函数super(parent):ClassLoader
然后调用并初始化了DexPathList类型的对象pathList。

ClassLoader.java

位置:~/android6.0.1_r66/libcore/libart/src/main/java/java/lang/ClassLoader.java
public abstract class ClassLoader {
...
protected ClassLoader(ClassLoader parentLoader) {
        this(parentLoader, false);
    }
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
        if (parentLoader == null && !nullAllowed) {
            throw new NullPointerException("parentLoader == null && !nullAllowed");
        }
        parent = parentLoader;
    }
...
}

该构造函数把传进来的父类加载器赋给了私有变量parent。

DexPathList.java

位置:~/android6.0.1_r66/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String zipSeparator = "!/";
    
    ...
    
    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {

        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }

        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }

        if (optimizedDirectory != null) {
            if (!optimizedDirectory.exists())  {
                throw new IllegalArgumentException(
                        "optimizedDirectory doesn't exist: "
                        + optimizedDirectory);
            }

            if (!(optimizedDirectory.canRead()
                            && optimizedDirectory.canWrite())) {
                throw new IllegalArgumentException(
                        "optimizedDirectory not readable/writable: "
                        + optimizedDirectory);
            }
        }

        this.definingContext = definingContext;

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,                                            suppressedExceptions);
                                             this.nativeLibraryDirectories = splitPaths(libraryPath, false);
        this.systemNativeLibraryDirectories =                splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
                allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null,
                                                          suppressedExceptions);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }
}

首先对传入参数的验证,然后调用makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader)方法

private static Element[] makePathElements(List<File> files, File optimizedDirectory,
                                              List<IOException> suppressedExceptions) {
        List<Element> elements = new ArrayList<>();
        /*
         * Open all files and load the (direct or contained) dex files
         * up front.
         */
        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)) {//原始的dex文件处理,而不是在zip或者jar中的dex文件                  
                    try {
                        dex = loadDexFile(file, optimizedDirectory);
                    } catch (IOException ex) {
                        System.logE("Unable to load dex file: " + file, ex);
                    }
                } else {//处理zip或者jar包中的dex文件
                    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 {
                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()]);
    }

dex文件、zip或者jar包中的dex文件都会调用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);
        }
    }

如果optimizedDirectory为null就会新建一个DexFile对象,调用DexFile的构造函数。

位置:~/android6.0.1_r66/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
...
 public DexFile(File file) throws IOException {
        this(file.getPath());
    }
 
public DexFile(String fileName) throws IOException {//fileName就是上一个构造函数的file.getPath()
        mCookie = openDexFile(fileName, null, 0);
        mFileName = fileName;
        guard.open("close");
}    
...

关键函数是openDexFile。

private static Object openDexFile(String sourceName, String outputName, int flags) throws IOException {
        // Use absolute paths to enable the use of relative paths when testing on host.
        return openDexFileNative(new File(sourceName).getAbsolutePath(),
                                 (outputName == null) ? null : new File(outputName).getAbsolutePath(),
                                 flags);
    }

openDexFileNative是一个native函数,需要找到具体实现:
grep -rns:-r指定要查找的是目录 -n显示行号 -s不显示错误信息
clipboard.png
clipboard.png
clipboard.png
所以openDexFileNative的具体实现就是:

位置:~/android6.0.1_r66/art/runtime/native/dalvik_system_DexFile.cc
static jobject DexFile_openDexFileNative(
    JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
  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);

  if (!dex_files.empty()) {
    jlongArray array = ConvertNativeToJavaArray(env, dex_files);
    if (array == nullptr) {
      ScopedObjectAccess soa(env);
      for (auto& dex_file : dex_files) {
        if (Runtime::Current()->GetClassLinker()->IsDexFileRegistered(*dex_file)) {
          dex_file.release();
        }
      }
    }
    return array;
  } else {
    ScopedObjectAccess soa(env);
    CHECK(!error_msgs.empty());
    // The most important message is at the end. So set up nesting by going forward, which will
    // wrap the existing exception as a cause for the following one.
    auto it = error_msgs.begin();
    auto itEnd = error_msgs.end();
    for ( ; it != itEnd; ++it) {
      ThrowWrappedIOException("%s", it->c_str());
    }

    return nullptr;
  }
}

wydong
40 声望5 粉丝

wyd