Android平臺類載入流程原始碼分析
前言
其實大家都知道的Android是使用Java作為開發語言,但是他使用的虛擬機器卻並不是傳統的JVM,在4.4以前Android使用Dalvik虛擬機器,之後使用ART(Android Runtime).
Dalvik和ART與傳統的JVM不同的地方在於,為了更加高效的在移動終端執行,Google重新定義了一套Dalvik位元組碼,用於在Dalvik和ART虛擬機器上執行.
因此如果你希望自己在本地生成的Java程式碼能夠在Android機器上執行,就必須通過轉碼的形式生成一份Dex檔案.
生成Dex檔案
當我們完成Java程式碼的書寫後,需要使用dx
命令將Java程式碼由javac生成的class檔案編譯為Android虛擬機器可識別的dex檔案.
編譯命令如下
1 dx --dex --output 'dex' 'target'
其中dex代表將要輸出的dex檔名,target代表用於作為輸入的檔案,可以為class或jar檔案.
解壓生成的dex檔案後,你會發現一個叫做classes.dex的檔案,是不是很眼熟? 在每個Android的APK中都會存在這麼一個檔案,他才是所有程式碼的集合,類似於class檔案.
生成完了Dex檔案,接下來,讓我們看看如何在Android裝置上執行它.
載入Dex檔案
下面是一個Dex檔案載入的Demo:
1 Context context = getBaseContext();
2 DexClassLoader loader = new DexClassLoader("/sdcard/demo.jar",
3 context.getCacheDir().getAbsolutePath(), null, context.getClassLoader());
4 try {
5 Class<?> clazz = loader.loadClass("com.kifile.sample.Sample");
6 Object object = clazz.newInstance();
7 Method method = clazz .getDeclaredMethod("println");
8 if (method != null) {
9 method.invoke(object);
10 }
11 } catch (ClassNotFoundException e) {
12 e.printStackTrace();
13 } catch (InstantiationException e) {
14 e.printStackTrace();
15 } catch (IllegalAccessException e) {
16 e.printStackTrace();
17 } catch (NoSuchMethodException e) {
18 e.printStackTrace();
19 } catch (InvocationTargetException e) {
20 e.printStackTrace();
21 }
通過以上程式碼,能夠從位於/sdcard/demo.jar的檔案中讀取出一個”com.kifile.sample.Sample”類,並生成例項,動態呼叫其內部的”println”方法.
類載入流程分析
說了這麼多了,都是教大家怎麼在Android下動態載入java檔案,現在我們來深入瞭解一下Android的類載入流程.
傳統Java類載入流程
首先對於我們先簡單說一下傳統的Java程式的類載入流程.
幾乎每一個Java類進行載入的時候都是通過ClassLoader進行載入的,當涉及引用新的類時,系統會自動呼叫當前類的ClassLoader區載入新類.
在載入的時候,首先判斷類有無載入,然後再從父ClassLoader中嘗試載入Class(之所以優先從父ClassLoader載入,是為了方便其他的兄弟ClassLoader能夠複用這個Class),當父ClassLoader也無法找到時,才通過findClass方法進行自查詢.
Dex類載入流程
熟悉了這個之後,我們再來看看Android的類載入流程.
其實兩者的基本流程都是類似的:在能夠複用類的時候,儘量複用類,實在找不到Class,才自己去查詢,但Android程式畢竟同普通的Java程式有所不同,
首先Android程式裡所有元件的啟動均在ActivityThread中執行,同時在ActivityThread中會擁有一個LoadedApk的物件,在LoadedApk物件中,儲存了包相關的資料,例如Dex檔案和資原始檔存放地址.
當我們啟動Application,Activity,Service等元件的時候,系統從LoadedApk中獲取ClassLoader物件,然後通過loadClass的方式,載入類.
這個ClassLoader物件就是PathClassLoader,因此對於Android應用而言,他幾乎所有的類都是通過PathClassLoader進行載入(除了諸如String,System等類,他們在Dalvik虛擬機器建立時就註冊了).
所以我們需要關心的就是PathClassLoader的載入流程,PathClassLoader與上面樣例程式碼中的DexClassLoader一樣繼承自BaseDexClassLoader.
我們看看BaseDexClassLoader類載入器的載入流程圖.
以上檔案的原始碼路徑分別為:
$ANDROID/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java $ANDROID/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java $ANDROID/libcore/dalvik/src/main/java/dalvik/system/DexFile.java $ANDROID/art/runtime/native/dalvik_system_DexFile.cc $ANDROID/art/runtime/native/java_lang_VMClassLoader.cc
如上所示,當DexClassLoader需要載入類時,先通過VMClassLoader查詢該類是否已經載入,如果尚未載入就通過建立時傳入的dexPath地址獲取到dex包路徑,然後根據Dex包路徑去例項化Class物件,並存入虛擬機器中,然後返回.
當你成功的從ClassLoader獲取到Class時,其實在dalvik_system_DexFile中已經將當前類放置到ClassLoader對應的一張Hash表中,便於你下次重新獲取:
1 #$ANDROID/art/runtime/native/dalvik_system_DexFile.cc#DexFile_defineClassNative
2 class_linker->InsertDexFileInToClassLoader(soa.Decode<mirror::Object*>(dexFile),
3 class_loader.Get());
1 #$ANDROID/art/runtime/class_linker.cc#InsertDexFileInToClassLoader
2 class_table->InsertWithHash(klass, hash);
當我們通過ClassLoader獲取類時,通過以下程式碼,將ClassLoader本身傳入原生代碼塊,從ClassLoader對應的Hash表中查詢類是否已經定義.
1 protected final Class<?> findLoadedClass(String className) {
2 ClassLoader loader;
3 if (this == BootClassLoader.getInstance())
4 loader = null;
5 else
6 loader = this;
7 return VMClassLoader.findLoadedClass(loader, className);
8 }
以此,Android就完成了他的類載入流程.
原文地址: http://blog.kifile.com/android/2015/11/10/dex_class_loader.html