Android中的ClassLoader
1. Android中有哪幾種ClassLoader?它們的作用和區(qū)別是什么?
有PathClassLoader, DexClassLoader, BootClassLoader
PathClassLoader: 用來加載系統(tǒng)類和應(yīng)用程序代碼(具體見PathClassLoader的構(gòu)造方法的注釋),只能本地文件系統(tǒng)上的文件和目錄,而不能是網(wǎng)絡(luò)上的
DexClassLoader: 用來加載dex,jar,apk,可以加載未安裝在應(yīng)用程序上的代碼(DexClassLoader需要提供一個應(yīng)用私有的可寫入的目錄,用來存放被優(yōu)化的classes)(具體可以看DexClassLoader構(gòu)造方法上英文的注釋文檔)
PathClassLoader, DexClassLoader 都是繼承自 BaseDexClassLoader,兩者都是調(diào)用的BaseDexClassLoader的方法
/**
* 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 PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
/**
* 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.getDir(String, int)} to create
* such a directory: <pre> {@code
* File dexOutputDir = context.getDir("dex", 0);
* }</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 DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
區(qū)別就在于optimizedDirectory是否為null。具體可以看BaseDexClassLoader的構(gòu)造方法
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.originalPath = dexPath;
this.pathList =
new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
從這里我們找到了DexPathList類,那么還需要看看DexPathList是干嘛的
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
...
this.dexElements =
makeDexElements(splitDexPath(dexPath), optimizedDirectory);
}
/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
private static Element[] makeDexElements(ArrayList<File> files,
File optimizedDirectory) {
ArrayList<Element> elements = new ArrayList<Element>();
for (File file : files) {
...
dex = loadDexFile(file, optimizedDirectory);
}
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);
}
}
...
/**
* Opens a DEX file from a given File object. This will usually be a ZIP/JAR
* file with a "classes.dex" inside.
*
* The VM will generate the name of the corresponding file in
* /data/dalvik-cache and open it, possibly creating or updating
* it first if system permissions allow. Don't pass in the name of
* a file in /data/dalvik-cache, as the named file is expected to be
* in its original (pre-dexopt) state.
*
* @param file
* the File object referencing the actual DEX file
*
* @throws IOException
* if an I/O error occurs, such as the file not being found or
* access rights missing for opening it
*/
public DexFile(File file) throws IOException {
this(file.getPath());
}
DexPathList主要是加載全部的dex文件,內(nèi)部通過dexElements屬性維護(hù)全部的dex
我們還需要看一下DexPathList類的loadDexFile方法,里面有我們關(guān)心的一個參數(shù)optimizedDirectory,optimizedDirectory為null時,構(gòu)造一個DexFile對象,不為null的時候,直接通過DexFile.loadDex(sourcePathName, outputPathName, flag)加載一個DexFile文件
先來看看DexFile.loadDex方法
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
return new DexFile(sourcePathName, outputPathName, flags);
}
public DexFile(String fileName) throws IOException {
mCookie = openDexFile(fileName, null, 0);
mFileName = fileName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}
private DexFile(String sourceName, String outputName, int flags) throws IOException {
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}
本質(zhì)上還是調(diào)用的DexFile的構(gòu)造方法,那么不同之處在哪,在于構(gòu)造方法里面openDexFile傳入的參數(shù)outputName,對于optimizedDirectory為null,傳入的outputName為null,這是為什么呢,因?yàn)镻athClassLoader加載的是已經(jīng)安裝的應(yīng)用,這部分classes已經(jīng)被optimized處理過了,位于/data/dalvik-cache/目錄下,不需要在為outputName指定路徑了,而對于DexClassLoader而言,加載的是沒有安裝過的dex文件,那么是需要將優(yōu)化的classes存儲到應(yīng)用的私有目錄。
BootClassLoader: 繼承的是ClassLoader類,作為PathClassLoader的父類加載器存在(也是Android中類加載器的根節(jié)點(diǎn)),也是一個package級別的類,對外獲取不到
2. 簡述ClassLoader的雙親委托模型
雙親委托模型,classLoader用于將一個class文件加載到虛擬機(jī)中,供程序運(yùn)行時使用,動態(tài)加載。
在加載class的過程中,首先會先判斷父加載器有沒有加載這個類,如果加載了,那么由父加載器返回這個class對象,如果沒有的話,那么就由當(dāng)前的類加載器加載。(父加載器也是按照這個過程來)
這樣做的好處是避免重復(fù)加載class,安全
3. 簡述雙親委托模型在熱修復(fù)領(lǐng)域的應(yīng)用
首先我們需要看一下,在Android中,如何加載一個類,看看ClassLoader的loadClass方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
...
c = findClass(name);
...
}
//對于android應(yīng)用程序中加載class的加載器,我們確定無疑就是PathClassLoader,而PathClassLoader繼承自BaseDexClassLoader,BaseDexClassLoader的findClass方法是重寫過的
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
pathList是什么呢,它的類型是DexPathList,DexPathList有一個成員變量叫做dexElements,維護(hù)了全部的dex文件,而我們要查找的類都是通過遍歷這個dexElements數(shù)組,來獲取到的。
在熱修復(fù)中,我們可以將修復(fù)后的bugFixdex插入到這個dexElements的最前面,那么在查找某一個類A(存在bug的話)的時候,就優(yōu)先從bugFixdex中查找到,并返回,而不會使用dexElements里面原來有問題的類A了,這樣就達(dá)到了修復(fù)bug的問題