#### **ClassLoade源碼分析**
> **注意**:
> 1. 下面的源碼是以Android6.0源碼為準
> 2. 由于Android API版本的升級,低版本的的源碼中DexClassLoader 和PathClassLoader可能和高版本的源碼不一樣,具體以高版本的源碼為準。
所有ClassLoader的源碼都在`/libcore/dalvik/src/main/java/dalvik/system/`目錄下。(**以Android6.0源碼為準**)
**背景**:
為了解決65535這個問題,Google提出了multidex方案,即一個apk文件可以包含多個dex文件。
不過值得注意的是,除了第一個dex文件以外,其他的dex文件都是以資源的形式被加載的, 換句話說就是在Application初始化前將dex文件注入到系統的ClassLoader中的。
根據Android虛擬機的類加載機制,同一個類只會被加載一次,所以熱修復也使用了這樣的機制,要讓修復后的類替換原有的類就必須讓補丁包的類被優先加載,也就是插入到原有dex之前。
首先需要知道的是android中兩個主要的Classloader,PathClassLoader和DexClassLoader, 它們都繼承自BaseDexClassLoader
Android系統通過PathClassLoader來加載系統類和主dex中的類。 而DexClassLoader則用于加載其他dex文件中的類。 他們都是繼承自BaseDexClassLoader,具體的加載方法是findClass。

:-: 圖1 ClassLoader加載類的整體流程
**為什么說PathClassLoader只能加載系統類和主dex的類呢?**,我們看一下這個類的源代碼
**PathClassLoader**
~~~
package dalvik.system;
/**
* Provides a simple {@link ClassLoader} implementation that operates on a list
* of files and directories in the local file system, but does not attempt to
* load classes from the network. Android uses this class for its system class
* loader and for its application class loader(s).
*/
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:
*
* <ul>
* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
* well as arbitrary resources.
* <li>Raw ".dex" files (not inside a zip file).
* </ul>
*
* 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);
}
}
~~~
源碼中的注釋,大概的意思是說 PathClassLoader被用來加載本地文件系統上的文件或目錄,但不能從網絡上加載;關鍵是它被用來加載系統類和我們的應用程序,這也是為什么它的兩個構造函數中調用父類構造器的時候第二個參數傳null, 可以查看下面
BaseDexClassLoader的源碼。
**DexClassLoader**
~~~
package dalvik.system;
/**
* 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**
~~~
package dalvik.system;
/**
* 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 + "]";
}
}
~~~
看一下構造上的注釋,參數解釋如下:
* dexPath: 需要被加載的dex文件地址,可以多個,用File.pathSeparator分割
* optimizedDirectory: dex文件被加載后會被編譯器優化,優化之后的dex存放路徑, 不可以為null。注意,注釋中也提到需要一個應用私有的可寫的一個路徑, 以防止應用被注入攻擊,并且給出了例子 File dexOutputDir = context.getDir(“dex”, 0);
* libraryPath:包含libraries的目錄列表,同樣用File.pathSeparator分割,如果沒有則傳null就行了
* parent:父類構造器
到這里估計會問不是說了optimizedDirectory不能傳null嗎,那PathClassLoader怎么傳了null呢?
我們繼續看一下BaseClassLoader的findClass,在BaseClassLoader的findClass方法中構造方法中創建了一個叫pathList的DexPathList類型的對象,然后在findClass的時候 代碼轉移到了pathList的findClass中,DexPathList又是什么呢我們進去看一下。通過下面的DexPathList的構造方法的源碼,可以知道第四個參數說optimizedDirectory 如果為空, 那么使用系統默認的文件夾,為什么PathClassLoader傳空就行呢,其實是 因為我們的應用已經安裝并優化了,優化后的dex存在于/data/dalvik-cache目錄下,這就是系統默認的文件夾, 這就是說為什么我們只能用DexClassLoader去加載其他類了
**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;
}
~~~
首先通過**findLoadedClass(className)**;判斷這個className的類文件是否已經加載過,如果沒有加載過,繼續判斷父類的**parent.loadClass(className, false)**;是否已經加載過,如果都沒有加載過,則調用**findClass(className)**;查找這個類,找到這個類,返回這個類。
**ClassLoader:findClass**
~~~
protected Class<?> findClass(String className) throws ClassNotFoundException {
throw new ClassNotFoundException(className);
}
~~~
可以看出這個方法是空實現,需要子類來實現。
**BaseDexClassLoader: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是BaseDexClassLoader的final變量,在其構造方法中就已經初始化
**BaseDexClassLoader:構造方法**
~~~
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
~~~
可以看到pathList是由DexPathList得到的,再通過DexPathList的findClass方法查找類
**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;
}
~~~
遍歷Element數組中元素(dex文件),得到dex文件,再通過DexFile的loadClassBinaryName方法得到字節碼。
dexElements是維護dex文件的數組, 每一個item對應一個dex文件。DexPathList遍歷dexElements,從每一個dex文件中查找目標類,在找到后即返回并停止遍歷。
**DexPathList的成員變量以及其初始化**
~~~
final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String zipSeparator = "!/";
private final ClassLoader definingContext;
private final Element[] dexElements;
private final Element[] nativeLibraryPathElements;
private final List<File> nativeLibraryDirectories;
private final List<File> systemNativeLibraryDirectories;
private final IOException[] dexElementsSuppressedExceptions;
/**
* Constructs an instance.
*
* @param definingContext the context in which any as-yet unresolved
* classes should be defined
* @param dexPath list of dex/resource path elements, separated by
* {@code File.pathSeparator}
* @param libraryPath list of native library directory path elements,
* separated by {@code File.pathSeparator}
* @param optimizedDirectory directory where optimized {@code .dex} files
* should be found and written to, or {@code null} to use the default
* system directory for same
*/
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>();
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;
}
}
}
~~~
Element是類DexPathList的一個內部類,它其中重要的一個變量就是DexFile,就是dex文件。
DexPathList的初始化中通過makePathElements方法
~~~
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
this.nativeLibraryDirectories = splitPaths(libraryPath, false);
~~~
makePathElements方法核心作用就是將指定路徑中的所有文件轉化成DexFile同時存儲到到Element[]這個數組中。nativeLibraryDirectories 就是lib庫了。
最終在findclass方法中實現。
**DexPathList:makePathElements**
~~~
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)) {
// 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) {
/*
* 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()]);
}
~~~
**DexPathList: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);
}
}
~~~
**DexFile:loadClassBinaryName、defineClass、defineClassNative方法**
~~~
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie)
throws ClassNotFoundException, NoClassDefFoundError;
~~~
defineClassNative是一個native層的方法。
- 前言
- Android 熱補丁技術——資源的熱修復
- 插件化系列詳解
- Dex分包——MultiDex
- Google官網——配置方法數超過 64K 的應用
- IMOOC熱修復與插件化筆記
- 第1章 class文件與dex文件解析
- Class文件解析
- dex文件解析
- class與dex對比
- 第2章 虛擬機深入講解
- 第3章 ClassLoader原理講解
- 類的加載過程
- ClassLoade源碼分析
- Android中的動態加載
- 第4章 熱修復簡單講解
- 第5章 熱修復AndFix詳解
- 第6章 熱修復Tinker詳解及兩種方式接入
- 第7章 引入熱修復后代碼及版本管理
- 第8章 插件化原理深入講解
- 第9章 使用Small完成插件化
- 第10章 使用Atlas完成插件化
- 第11章 課程整體總結
- DN學院熱修復插件化筆錄
- 插件化
- 熱修復
- Android APP開發應掌握的底層知識
- 概述
- Binder
- AIDL
- AMS
- Activity的啟動和通信原理
- App啟動流程第2篇
- App內部的頁面跳轉
- Context家族史
- Service
- BroadcastReceiver
- ContentProvider
- PMS及App安裝過程