以下內容節選自本書
ART和dalvik相比,系統的性能得到了顯著提升,同時佔用的內存更少,因此能支持配置更低的設備。但是ART模式下編譯出來的文件會比以前增大10%-20%,系統需要更多的存儲空間,同時因爲在安裝時要執行編譯,應用的安裝時間也比以前更長了。
ART和dalvik相比,系統的性能得到了顯著提升,同時佔用的內存更少,因此能支持配置更低的設備。但是ART模式下編譯出來的文件會比以前增大10%-20%,系統需要更多的存儲空間,同時因爲在安裝時要執行編譯,應用的安裝時間也比以前更長了。
開啓ART模式
在Android中ART模式缺省是關閉的,如果我們希望啓動ART模式,需要在“設置”應用中手工打開它。進入“設置”後,選擇“開發者選項”,再點擊“選擇運行環境”,會彈出如選擇DALVIK或ART的對話框。選擇ART後,系統將會重新啓動,然後系統的運行環境就會從Dalvik切換到ART模式。
讓我們打開“設置”應用的代碼,看看這種切換到底做了些什麼。下面是DevelopmentSettings.java中的一段代碼:
builder.setPositiveButton(android.R.string.ok, newOnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SystemProperties.set(SELECT_RUNTIME_PROPERTY,newRuntimeValue);
pokeSystemProperties();
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
pm.reboot(null);
}
});
上面這段代碼只是設置重新設置了屬性SELECT_RUNTIME_PROPERTY的值,然後就重啓設備。這個屬性的定義如下:
String SELECT_RUNTIME_PROPERTY = "persist.sys.dalvik.vm.lib"
注意屬性是以persist開頭的,所以它的值在重新開機後也可以保存。這個屬性定義的是當前使用的運行環境庫,如果是dalvik,它的值爲libdvm.so,如果是art,它的值爲libart.so。通過這個屬性,系統就能判斷出需要運行的模式。
ART系統的源碼位於目錄art下,包含了以下目錄:
build目錄:存放了一些mk文件。
compiler目錄:動態庫libart-compiler.so的源碼目錄,它包含了一個ART的編譯器。
dalvikvm目錄:可執行文件dalvikvm的源碼目錄。
dex2oat目錄:可執行文件dex2oat的源碼目錄。用來將dex格式的文件編譯成可執行文件。dex2oat文件會鏈接libart-compiler動態庫。
jdwpspy目錄:可執行文件jdwpspy的源碼目錄。這是一個調試工具,通過socket接收VM中輸出的消息並打印輸出。
oatdump目錄:可執行文件oatdump的源碼目錄。這是一個工具,能dump出art文件的信息。
runtime目錄:動態庫libart.so的源碼目錄。
test目錄:包含了大量測試代碼的目錄。
tools目錄:包含了幾個腳本文件。
兩種模式的區別
從系統的實現角度看,ART和Dalvik的區別只表現在兩個地方。一處是在安裝應用執行優化時,Dalvik模式下執行的是dexopt程序,而ART模式下執行的是dex2oat程序。這個在“11.5.5節dexopt(優化)命令”中已經介紹過來。通過dex2oat程序編譯後得到的是elf格式的可執行文件,而不是以前的dalvik字節碼了。
另一處是在運行應用時,Dalvik模式下系統爲應用進程鏈接的是libdvm.so,而ART模式下鏈接的是libart.so。這兩個庫文件能夠相互替換,是因爲它們都實現了相同的接口。下面我們看看Android是如何來裝載不同運行庫的。
前面我們已經介紹過了,Zygote進程啓動時會調用AndroidRuntime的start()函數來啓動虛擬機,start()函數中有下面兩行代碼:
void AndroidRuntime::start(const char* className, const char*options)
{
......
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
......
}
我們看看jni_invocation的Init()函數的代碼:
bool JniInvocation::Init(const char* library) {
#ifdef HAVE_ANDROID_OS
char default_library[PROPERTY_VALUE_MAX]; //從property中得到運行庫的名稱
property_get(kLibrarySystemProperty,default_library, kLibraryFallback);
#else
const char* default_library =kLibraryFallback;
#endif
if (library == NULL) {
library =default_library;
}
handle_ = dlopen(library,RTLD_NOW); // 裝載運行庫
if (handle_ == NULL) {
...... //錯誤處理
}
// 裝載三個VM的入口函數。
if(!FindSymbol(reinterpret_cast(&JNI_GetDefaultJavaVMInitArgs_),
"JNI_GetDefaultJavaVMInitArgs")) {
returnfalse;
}
if(!FindSymbol(reinterpret_cast(&JNI_CreateJavaVM_),
"JNI_CreateJavaVM")) {
returnfalse;
}
if(!FindSymbol(reinterpret_cast(&JNI_GetCreatedJavaVMs_),
"JNI_GetCreatedJavaVMs")) {
returnfalse;
}
return true;
}
JniInvocation的Init()函數中通過屬性persist.sys.dalvik.vm.lib取得了運行庫的名稱,然後調用dlopen()來裝載運行庫,同時從動態庫中讀取了三個函數的地址,它們是JNI_GetDefaultJavaVMInitArgs(),JNI_CreateJavaVM()和JNI_GetCreatedJavaVMs()。這三個函數相當於是虛擬機的三個入口函數,通過調用它們,就能創建出虛擬機系統。