Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android ART運行時無縫替換Dalvik虛擬機的過程分析

Android ART運行時無縫替換Dalvik虛擬機的過程分析

編輯:關於Android編程

Android 4.4發布了一個ART運行時,准備用來替換掉之前一直使用的Dalvik虛擬機,希望籍此解決飽受诟病的性能問題。老羅不打算分析ART的實現原理,只是很有興趣知道ART是如何無縫替換掉原來的Dalvik虛擬機的。畢竟在原來的系統中,大量的代碼都是運行在Dalvik虛擬機裡面的。開始覺得這個替換工作是挺復雜的,但是分析了相關代碼之後,發現思路是很清晰的。本文就詳細分析這個無縫的替換過程。

老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!

我們知道,Dalvik虛擬機實則也算是一個Java虛擬機,只不過它執行的不是class文件,而是dex文件。因此,ART運行時最理想的方式也是實現為一個Java虛擬機的形式,這樣就可以很容易地將Dalvik虛擬機替換掉。注意,我們這裡說實現為Java虛擬機的形式,實際上是指提供一套完全與Java虛擬機兼容的接口。例如,Dalvik虛擬機在接口上與Java虛擬機是一致的,但是它的內部可以是完全不一樣的東西。

實際上,ART運行時就是真的和Dalvik虛擬機一樣,實現了一套完全兼容Java虛擬機的接口。為了方便描述,接下來我們就將ART運行時稱為ART虛擬機,它和Dalvik虛擬機、Java虛擬機的關系如圖1所示:

\

圖1 Java虛擬機、Dalvik虛擬機和ART運行時的關系

從圖1可以知道,Dalvik虛擬機和ART虛擬機都實現了三個用來抽象Java虛擬機的接口:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD48cD4gICAgICAgMS4gSk5JX0dldERlZmF1bHRKYXZhVk1Jbml0QXJncyAtLSC78cih0OnE4rv6tcTErMjPs/XKvLuvss7K/TwvcD48cD4gICAgICAgMi4gSk5JX0NyZWF0ZUphdmFWTSAtLSDU2r34s8zW0LS0vajQ6cTiu/rKtcD9PC9wPjxwPiAgICAgICAzLiBKTklfR2V0Q3JlYXRlZEphdmFWTXMgLS0gu/HIob34s8zW0LS0vai1xNDpxOK7+sq1wP08L3A+PHA+ICAgICAgINTaQW5kcm9pZM+1zbPW0KOsRGF2aWvQ6cTiu/rKtc/W1NpsaWJkdm0uc2/W0KOsQVJU0OnE4rv6yrXP1tTabGliYXJ0LnNv1tCho9Kyvs3Kx8u1o6xsaWJkdm0uc2+6zWxpYmFydC5zb7W8s/bBy0pOSV9HZXREZWZhdWx0SmF2YVZNSW5pdEFyZ3OhokpOSV9DcmVhdGVKYXZhVk26zUpOSV9HZXRDcmVhdGVkSmF2YVZNc9XiyP249r3Tv9qjrLmpzeK957X308OhozwvcD48cD4gICAgICAgtMvN4qOsQW5kcm9pZM+1zbO7uczhuanBy9K7uPbPtc2zyvTQ1HBlcnNpc3Quc3lzLmRhbHZpay52bS5saWKjrMv8tcTWtdKqw7S1yNPabGliZHZtLnNvo6zSqsO0tcjT2mxpYmFydC5zb6GjtbG1yNPabGliZHZtLnNvyrGjrL7Nse3KvrWxx7DTw7XEysdEYWx2aWvQ6cTiu/qjrLb4tbG1yNPabGliYXJ0LnNvyrGjrL7Nse3KvrWxx7DTw7XEysdBUlTQ6cTiu/qhozwvcD48cD4gICAgICAg0tTJz8Poyva1xERhbHZpa9DpxOK7+rrNQVJU0OnE4rv6tcS5ss2s1q60pqOstbHIu8v8w8fWrrzk1+7P1Nb4u7nKx7K7zazWrrSmoaOyu82stcS12Le9vs3U2tPao6xEYWx2aWvQ6cTiu/rWtNDQtcTKx2RleNfWvdrC66OsQVJU0OnE4rv61rTQ0LXEysexvrXYu/rG98LroaPV4tLizrbXxURhbHZpa9DpxOK7+rD8uqzT0NK7uPa94srNxvejrNPDwLTWtNDQZGV419a92sLro6y+38zlv8nS1LLOv7xEYWx2aWvQ6cTiu/q88tKqvenJ3LrN0afPsLzGu67V4rj2z7XB0LXEzsTVwqGjtbHIu6OsQW5kcm9pZLTTMi4yv6rKvKOs0rKw/Lqs09BKSVSjqEp1c3QtSW4tVGltZaOpo6zTw8C01NrUy9DQyrG2r8ystdi9q9a00NDGtcLKuty437XEZGV419a92sLrt63S67PJsb612Lv6xvfC66OsyLu689TZ1rTQ0KGjzai5/UpJVKOsvs2/ydLU09DQp7XYzOG430RhbHZpa9DpxOK7+rXE1rTQ0NCnwsqho7WrysejrL2rZGV419a92sLrt63S67PJsb612Lv6xvfC68rHt6LJ+tTa06bTw7PM0PK1xNTL0NC5/bPM1tC1xKOssqLH0tOm08OzzNDyw7/Su7TO1tjQwtTL0NC1xMqxuvKjrLa80qrX9tbY1/bV4rj2t63S67mk1/e1xKGj0vK0y6OsvLTKudPDssnTw8HLSklUo6xEYWx2aWvQ6cTiu/q1xNfczOXQ1MTcu7nKx7K7xNzT69axvdPWtNDQsb612Lv6xvfC67XEQVJU0OnE4rv6z+CxyKGjPC9wPjxwPiAgICAgICAgxMfDtKOsQVJU0OnE4rv61rTQ0LXEsb612Lv6xvfC68rHtNPExMDvwLS1xMTYo79BbmRyb2lktcTUy9DQyrG000RhbHZpa9DpxOK7+szmu7uzyUFSVNDpxOK7+qOssqKyu9Kqx/O/qrei1d/Sqr2r1tjQwr2r19S8urXE06bTw9axvdOx4NLrs8nEv7Hqu/rG98LroaPSsr7NysfLtaOsv6q3otXfv6q3orP2tcTTptPDs8zQ8r6tuf2x4NLrus208rD81q6686OsyNTIu8rH0ru49rD8uqxkZXjX1r3awuu1xEFQS87EvP6ho7zIyLvTptPDs8zQ8rD8uqy1xMjUyLvKx2RleNfWvdrC66OstvhBUlTQ6cTiu/rQ6NKqtcTKx7G+tdi7+sb3wuujrNXivs2x2Mi70qrT0NK7uPa3rdLrtcS5/bPMoaPV4rj2t63S67XEuf2zzLWxyLuyu8Tct6LJ+tOm08OzzNDy1MvQ0LXEyrG68qOst/HU8rXEu7C+zbrNRGFsdmlr0OnE4rv6tcRKSVTSu9H5wcuho9TavMbL47v6tcTKwL3nwO+jrNPrSklUz+C21LXEysdBT1Sho0FPVL34QWhlYWQtT2YtVGltZbXEvPKzxqOsy/y3osn61NqzzNDy1MvQ0Naux7Cho87Sw8fTw76yzKzT79HUo6jA/cjnQy9DKyujqcC0v6q3otOm08OzzNDytcTKsbryo6yx4NLrxvfWsb3Tvs2w0cv8w8e3rdLrs8nEv7Hqu/rG98LroaPV4tbWvrLMrNPv0dS1xLHg0uu3vcq90rLKx0FPVLXE0rvW1qGjtavKx8eww+bO0sPHzOG1vaOsQVJU0OnE4rv6sqKyu9Kqx/O/qrei1d+9q9fUvLq1xNOm08PWsb3TseDS67PJxL+x6rv6xvfC66Gj1eLR+aOsvavTptPDtcRkZXjX1r3awuu3rdLrs8mxvrXYu/rG98LrtcTX7sehtbFBT1TKsbv6vs23osn61NrTptPDsLLXsLXEyrG68qGjPC9wPjxwPiAgICAgICDO0sPH1qq1wKOsw7vT0EFSVNDpxOK7+taux7CjrNOm08PU2rCy17C1xLn9s8yjrMbkyrXSsrvh1rTQ0NK7tM6hsLet0uuhsbXEuf2zzKGj1ruyu7n91eK49qGwt63S66GxtcS5/bPMyse9q2RleNfWvdrC67340NDTxbuvo6zSsr7NysfTyWRleM7EvP7J+rPJb2RleM7EvP6ho9XiuPa5/bPM08mwstewt/7O8VBhY2thZ2VNYW5hZ2VyU2VydmljZcfrx/PK2LukvfizzGluc3RhbGxkwLTWtNDQtcSho7TT1eK49r3HtsjAtMu1o6zU2tOm08OwstewtcS5/bPM1tC9q2RleNfWvdrC67et0uuzybG+tdi7+sb3wuu21NStwLS1xNOm08OwstewwfezzLv5sb7Jz77Nsru74bL6yfrKssO007DP7KGjPC9wPjxwPiAgICAgICAg09DBy9LUyc+1xLGzvrDWqsq21q6686OsztLDx73Tz8LAtL7NtNPBvbj2vce2yMC0wcu94kFSVNDpxOK7+srHyOe6ztf2tb3O3rfszOa7u0RhbHZpa9DpxOK7+rXEo7o8L3A+PHA+ICAgICAgICAxLiBBUlTQ6cTiu/q1xMb0tq+5/bPMo7s8L3A+PHA+ICAgICAgICAyLiBEZXjX1r3awuu3rdLrs8mxvrXYu/rG98LrtcS5/bPMoaM8L3A+PHA+ICAgICAgICDO0sPH1qq1wKOsQW5kcm9pZM+1zbPU2sb0tq+1xMqxuvKjrLvhtLS9qNK7uPZaeWdvdGW9+LPMo6yz5LWx06bTw7PM0PK9+LPMt/W7r8b3oaNaeWdvdGW9+LPM1NrG9LavtcS5/bPM1tCjrNPWu+G0tL2o0ru49kRhbHZpa9DpxOK7+qGjWnlnb3RlvfizzMrHzai5/bi01sbX1Ly6wLS0tL2o0MK1xNOm08OzzNDyvfizzLXEoaPV4tLizrbXxVp5Z290Zb34s8y74b2r19S8urXERGFsdmlr0OnE4rv6uLTWxrj406bTw7PM0PK9+LPMoaPNqLn91eLW1re9yr2+zb/J0tS087TztdjM4bjf06bTw7PM0PK1xMb0tq/L2bbIo6zS8s6q1eLW1re9yr2x3MPiwcvDv9K7uPbTptPDs8zQ8r34s8zU2sb0tq+1xMqxuvK2vNKqyKW0tL2o0ru49kRhbHZpa6GjysLKtcnPo6xaeWdvdGW9+LPMzai5/dfUztK4tNbGtcS3vcq9wLS0tL2o06bTw7PM0PK9+LPMo6zKociltcSyu732vfbKx9Om08OzzNDyvfizzLS0vahEYWx2aWvQ6cTiu/q1xMqxvOSjrLu5xNzKocil06bTw7PM0PK9+LPMvNPU2Lj31tbPtc2zv+K6zc+1zbPXytS0tcTKsbzko6zS8s6qy/zDx9TaWnlnb3RlvfizzNbQ0tG+rbzT1Ni5/cHLo6yyosfS0rK74cGszaxEYWx2aWvQ6cTiu/rSu8bwuLTWxrW906bTw7PM0PK9+LPM1tDIpaGjudjT2lp5Z290Zb34s8y6zdOm08OzzNDyvfizzMb0tq+1xLj8tuDWqsq2o6y/ydLUss6/vEFuZHJvaWTPtc2zvfizzFp5Z290Zcb0tq+5/bPMtcTUtLT6wuu31s72us1BbmRyb2lk06bTw7PM0PK9+LPMxvS2r7n9s8y1xNS0tPrC67fWzvbV4sG9xqrOxNXCoaM8L3A+PHA+ICAgICAgICC8tMi706bTw7PM0PK9+LPMwO/D5rXERGFsdmlr0OnE4rv6trzKx7TTWnlnb3RlvfizzNbQuLTWxrn9wLS1xKOsxMfDtL3Tz8LAtM7Sw8e+zbzM0PhaeWdvdGW9+LPMysfI57rOtLS9qERhbHZpa9DpxOK7+rXEoaO000RhbHZpa9DpxOK7+rXExvS2r7n9s8y31s721eLGqs7E1cK/ydLU1qq1wKOsWnlnb3RlvfizzNbQtcREYWx2aWvQ6cTiu/rKx7TTQW5kcm9pZFJ1dGltZTo6c3RhcnTV4rj2uq/K/b+qyry0tL2otcSho9LytMujrL3Tz8LAtM7Sw8e+zb+0v7TV4rj2uq/K/bXEyrXP1qO6PC9wPjxwPjwvcD48cHJlIGNsYXNzPQ=="brush:java;">void AndroidRuntime::start(const char* className, const char* options) { ...... /* start the virtual machine */ JniInvocation jni_invocation; jni_invocation.Init(NULL); JNIEnv* env; if (startVm(&mJavaVM, &env) != 0) { return; } ...... /* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */ char* slashClassName = toSlashClassName(className); jclass startClass = env->FindClass(slashClassName); if (startClass == NULL) { ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else { jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { ALOGE("JavaVM unable to find main() in '%s'\n", className); /* keep going */ } else { env->CallStaticVoidMethod(startClass, startMeth, strArray); #if 0 if (env->ExceptionCheck()) threadExitUncaughtException(env); #endif } } ...... } 這個函數定義在文件frameworks/base/core/jni/AndroidRuntime.cpp中。

AndroidRutime類的成員函數start最主要是做了以下三件事情:

1. 創建一個JniInvocation實例,並且調用它的成員函數init來初始化JNI環境;

2. 調用AndroidRutime類的成員函數startVm來創建一個虛擬機及其對應的JNI接口,即創建一個JavaVM接口和一個JNIEnv接口;

3. 有了上述的JavaVM接口和JNIEnv接口之後,就可以在Zygote進程中加載指定的class了。

其中,第1件事情和第2件事情又是最關鍵的。因此,接下來我們繼續分析它們所對應的函數的實現。

JniInvocation類的成員函數init的實現如下所示:

#ifdef HAVE_ANDROID_OS
static const char* kLibrarySystemProperty = "persist.sys.dalvik.vm.lib";
#endif
static const char* kLibraryFallback = "libdvm.so";

bool JniInvocation::Init(const char* library) {
#ifdef HAVE_ANDROID_OS
  char default_library[PROPERTY_VALUE_MAX];
  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) {
    if (strcmp(library, kLibraryFallback) == 0) {
      // Nothing else to try.
      ALOGE("Failed to dlopen %s: %s", library, dlerror());
      return false;
    }
    // Note that this is enough to get something like the zygote
    // running, we can't property_set here to fix this for the future
    // because we are root and not the system user. See
    // RuntimeInit.commonInit for where we fix up the property to
    // avoid future fallbacks. http://b/11463182
    ALOGW("Falling back from %s to %s after dlopen error: %s",
          library, kLibraryFallback, dlerror());
    library = kLibraryFallback;
    handle_ = dlopen(library, RTLD_NOW);
    if (handle_ == NULL) {
      ALOGE("Failed to dlopen %s: %s", library, dlerror());
      return false;
    }
  }
  if (!FindSymbol(reinterpret_cast(&JNI_GetDefaultJavaVMInitArgs_),
                  "JNI_GetDefaultJavaVMInitArgs")) {
    return false;
  }
  if (!FindSymbol(reinterpret_cast(&JNI_CreateJavaVM_),
                  "JNI_CreateJavaVM")) {
    return false;
  }
  if (!FindSymbol(reinterpret_cast(&JNI_GetCreatedJavaVMs_),
                  "JNI_GetCreatedJavaVMs")) {
    return false;
  }
  return true;
}
這個函數定義在文件libnativehelper/JniInvocation.cpp中。

JniInvocation類的成員函數init所做的事情很簡單。它首先是讀取系統屬性persist.sys.dalvik.vm.lib的值。前面提到,系統屬性persist.sys.dalvik.vm.lib的值要麼等於libdvm.so,要麼等於libart.so。因此,接下來通過函數dlopen加載到進程來的要麼是libdvm.so,要麼是libart.so。無論加載的是哪一個so,都要求它導出JNI_GetDefaultJavaVMInitArgs、JNI_CreateJavaVM和JNI_GetCreatedJavaVMs這三個接口,並且分別保存在JniInvocation類的三個成員變量JNI_GetDefaultJavaVMInitArgs_、JNI_CreateJavaVM_和JNI_GetCreatedJavaVMs_中。這三個接口也就是前面我們提到的用來抽象Java虛擬機的三個接口。

從這裡就可以看出,JniInvocation類的成員函數init實際上就是根據系統屬性persist.sys.dalvik.vm.lib來初始化Dalvik虛擬機或者ART虛擬機環境。

接下來我們繼續看AndroidRutime類的成員函數startVm的實現:

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
{
    ......

    /*
     * Initialize the VM.
     *
     * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
     * If this call succeeds, the VM is ready, and we can start issuing
     * JNI calls.
     */
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
        ALOGE("JNI_CreateJavaVM failed\n");
        goto bail;
    }

    ......
}
這個函數定義在文件frameworks/base/core/jni/AndroidRuntime.cpp中。

AndroidRutime類的成員函數startVm最主要就是調用函數JNI_CreateJavaVM來創建一個JavaVM接口及其對應的JNIEnv接口:

extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  return JniInvocation::GetJniInvocation().JNI_CreateJavaVM(p_vm, p_env, vm_args);
}
這個函數定義在文件libnativehelper/JniInvocation.cpp中。

JniInvocation類的靜態成員函數GetJniInvocation返回的便是前面所創建的JniInvocation實例。有了這個JniInvocation實例之後,就繼續調用它的成員函數JNI_CreateJavaVM來創建一個JavaVM接口及其對應的JNIEnv接口:

jint JniInvocation::JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  return JNI_CreateJavaVM_(p_vm, p_env, vm_args);
}
這個函數定義在文件libnativehelper/JniInvocation.cpp中。

JniInvocation類的成員變量JNI_CreateJavaVM_指向的就是前面所加載的libdvm.so或者libart.so所導出的函數JNI_CreateJavaVM,因此,JniInvocation類的成員函數JNI_CreateJavaVM返回的JavaVM接口指向的要麼是Dalvik虛擬機,要麼是ART虛擬機。

通過上面的分析,我們就很容易知道,Android系統通過將ART運行時抽象成一個Java虛擬機,以及通過系統屬性persist.sys.dalvik.vm.lib和一個適配層JniInvocation,就可以無縫地將Dalvik虛擬機替換為ART運行時。這個替換過程設計非常巧妙,因為涉及到的代碼修改是非常少的。

以上就是ART虛擬機的啟動過程,接下來我們再分析應用程序在安裝過程中將dex字節碼翻譯為本地機器碼的過程。

Android應用程序的安裝過程可以參考Android應用程序安裝過程源代碼分析這篇文章。 簡單來說,就是Android系統通過PackageManagerService來安裝APK,在安裝的過程,PackageManagerService會通過另外一個類Instalerl的成員函數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來處理的:

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 */
    } 

    ......
}

這個函數定義在文件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文件優化成oart文件,實際上就是將dex字節碼翻譯成本地機器碼,並且保存在一個oat文件中。

函數run_dexopt和run_dex2oat的實現如下所示:

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中。

這從裡就可以看出,函數run_dexopt通過調用/system/bin/dexopt來對dex字節碼進行優化,而函數run_dex2oat通過調用/system/bin/dex2oat來將dex字節碼翻譯成本地機器碼。注意,無論是對dex字節碼進行優化,還是將dex字節碼翻譯成本地機器碼,最終得到的結果都是保存在相同名稱的一個odex文件裡面的,但是前者對應的是一個dexy文件(表示這是一個優化過的dex),後者對應的是一個oat文件(實際上是一個自定義的elf文件,裡面包含的都是本地機器指令)。通過這種方式,原來任何通過絕對路徑引用了該odex文件的代碼就都不需要修改了。

通過上面的分析,我們就很容易知道,只需要將dex文件的優化過程替換成dex文件翻譯成本地機器碼的過程,就可以輕松地在應用安裝過程,無縫地將Dalvik虛擬機替換成ART運行時。

最後,還有一個地方需要注意的是,應用程序的安裝發生在兩個時機,第一個時機是系統啟動的時候,第二個時機系統啟動完成後用戶自行安裝的時候。在第一個時機中,系統除了會對/system/app和/data/app目錄下的所有APK進行dex字節碼到本地機器碼的翻譯之外,還會對/system/framework目錄下的APK或者JAR文件,以及這些APK所引用的外部JAR,進行dex字節碼到本地機器碼的翻譯。這樣就可以保證除了應用之外,系統中使用Java來開發的系統服務,也會統一地從dex字節碼翻譯成本地機器碼。也就是說,將Android系統中的Dalvik虛擬機替換成ART運行時之後,系統中的代碼都是由ART運行時來執行的了,這時候就不會對Dalvik虛擬機產生任何的依賴。

至此,我們就分析完成ART運行時無縫替換Dalvik虛擬機的過程了,更多的干貨分享衣關注老羅的新浪微博:http://weibo.com/shengyangluo。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved