Dalvik,ART與ODEX相愛相生

very_on發表於2018-04-11

如果你有這樣的問題:
1.Dalvik和ART的區別
2.DEX在Dalvik轉化為ODEX和ART中轉化為ODEX的過程有上面區別
3.multidex在dalvik上起作用,ART上使用的也是multidex麼(如果不是的話在application中寫入multidex.install會對apk啟動造成影響麼)

如果你比較“懶”,,懶得看老羅的原始碼分析,,,長篇大論
請“簡要”看完以下“簡要”內容

一:Dalvik和ART的區別#

Dalvik: Dalvik是Google公司自己設計用於Android平臺的Java虛擬機器它可以支援已轉換為 .dex(即Dalvik Executable)格式的Java應用程式的執行,.dex格式是專為Dalvik設計的一種壓縮格式,適合記憶體和處理器速度有限的系統。執行的是位元組碼,它是依靠Just-In-Time (JIT)機制去解釋位元組碼
ART:即Android Runtime,google為了替代Dalvik專門為Android研發的。Android KK為開發者推出,L版本正式上線。比替代品更高效省電,執行的是本地機器碼(也就是linux的ELF檔案格式),依靠Ahead-Of-Time (AOT)機制

二.在不同平臺DEX轉化為ODEX的過程#

簡化流程如下:


打包安裝執行簡化流程.png

這裡參考的是
http://blog.csdn.net/luoshengyang/article/details/18006645

android安裝過程原始碼分析:http://blog.csdn.net/luoshengyang/article/details/6747696
簡單來說,就是Android系統通過PackageManagerService來安裝APK,在安裝的過程,PackageManagerService會通過另外一個類Installer的成員函式dexopt來對APK裡面的dex位元組碼進行優化:

public final class Installer {  
    ......  
  
    public int dexopt(String apkPath, int uid, boolean isPublic) {  
        StringBuilder builder = new StringBuilder("dexopt");  
        builder.append(' ');  
        builder.append(apkPath);  
        builder.append(' ');  
        builder.append(uid);  
        builder.append(isPublic ? " 1" : " 0");  
        return execute(builder.toString());  
    }  
  
    ......  
}  

這個函式定義在檔案frameworks/base/services/java/com/android/server/pm/Installer.java中。Installer通過socket向守護程式installd傳送一個dexopt請求,這個請求是由installd裡面的函式dexopt來處理的。詳細過程請移步Android ART執行時無縫替換Dalvik虛擬機器的過程分析

int dexopt(const char *apk_path, uid_t uid, int is_public)
{
    struct utimbuf ut;
    struct stat apk_stat, dex_stat;
    char out_path[PKG_PATH_MAX];
    char dexopt_flags[PROPERTY_VALUE_MAX];
    char persist_sys_dalvik_vm_lib[PROPERTY_VALUE_MAX];
    char *end;
    int res, zip_fd=-1, out_fd=-1;

    ......

    /* The command to run depend ones the value of persist.sys.dalvik.vm.lib */
    property_get("persist.sys.dalvik.vm.lib", persist_sys_dalvik_vm_lib, "libdvm.so");

    /* Before anything else: is there a .odex file?  If so, we have
     * precompiled the apk and there is nothing to do here.
     */
    sprintf(out_path, "%s%s", apk_path, ".odex");
    if (stat(out_path, &dex_stat) == 0) {
        return 0;
    }

    if (create_cache_path(out_path, apk_path)) {
        return -1;
    }

    ......

    out_fd = open(out_path, O_RDWR | O_CREAT | O_EXCL, 0644);

    ......

    pid_t pid;
    pid = fork();
    if (pid == 0) {
        ......

        if (strncmp(persist_sys_dalvik_vm_lib, "libdvm", 6) == 0) {
            run_dexopt(zip_fd, out_fd, apk_path, out_path, dexopt_flags);
        } else if (strncmp(persist_sys_dalvik_vm_lib, "libart", 6) == 0) {
            run_dex2oat(zip_fd, out_fd, apk_path, out_path, dexopt_flags);
        } else {
            exit(69);   /* Unexpected persist.sys.dalvik.vm.lib value */
        }
        exit(68);   /* only get here on exec failure */
    } 

    ......
}
……
static void run_dexopt(int zip_fd, int odex_fd, const char* input_file_name,
    const char* output_file_name, const char* dexopt_flags)
{
    static const char* DEX_OPT_BIN = "/system/bin/dexopt";
    static const int MAX_INT_LEN = 12;      // '-'+10dig+'\0' -OR- 0x+8dig
    char zip_num[MAX_INT_LEN];
    char odex_num[MAX_INT_LEN];

    sprintf(zip_num, "%d", zip_fd);
    sprintf(odex_num, "%d", odex_fd);

    ALOGV("Running %s in=%s out=%s\n", DEX_OPT_BIN, input_file_name, output_file_name);
    execl(DEX_OPT_BIN, DEX_OPT_BIN, "--zip", zip_num, odex_num, input_file_name,
        dexopt_flags, (char*) NULL);
    ALOGE("execl(%s) failed: %s\n", DEX_OPT_BIN, strerror(errno));
}

static void run_dex2oat(int zip_fd, int oat_fd, const char* input_file_name,
    const char* output_file_name, const char* dexopt_flags)
{
    static const char* DEX2OAT_BIN = "/system/bin/dex2oat";
    static const int MAX_INT_LEN = 12;      // '-'+10dig+'\0' -OR- 0x+8dig
    char zip_fd_arg[strlen("--zip-fd=") + MAX_INT_LEN];
    char zip_location_arg[strlen("--zip-location=") + PKG_PATH_MAX];
    char oat_fd_arg[strlen("--oat-fd=") + MAX_INT_LEN];
    char oat_location_arg[strlen("--oat-name=") + PKG_PATH_MAX];

    sprintf(zip_fd_arg, "--zip-fd=%d", zip_fd);
    sprintf(zip_location_arg, "--zip-location=%s", input_file_name);
    sprintf(oat_fd_arg, "--oat-fd=%d", oat_fd);
    sprintf(oat_location_arg, "--oat-location=%s", output_file_name);

    ALOGV("Running %s in=%s out=%s\n", DEX2OAT_BIN, input_file_name, output_file_name);
    execl(DEX2OAT_BIN, DEX2OAT_BIN,
          zip_fd_arg, zip_location_arg,
          oat_fd_arg, oat_location_arg,
          (char*) NULL);
    ALOGE("execl(%s) failed: %s\n", DEX2OAT_BIN, strerror(errno));
}

函式定義在frameworks/native/cmds/installd/commands.c中 函式dexopt首先是讀取系統屬性persist.sys.dalvik.vm.lib的值,接著在/data/dalvik-cache目錄中建立一個odex檔案。這個odex檔案就是作為dex檔案優化後的輸出檔案。再接下來,函式dexopt通過fork來建立一個子程式。如果系統屬性persist.sys.dalvik.vm.lib的值等於libdvm.so,那麼該子程式就會呼叫函式run_dexopt來將dex檔案優化成odex檔案。另一方面,如果系統屬性persist.sys.dalvik.vm.lib的值等於libart.so,那麼該子程式就會呼叫函式run_dex2oat來將dex檔案翻譯成oat檔案,實際上就是將dex位元組碼翻譯成本地機器碼,並且儲存在一個oat檔案中。
函式run_dexopt通過呼叫/system/bin/dexopt來對dex位元組碼進行優化,而函式run_dex2oat通過呼叫/system/bin/dex2oat來將dex位元組碼翻譯成本地機器碼。注意,無論是對dex位元組碼進行優化,還是將dex位元組碼翻譯成本地機器碼,最終得到的結果都是儲存在相同名稱的一個odex檔案裡面的,但是前者對應的是一個dey檔案(表示這是一個優化過的dex),後者對應的是一個oat檔案(實際上是一個自定義的elf檔案,裡面包含的都是本地機器指令)。通過這種方式,原來任何通過絕對路徑引用了該odex檔案的程式碼就都不需要修改了。

三.oat檔案格式#

藉助羅大神的圖我們可以知道,OAT檔案本質上是一個ELF檔案,因此在最外層它具有一般ELF檔案的結構,例如它有標準的ELF檔案頭以及通過段(Section)來描述檔案內容。







Paste_Image.png

OAT檔案包含有兩個特殊的段oatdata和oatexec,前者包含有用來生成本地機器指令的dex檔案內容,後者包含有生成的本地機器指令,它們之間的關係通過儲存在oatdata段前面的oat頭部描述。

APK安裝過程中生成的OAT檔案的輸入只有一個DEX檔案,也就是來自於打包在要安裝的APK檔案裡面的classes.dex檔案。實際上,一個OAT檔案是可以由若干個DEX生成的。這意味著在生成的OAT檔案的oatdata段中,包含有多個DEX檔案。詳細分析請移步Android執行時ART載入OAT檔案的過程分析

四.multidex載入odex,multidex和oat的關係

MultiDex在dalvik虛擬機器上的簡要安裝過程:
將/data/app/apkName.apk路徑下解壓得到的classes2.dex, …, classesN.dex,依次寫入到/data/data/pkgName/code_cache/secondary-dexes/apkName.apk.classes2.zip等zip檔案的classes.dex中,並返回這個zip列表。然後針對這個zip列表執行安裝過程,具體過程是,將這個要安裝的zip列表加入BaseDexClassLoader的pathList例項的dexElements陣列中,其中會針對各dex檔案進行dex2opt優化。一旦加入到了dexElements陣列中,程式啟動的時候,ClassLoader會載入dexElements陣列中的元素,從而實現multi dex的安裝。

上面提到OAT檔案可以由若干個dex生成,也就是不需要multidex去進行安裝,但是multidex是application中
進行Install的,跟虛擬機器關係不大。

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
}

在install函式中在執行從提取dex檔案列表前會做一些校驗操作,其中包含檢查APK是否已安裝,若APK已安裝,則不進行後續操作。檢查SDK版本號,版本號大於20不能保證MultiDex可正常Work

Set var2 = installedApk;
                synchronized(installedApk) {
                    String apkPath = e.sourceDir;
                    if(installedApk.contains(apkPath)) {
                        return;
                    }

所以multidex在ART上不會影響程式的邏輯,它和ART沒有關係~。。。。
multidex原始碼分析:MultiDex安裝過程原始碼分析

小結:

從安裝過程上來看
Java的程式碼實際上需要兩次“轉換”才可以在android裝置上執行
一.PC端:.class->.dex->.apk
二.phone:dex->odex

區別在於第二步。
ART : .dex->.odex(機器碼)(AOT  Ahead-Of-Time)
Dalvik: .dex->.odex(位元組碼)(JIT Just-In-Time)
機器碼可直接執行,而位元組碼每次啟動都需要執行將優化過的odex位元組碼再轉換成機器碼

ART優缺####

系統效能大幅提升
App啟動、執行更快
減少每次啟動的編譯增加電池續航
儲存佔用更大
安裝時間更長



ART、JIT、AOT、Dalvik之間有什麼關係?

JIT與Dalvik

JIT是"Just In Time Compiler"的縮寫,就是"即時編譯技術",與Dalvik虛擬機器相關。

怎麼理解這句話呢?這要從Android的一些特性說起。

JIT是在2.2版本提出的,目的是為了提高Android的執行速度,一直存活到4.4版本,因為在4.4之後的ROM中,就不存在Dalvik虛擬機器了。

我們使用Java開發android,在編譯打包APK檔案時,會經過以下流程

  • Java編譯器將應用中所有Java檔案編譯為class檔案
  • dx工具將應用編譯輸出的類檔案轉換為Dalvik位元組碼,即dex檔案

之後經過簽名、對齊等操作變為APK檔案。

Dalvik虛擬機器可以看做是一個Java VM,他負責解釋dex檔案為機器碼,如果我們不做處理的話,每次執行程式碼,都需要Dalvik將dex程式碼翻譯為微處理器指令,然後交給系統處理,這樣效率不高。

為了解決這個問題,Google在2.2版本新增了JIT編譯器,當App執行時,每當遇到一個新類,JIT編譯器就會對這個類進行編譯,經過編譯後的程式碼,會被優化成相當精簡的原生型指令碼(即native code),這樣在下次執行到相同邏輯的時候,速度就會更快。

當然使用JIT也不一定加快執行速度,如果大部分程式碼的執行次數很少,那麼編譯花費的時間不一定少於執行dex的時間。Google當然也知道這一點,所以JIT不對所有dex程式碼進行編譯,而是隻編譯執行次數較多的dex為本地機器碼。

有一點需要注意,那就是dex位元組碼翻譯成本地機器碼是發生在應用程式的執行過程中的,並且應用程式每一次重新執行的時候,都要做重做這個翻譯工作,所以這個工作並不是一勞永逸,每次重新開啟App,都需要JIT編譯。

另外,Dalvik虛擬機器從Android一出生一直活到4.4版本,而JIT在Android剛釋出的時候並不存在,在2.2之後才被新增到Dalvik中。

ART與AOT

AOT是"Ahead Of Time"的縮寫,指的就是ART(Anroid RunTime)這種執行方式。

前面介紹過,JIT是執行時編譯,這樣可以對執行次數頻繁的dex程式碼進行編譯和優化,減少以後使用時的翻譯時間,雖然可以加快Dalvik執行速度,但是還是有弊病,那就是將dex翻譯為本地機器碼也要佔用時間,所以Google在4.4之後推出了ART,用來替換Dalvik。

在4.4版本上,兩種執行時環境共存,可以相互切換,但是在5.0+,Dalvik虛擬機器則被徹底的丟棄,全部採用ART。

ART的策略與Dalvik不同,在ART 環境中,應用在第一次安裝的時候,位元組碼就會預先編譯成機器碼,使其成為真正的本地應用。之後開啟App的時候,不需要額外的翻譯工作,直接使用本地機器碼執行,因此執行速度提高。

當然ART與Dalvik相比,還是有缺點的。

  • ART需要應用程式在安裝時,就把程式程式碼轉換成機器語言,所以這會消耗掉更多的儲存空間,但消耗掉空間的增幅通常不會超過應用程式碼包大小的20%
  • 由於有了一個轉碼的過程,所以應用安裝時間難免會延長

但是這些與更流暢的Android體驗相比而言,不值一提。

總結

通過前面背景知識的介紹,我終於可以更簡單的介紹這四個名詞之間的關係了:

  • JIT代表執行時編譯策略,也可以理解成一種執行時編譯器,是為了加快Dalvik虛擬機器解釋dex速度提出的一種技術方案,來快取頻繁使用的本地機器碼
  • ART和Dalvik都算是一種Android執行時環境,或者叫做虛擬機器,用來解釋dex型別檔案。但是ART是安裝時解釋,Dalvik是執行時解釋
  • AOT可以理解為一種編譯策略,即執行前編譯,ART虛擬機器的主要特徵就是AOT

複製貼上+理解,,,希望以後忘了能回來看看~!~



作者:SYfarming
連結:https://www.jianshu.com/p/389911e2cdfb
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

相關文章