上兩篇文章分析了資源的加載和進程,Activity啓動相關的內容,這篇是Dex加載相關的內容了,本篇結束,咱們也就能夠開始對於一些熱修復,插件化框架的實現剖析了。java
上圖爲Android中ClassLoader的類圖,與JVM不一樣,Dalvik的虛擬機不能用ClassCload直接加載.dex,Android從ClassLoader派生出了兩個類:DexClassLoader
和PathClassLoader
。而這兩個類就是咱們加載dex文件的關鍵,這二者的區別是:android
Dalvik虛擬機如同其餘Java虛擬機同樣,在運行程序時首先須要將對應的類加載到內存中。而在Java標準的虛擬機中,類加載能夠從class文件中讀取,也能夠是其餘形式的二進制流。所以,咱們經常利用這一點,在程序運行時手動加載Class,從而達到代碼動態加載執行的目的。git
只不過Android平臺上虛擬機運行的是Dex字節碼,一種對class文件優化的產物,傳統Class文件是一個Java源碼文件會生成一個.class文件,而Android是把全部Class文件進行合併,優化,而後生成一個最終的class.dex,目的是把不一樣class文件重複的東西只需保留一份,若是咱們的Android應用不進行分dex處理,最後一個應用的apk只會有一個dex文件。github
PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader,其中的主要邏輯都是在BaseDexClassLoader完成的。編程
能夠看出在加載類時首先判斷這個類是否以前被加載過,若是有則直接返回,若是沒有則首先嚐試讓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數組,在後面對於類的查找就會在該數組中進行查找。框架
/** * 根據給予的地址構建一個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數組之中。ide
/** * 構造一個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)