1. 程式人生 > >Android平臺類載入流程原始碼分析

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