上兩篇文章分析了資源的加載和進程,Activity啓動相關的內容,這篇是Dex加載相關的內容了,本篇結束,咱們也就能夠開始對於一些熱修復,插件化框架的實現剖析了。java
上圖爲Android中ClassLoader的類圖,與JVM不一樣,Dalvik的虛擬機不能用ClassCload直接加載.dex,Android從ClassLoader派生出了兩個類:DexClassLoader
和PathClassLoader
。而這兩個類就是咱們加載dex文件的關鍵,這二者的區別是:android
DexClassLoader:能夠加載jar/apk/dex,能夠從SD卡中加載未安裝的apk。git
PathClassLoader:要傳入系統中apk的存放Path,因此只能加載已經安裝的apk文件。github
Dalvik虛擬機如同其餘Java虛擬機同樣,在運行程序時首先須要將對應的類加載到內存中。而在Java標準的虛擬機中,類加載能夠從class文件中讀取,也能夠是其餘形式的二進制流。所以,咱們經常利用這一點,在程序運行時手動加載Class,從而達到代碼動態加載執行的目的。編程
只不過Android平臺上虛擬機運行的是Dex字節碼,一種對class文件優化的產物,傳統Class文件是一個Java源碼文件會生成一個.class文件,而Android是把全部Class文件進行合併,優化,而後生成一個最終的class.dex,目的是把不一樣class文件重複的東西只需保留一份,若是咱們的Android應用不進行分dex處理,最後一個應用的apk只會有一個dex文件。數組
PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader,其中的主要邏輯都是在BaseDexClassLoader完成的。bash
能夠看出在加載類時首先判斷這個類是否以前被加載過,若是有則直接返回,若是沒有則首先嚐試讓parent ClassLoader進行加載,加載不成功纔在本身的findClass中進行加載。這和java虛擬機中常見的雙親委派模型一致的,這種模型並非一個強制性的約束模型,好比你能夠繼承ClassLoader複寫loadCalss方法來破壞這種模型,只不過雙親委派模是一種被推薦的實現類加載器的方式,並且jdk1.2之後已經不提倡用戶在覆蓋loadClass方法,而應該把本身的類加載邏輯寫到findClass中。數據結構
public class BaseDexClassLoader extends ClassLoader {
private final String originalPath;
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.originalPath = dexPath;
//構造DexPahtList對象
this.pathList =
new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//Class 查找在PathList中根據類名進行查找
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
@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);
}
@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;
}
@Override
public String toString() {
return getClass().getName() + "[" + originalPath + "]";
}
}
複製代碼
經過源碼能夠看出,在BaseDexClassLoader的構造函數中建立了DexPathList實例,在BaseDexClassLoader中,對於類的查找和資源的查找,都是經過其中的DexPathList實例來進行的。對於類的等相關信息的查找是在DexPathList中實現的,接下來,咱們看一下DexPathList的源碼實現。框架
final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String JAR_SUFFIX = ".jar";
private static final String ZIP_SUFFIX = ".zip";
private static final String APK_SUFFIX = ".apk";
/** class definition context */
private final ClassLoader definingContext;
//Dex,Resource元素
private final Element[] dexElements;
//Native庫的地址路徑
private final File[] nativeLibraryDirectories;
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;
this.dexElements =
makeDexElements(splitDexPath(dexPath), optimizedDirectory);
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
}
複製代碼
在構造函數中,根據dexPath,構建一個DexElement數組,在後面對於類的查找就會在該數組中進行查找。ide
/**
* 根據給予的地址構建一個Element數組
*/
private static Element[] makeDexElements(ArrayList<File> files,
File optimizedDirectory) {
ArrayList<Element> elements = new ArrayList<Element>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
ZipFile zip = null;
DexFile dex = null;
String name = file.getName();
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 if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
try {
zip = new ZipFile(file);
} catch (IOException ex) {
}
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ignored) {
}
} else {
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
複製代碼
根據傳遞的路徑,而後解析路徑下的內容,根據dex,zip等構建Element實例,而後將這個實例添加到Element數組之中。
/**
* 構造一個DexFile對象
*/
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);
}
}
複製代碼
Element數據結構
static class Element {
public final File file;
public final ZipFile zipFile;
public final DexFile dexFile;
public Element(File file, ZipFile zipFile, DexFile dexFile) {
this.file = file;
this.zipFile = zipFile;
this.dexFile = dexFile;
}
}
複製代碼
/**
* 類的查找,從DexElements中,逐個DexFile中查找,若是找到,
* 就加載這個類,而後返回
*/
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
複製代碼
根據傳遞的目錄,加載相應的dex,apk來構建層Element實例。
Android Runtime(縮寫爲ART),在Android 5.0及後續Android版本中做爲正式的運行時庫取代了以往的Dalvik虛擬機。ART可以把應用程序的字節碼轉換爲機器碼,是Android所使用的一種新的虛擬機。它與Dalvik的主要不一樣在於:Dalvik採用的是JIT技術,字節碼都須要經過即時編譯器轉換爲機器碼,這會拖慢應用的運行效率,而ART採用Ahead-of-time(AOT)技術,應用在第一次安裝的時候,字節碼就會預先編譯成機器碼,這個過程叫作預編譯。ART同時也改善了性能、垃圾回收(Garbage Collection)、應用程序出錯以及性能分析。可是請注意,運行時內存佔用空間較少一樣意味着編譯二進制須要更高的存儲。
ART模式相比原來的Dalvik,會在安裝APK的時候,使用Android系統自帶的dex2oat工具把APK裏面的.dex文件轉化成OAT文件,OAT文件是一種Android私有ELF文件格式,它不只包含有從DEX文件翻譯而來的本地機器指令,還包含有原來的DEX文件內容。這使得咱們無需從新編譯原有的APK就可讓它正常地在ART裏面運行,也就是咱們不須要改變原來的APK編程接口。ART模式的系統裏,一樣存在DexClassLoader類,包名路徑也沒變,只不過它的具體實現與原來的有所不一樣,可是接口是一致的。實際上,ART運行時就是和Dalvik虛擬機同樣,實現了一套徹底兼容Java虛擬機的接口。
在安裝的優化過程,dexopt函數會啓動一個子進程來執行優化,同時會根據目前系統使用的是Dalvik虛擬機仍是ART來決定將apk轉換層何種格式,若是轉換層odex格式,則調用/system/bin/dexopt文件來執行轉化,若是轉換層ART的oat格式,則調用/system/bin/dex2oat來執行轉換。
則dexopt和dexoat中會執行一些優化操做,這個優化操做也會影響到咱們後續類的動態加載。但因爲上層接口一致,所以,做爲開發者無需關心安裝時具體的優化方式。
Dexopt優化和驗證Dalvik (Dalvik Optimization and Verification With dexopt)