[原]Android WebView加載Chromium動態庫的過程分析

Chromium動態庫的體積比較大,有27M左右,其中程序段和數據段分別占據25.65M和1.35M。如果按照通常方式加載Chromium動態庫,那么當有N個正在運行的App使用WebView時,系統需要為Chromium動態庫分配的內存為(25.65 + N x 1.35)M。這是非常可觀的。為此,Android使用了特殊的方式加載Chromium動態庫。本文接下來就詳細分析這種特殊的加載方式。

為什么當有N個正在運行的App使用WebView時,系統需要為Chromium動態庫分配的內存為(25.65 + N x 1.35)M呢?這是由于動態庫的程序段是只讀的,可以在多個進程之間進行共享,但是數據段一般是可讀可寫的,不能共享。在1.35M的數據段中,有1.28M在Chromium動態庫加載完成后就是只讀的。這1.28M數據包含有C++虛函數表,以及指針類型的常量等,它們在編譯的時候會放在一個稱為GNU_RELRO Section中,如圖1所示:

圖1 App進程間不共享GNU_RELRO Section

如果我們將該GNU_RELRO Section看作是一般的數據段,那么系統就需要為每一個使用了WebView的App進程都分配一段1.28M大小的內存空間。前面說到,這1.28M數據在Chromium動態庫加載完成后就是只讀的,那么有沒有辦法讓它像程序段一樣,在多個App進程之間共享呢?

只要能滿足一個條件,那么答案就是肯定的。這個條件就是所有的App進程都在相同的虛擬地址空間加載Chromium動態庫。在這種情況下,就可以在系統啟動的過程中,創建一個臨時進程,并且在這個進程中加載Chromium動態庫。假設Chromium動態庫的加載地址為Base Address。加載完成后,將Chromium動態庫的GNU_RELRO Section的內容Dump出來,并且寫入到一個文件中去。

以后App進程加載Chromium動態庫時,都將Chromium動態庫加載地址Base Address上,并且使用內存映射的方式將前面Dump出來的GNU_RELRO文件代替Chromium動態庫的GNU_RELRO Section。這樣就可以實現在多個App進程之間共享Chromium動態庫的GNU_RELRO Section了,如圖2所示:

圖2 App進程間共享 GNU_RELRO Section

從前面 Android應用程序進程啟動過程的源代碼分析 一文可以知道,所有的App進程都是由Zygote進程fork出來的,因此,要讓所有的App進程都在相同的虛擬地址空間加載Chromium動態庫的最佳方法就是在 Zygote進程的地址空間中預留一塊地址。

還有兩個問題需要解決。第一個問題是要將加載后的Chromium動態庫的GNU_RELRO Section的內容Dump出來,并且寫入到一個文件中去。第二個問題是要讓所有的App進程將Chromium動態加載在指定位置,并且可以使用指定的文件來代替它的 GNU_RELRO Section。這兩個問題都可以通過Android在5.0版本提供的一個動態庫加載函數android_dlopen_ext解決。

接下來,我們就結合源碼,分析 Zygote進程是如何為App進程預留地址加載Chromium動態庫的,以及App進程如何使用新的 動態庫加載函數 android_dlopen_ext加載 Chromium動態庫的。

從前面 Android系統進程Zygote啟動過程的源代碼分析 一文可以知道,Zygote進程在Java層的入口函數為ZygoteInit類的靜態成員函數main,它的實現如下所示:

public class ZygoteInit {
    ......

    public static void main(String argv[]) {
        try {
            .......

            preload();
            .......

            if (startSystemServer) {
                startSystemServer(abiList, socketName);
            }

            ......
            runSelectLoop(abiList);

            ......
        } catch (MethodAndArgsCaller caller) {
            ......
        } catch (RuntimeException ex) {
            ......
        }
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中。

ZygoteInit類的靜態成員函數main 在啟動System進程以及使得Zygote進程進入運行狀態之前,首先會調用另外一個靜態成員函數preload預加載資源。這些預加載的資源以后就可以在App進程之間進行共享。

ZygoteInit類的靜態成員函數preload 在預加載資源的過程中,就會為Chromium動態庫預保留加載地址,如下所示:

public class ZygoteInit {
    ......

    static void preload() {
        ......
        // Ask the WebViewFactory to do any initialization that must run in the zygote process,
        // for memory sharing purposes.
        WebViewFactory.prepareWebViewInZygote();
        ......
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中。

ZygoteInit類的靜態成員函數preload是通過調用WebViewFactory類的靜態成員函數prepareWebViewInZygote為Chromium動態庫預保留加載地址的,后者的實現如下所示:

public final class WebViewFactory {
    ......

    public static void prepareWebViewInZygote() {
        try {
            System.loadLibrary("webviewchromium_loader");
            long addressSpaceToReserve =
                    SystemProperties.getLong(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
                    CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
            sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);

            ......
        } catch (Throwable t) {
            ......
        }
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

WebViewFactory類的靜態成員函數prepareWebViewInZygote首先會加載一個名稱為“webviewchromium_loader”的動態庫,接下來又會獲得需要為Chromium動態庫預留的地址空間大小addressSpaceToReserve。知道了要預留的地址空間的大小之后,WebViewFactory類的靜態成員函數prepareWebViewInZygote就會調用另外一個靜態成員函數nativeReserveAddressSpace為Chromium動態庫預留地址空間。

WebViewFactory類的靜態成員函數nativeReserveAddressSpace是一個JNI方法,它在C++層對應的函數為ReserveAddressSpace。這個函數實現在上述名稱為“webviewchromium_loader”的動態庫中,如下所示:

jboolean ReserveAddressSpace(JNIEnv*, jclass, jlong size) {
  return DoReserveAddressSpace(size);
}

這個函數定義在文件frameworks/webview/chromium/loader/loader.cpp中。

函數ReserveAddressSpace調用另外一個函數DoReserveAddressSpace預留大小為size的地址空間,如下所示:

void* gReservedAddress = NULL;
size_t gReservedSize = 0;

jboolean DoReserveAddressSpace(jlong size) {
  size_t vsize = static_cast<size_t>(size);

  void* addr = mmap(NULL, vsize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  ......

  gReservedAddress = addr;
  gReservedSize = vsize;
  ......

  return JNI_TRUE;
}

這個函數定義在文件frameworks/webview/chromium/loader/loader.cpp中。

函數DoReserveAddressSpace是通過系統接口mmap預留指定大小的地址空間的。同時,預留出來的地址空間的起始地址和大小分別記錄在全局變量gReservedAddress和gReservedSize中。以后Chromium動態庫就可以加載在該地址空間中。

為Chromium動態庫預留好加載地址之后,Android系統接下來要做的一件事情就是請求Zygote進程啟動一個臨時進程。這個臨時進程將會在上述預留的地址空間中加載Chromium動態庫。這個Chromium動態庫在加載的過程中,它的 GNU_RELRO Section會被重定位。重定位完成之后,就可以將它的內容Dump到一個文件中去。這個文件以后就會以內存映射的方式代替在App進程中加載的Chromium動態庫的 GNU_RELRO Section。

請求Zygote進程啟動臨時進程的操作是由System進程完成的。從前面 Android系統進程Zygote啟動過程的源代碼分析 一文可以知道,System進程是由Zygote進程啟動的,它在Java層的入口函數為SystemServer的靜態成員函數main,它的實現如下所示:

public final class SystemServer {
    ......

    public static void main(String[] args) {
        new SystemServer().run();
    }

    ......
}

這個函數定義在文件frameworks/base/services/java/com/android/server/SystemServer.java中。

SystemServer的靜態成員函數main首先是創建一個SystemServer對象,然后調用這個SystemServer對象的成員函數run在System進程中啟動各種系統服務, 如下所示:

public final class SystemServer {
    ......

    private void run() {
        ......

        // Start services.
        try {
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
        } catch (Throwable ex) {
            ......
        }

        ......

        // Loop forever.
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

    ......
}

這個函數定義在文件frameworks/base/services/java/com/android/server/SystemServer.java中。

SystemServer類的成員函數run首先會調用成員函數startBootstrapServices啟動Bootstrap類型的系統服務。這些系統服務包括Activity Manager Service、Power Manager Service、Package Manager Service和Display Manager Service等。

SystemServer類的成員函數run接下來又會調用成員函數startCoreServices啟動Core類型的系統服務。這些系統服務包括Lights Service、Battery Service和Usage Stats Service等。

SystemServer類的成員函數run再接下來還會調用成員函數startOtherServices啟動其它的系統服務。這些其它的系統服務包括Account Manager Service、Network Management Service和Window Manager Service等。

SystemServer類的成員函數startOtherServices啟動完成其它的系統服務之后,就會創建一個Runnable。這個Runnable會在Activity Manger Service啟動完成后執行,如下所示:

public final class SystemServer {
    ......

    private void startOtherServices() {
        final Context context = mSystemContext;
        AccountManagerService accountManager = null;
        ContentService contentService = null;
        VibratorService vibrator = null;
        IAlarmManager alarm = null;
        MountService mountService = null;
        NetworkManagementService networkManagement = null;
        NetworkStatsService networkStats = null;
        NetworkPolicyManagerService networkPolicy = null;
        ConnectivityService connectivity = null;
        NetworkScoreService networkScore = null;
        NsdService serviceDiscovery= null;
        WindowManagerService wm = null;
        BluetoothManagerService bluetooth = null;
        UsbService usb = null;
        SerialService serial = null;
        NetworkTimeUpdateService networkTimeUpdater = null;
        CommonTimeManagementService commonTimeMgmtService = null;
        InputManagerService inputManager = null;
        TelephonyRegistry telephonyRegistry = null;
        ConsumerIrService consumerIr = null;
        AudioService audioService = null;
        MmsServiceBroker mmsService = null;
        ......

       mActivityManagerService.systemReady(new Runnable() {
            @Override
            public void run() {
                ......

                WebViewFactory.prepareWebViewInSystemServer();

                ......
            }
        });
    }

    ......
}

這個函數定義在文件frameworks/base/services/java/com/android/server/SystemServer.java中。

這個Runnable在執行的時候,會調用WebViewFactory類的靜態成員函數prepareWebViewInSystemServer請求Zygote進程啟動一個臨時的進程,用來加載Chromium動態庫,并且將完成重定位操作后的GNU_RELRO Section的內容Dump到一個文件中去。

WebViewFactory類的靜態成員函數prepareWebViewInSystemServer的實現如下所示:

public final class WebViewFactory {
    ......

    public static void prepareWebViewInSystemServer() {
        String[] nativePaths = null;
        try {
            nativePaths = getWebViewNativeLibraryPaths();
        } catch (Throwable t) {
            // Log and discard errors at this stage as we must not crash the system server.
            Log.e(LOGTAG, "error preparing webview native library", t);
        }
        prepareWebViewInSystemServer(nativePaths);
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

WebViewFactory類的靜態成員函數prepareWebViewInSystemServer首先調用另外一個靜態成員函數getWebViewNativeLibraryPaths獲得Chromium動態庫的文件路徑,如下所示:

public final class WebViewFactory {
    ......

    private static String[] getWebViewNativeLibraryPaths()
            throws PackageManager.NameNotFoundException {
        final String NATIVE_LIB_FILE_NAME = "libwebviewchromium.so";

        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
        ApplicationInfo ai = pm.getApplicationInfo(getWebViewPackageName(), 0);

        String path32;
        String path64;
        boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
        if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
            // Multi-arch case.
            if (primaryArchIs64bit) {
                // Primary arch: 64-bit, secondary: 32-bit.
                path64 = ai.nativeLibraryDir;
                path32 = ai.secondaryNativeLibraryDir;
            } else {
                // Primary arch: 32-bit, secondary: 64-bit.
                path64 = ai.secondaryNativeLibraryDir;
                path32 = ai.nativeLibraryDir;
            }
        } else if (primaryArchIs64bit) {
            // Single-arch 64-bit.
            path64 = ai.nativeLibraryDir;
            path32 = "";
        } else {
            // Single-arch 32-bit.
            path32 = ai.nativeLibraryDir;
            path64 = "";
        }
        if (!TextUtils.isEmpty(path32)) path32 += "/" + NATIVE_LIB_FILE_NAME;
        if (!TextUtils.isEmpty(path64)) path64 += "/" + NATIVE_LIB_FILE_NAME;
        return new String[] { path32, path64 };
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

Android系統將Chromium動態庫打包在一個WebView Package中。這個WebView Package也是一個APK,它的Package Name可以通過調用WebViewFactory類的靜態成員函數getWebViewPackageName獲得,如下所示:

public final class WebViewFactory {
    ......

    public static String getWebViewPackageName() {
        return AppGlobals.getInitialApplication().getString(
                com.android.internal.R.string.config_webViewPackageName);
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

WebViewFactory類的靜態成員函數getWebViewPackageName又是通過系統字符串資源com.android.internal.R.string.config_webViewPackageName獲得WebView Package的Package Name。

系統字符串資源com.android.internal.R.string.config_webViewPackageName的定義如下所示:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
  ......

  <!-- Package name providing WebView implementation. -->
    <string name="config_webViewPackageName" translatable="false">com.android.webview</string>

  ......
</resources>

這個字符串資源定義在文件frameworks/base/core/res/res/values/config.xml中。

從這里就可以看到,WebView Package這個APK的Package Name定義為"com.android.webview"。通過查找源碼,可以發現,這個APK實現在目錄frameworks/webview/chromium中。

回到WebViewFactory類的靜態成員函數getWebViewNativeLibraryPaths中,它知道了WebView Package這個APK的Package Name之后,就可以通過Package Manager Service獲得它的安裝信息。Package Manager Service負責安裝系統上所有的APK,因此通過它可以獲得任意一個APK的安裝信息。

獲得了WebView Package這個APK的安裝信息之后,就可以知道它用來保存動態庫的目錄。Chromium動態庫就是保存在這個目錄下的。因此,WebViewFactory類的靜態成員函數getWebViewNativeLibraryPaths最終可以獲得Chromium動態庫的文件路徑。注意,獲得Chromium動態庫的文件路徑可能有兩個。一個是32位版本的,另外一個是64位版本的。

為什么要將Chromium動態庫打包在一個WebView Package中呢?而不是像其它的系統動態庫一樣,直接放在/system/lib目錄下。原因為了方便以后升級WebView。例如,Chromium有了新的版本之后,就可以將它打包在一個更高版本的WebView Package中。當用戶升級WebView Package的時候,就獲得了基于新版本Chromium實現的WebView了。

這一步執行完成后,回到WebViewFactory類的靜態成員函數prepareWebViewInSystemServer中,這時候它就獲得了Chromium動態庫的文件路徑。接下來,它繼續調用另外一個靜態成員函數prepareWebViewInSystemServer請求Zygote進程啟動一個臨時的進程,用來加載Chromium動態庫,以便將完成重定位操作后的GNU_RELRO Section的內容Dump到一個文件中去。

WebViewFactory類的靜態成員函數prepareWebViewInSystemServer的實現如下所示:

public final class WebViewFactory {

    ......

    private static void prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
        if (DEBUG) Log.v(LOGTAG, "creating relro files");

        // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
        // unexpected values will be handled there to ensure that we trigger notifying any process
        // waiting on relreo creation.
        if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
            if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
            createRelroFile(false /* is64Bit */, nativeLibraryPaths);
        }

        if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
            if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
            createRelroFile(true /* is64Bit */, nativeLibraryPaths);
        }
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

WebViewFactory類的靜態成員函數prepareWebViewInSystemServer根據系統對32位和64位的支持情況,調用另外一個靜態成員函數createRelroFile相應地創建32位和64位的Chromium GNU_RELRO Section文件。

WebViewFactory類的靜態成員函數createRelroFile的實現如下所示:

public final class WebViewFactory {
    ......

    private static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
        ......

        try {
            ......

            int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
                    RelroFileCreator.class.getName(), nativeLibraryPaths, "WebViewLoader-" + abi, abi,
                    Process.SHARED_RELRO_UID, crashHandler);
            ......
        } catch (Throwable t) {
            ......
        }
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

WebViewFactory類的靜態成員函數createRelroFile請求系統的Activity Manager Service啟動一個Isolated Process。這個Isolated Process的啟動過程與App進程是一樣的,只不過它是一個被隔離的進程,沒有自己的權限。

App進程最終是由Zygote進程fork出來的,并且它在Java層的入口函數為ActivityThread類的靜態成員函數main。這個入口函數是可以指定的。WebViewFactory類的靜態成員函數createRelroFile將請求啟動的Isolated Process的入口函數指定為RelroFileCreator類的靜態成員函數main。

這意味著,當上述Isolated Process啟動起來之后,RelroFileCreator類的靜態成員函數main就會被調用,如下所示:

public final class WebViewFactory {
    ......

    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
            "/data/misc/shared_relro/libwebviewchromium32.relro";
    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
            "/data/misc/shared_relro/libwebviewchromium64.relro";
    ......

    private static class RelroFileCreator {
        // Called in an unprivileged child process to create the relro file.
        public static void main(String[] args) {
            ......
            try{
                ......
                result = nativeCreateRelroFile(args[0] /* path32 */,
                                               args[1] /* path64 */,
                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
                ......
            } finally {
                ......
            }
        }
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

前面獲得的Chromium動態庫文件路徑會通過參數args傳遞進來。有了Chromium動態庫文件路徑之后,RelroFileCreator類的靜態成員函數main就會調用另外一個靜態成員函數nativeCreateRelroFile對它進行加載。加載完成后,RelroFileCreator類的靜態成員函數nativeCreateRelroFile會將Chromium動態庫已經完成重定位操作的Chromium GNU_RELRO Section寫入到指定的文件中去。對于32位的Chromium動態庫來說,它的GNU_RELRO Section會被寫入到文件/data/misc/shared_relro/libwebviewchromium32.relro中,而對于64位的Chromium動態庫來說,它的GNU_RELRO Section會被寫入到文件/data/misc/shared_relro/libwebviewchromium64.relro中。

RelroFileCreator類的靜態成員函數nativeCreateRelroFile是一個JNI方法,它由C++層的函數CreateRelroFile實現,如下所示:

jboolean CreateRelroFile(JNIEnv* env, jclass, jstring lib32, jstring lib64,
                         jstring relro32, jstring relro64) {
#ifdef __LP64__
  jstring lib = lib64;
  jstring relro = relro64;
  (void)lib32; (void)relro32;
#else
  jstring lib = lib32;
  jstring relro = relro32;
  (void)lib64; (void)relro64;
#endif
  jboolean ret = JNI_FALSE;
  const char* lib_utf8 = env->GetStringUTFChars(lib, NULL);
  if (lib_utf8 != NULL) {
    const char* relro_utf8 = env->GetStringUTFChars(relro, NULL);
    if (relro_utf8 != NULL) {
      ret = DoCreateRelroFile(lib_utf8, relro_utf8);
      env->ReleaseStringUTFChars(relro, relro_utf8);
    }
    env->ReleaseStringUTFChars(lib, lib_utf8);
  }
  return ret;
}

這個函數定義在文件frameworks/webview/chromium/loader/loader.cpp中。

函數CreateRelroFile判斷自己是32位還是64位的實現,然后從參數lib32和lib64中選擇對應的Chromium動態庫進行加載。這個加載過程是通過調用另外一個函數DoCreateRelroFile實現的,如下所示:

void* gReservedAddress = NULL;
size_t gReservedSize = 0;

......

jboolean DoCreateRelroFile(const char* lib, const char* relro) {
  ......

  static const char tmpsuffix[] = ".XXXXXX";
  char relro_tmp[strlen(relro) + sizeof(tmpsuffix)];
  strlcpy(relro_tmp, relro, sizeof(relro_tmp));
  strlcat(relro_tmp, tmpsuffix, sizeof(relro_tmp));
  int tmp_fd = TEMP_FAILURE_RETRY(mkstemp(relro_tmp));
  ......

  android_dlextinfo extinfo;
  extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_WRITE_RELRO;
  extinfo.reserved_addr = gReservedAddress;
  extinfo.reserved_size = gReservedSize;
  extinfo.relro_fd = tmp_fd;
  void* handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo);
  int close_result = close(tmp_fd);
  ......

  if (close_result != 0 ||
      chmod(relro_tmp, S_IRUSR | S_IRGRP | S_IROTH) != 0 ||
      rename(relro_tmp, relro) != 0) {
    ......
    return JNI_FALSE;
  }

  return JNI_TRUE;
}

這個函數定義在文件frameworks/webview/chromium/loader/loader.cpp中。

參數lib描述的是要加載的Chromium動態庫的文件路徑,另外一個參數relro描述的是要生成的Chromium GNU_RELRO Section文件路徑。

我們假設要加載的Chromium動態庫是32位的。函數DoCreateRelroFile的執行過程如下所示:

1.  創建一個臨時的mium GNU_RELRO Section文件,文件路徑為”/data/misc/shared_relro/libwebviewchromium32.relro.XXXXXX“,并且會找開這個臨時文件,獲得一個文件描述符tmp_fd。

2. 創建一個android_dlextinfo結構體。這個android_dlextinfo結構體會指定一個ANDROID_DLEXT_RESERVED_ADDRESS標志,以及一個Reserved地址gReservedAddress,表示要將Chromium動態庫加載在全局變量gReservedAddress描述的地址中。從前面的分析可以知道,Zygote進程為Chromium動態庫預留的地址空間的起始地址就保存在全局變量gReservedAddress中。由于當前進程是由Zygote進程fork出來的,因此它同樣會獲得Zygote進程預留的地址空間。

3. 前面創建的android_dlextinfo結構體,同時還會指定一個ANDROID_DLEXT_WRITE_RELRO標專,以及一個Relro文件描述符tmp_fd,表示在加載完成Chromium動態庫后,將完成重定位操作后的GNU_RELRO Section寫入到指定的文件中去,也就是寫入到上述的臨時文件/data/misc/shared_relro/libwebviewchromium32.relro.XXXXXX中去。

4. 調用Linker導出的函數android_dlopen_ext按照上述兩點規則加載Chromium動態庫。加載完成后,臨時文件/data/misc/shared_relro/libwebviewchromium32.relro.XXXXXX將被重新命名為”/data/misc/shared_relro/libwebviewchromium32.relro“。

這樣,我們得就到一個Chromium GNU_RELRO Section文件。這個Chromium GNU_RELRO Section文件在App進程加載Chromium動態庫時就會使用到。接下來我們就繼續分析App進程加載Chromium動態庫的過程。

當App使用了WebView的時候,系統就為會其加載Chromium動態庫。這個加載過程是從創建WebView對象的時候發起的。因此,接下來我們就從WebView類的構造函數開始分析App進程加載Chromium動態庫的過程,如下所示:

public class WebView extends AbsoluteLayout
        implements ViewTreeObserver.OnGlobalFocusChangeListener,
        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
    ......

    protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
            Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
        ......

        ensureProviderCreated();
        mProvider.init(javaScriptInterfaces, privateBrowsing);

        ......
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebView.java中。

WebView類的構造函數會調用另外一個成員函數ensureProviderCreated確保Chromium動態庫已經加載。在Chromium動態庫已經加載的情況下,WebView類的成員函數ensureProviderCreated還會創建一個WebView Provider,并且保存保存在成員變量mProvider中。這個WebView Provider才是真正用來實現WebView的功能的。

有了這個WebView Provider之后,WebView類的構造函數就會調用它的成員函數init啟動網頁渲染引擎。對于基于Chromium實現的WebView來說,它使用的WebView Provider是一個WebViewChromium對象。當這個WebViewChromium對象的成員函數init被調用的時候,它就會啟動Chromium的網頁渲染引擎。這個過程我們在接下來的一篇文章再詳細分析。

接下來,我們繼續分析WebView類的成員函數ensureProviderCreated的實現,如下所示:

public class WebView extends AbsoluteLayout
        implements ViewTreeObserver.OnGlobalFocusChangeListener,
        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
    ......

    private void ensureProviderCreated() {
        checkThread();
        if (mProvider == null) {
            // As this can get called during the base class constructor chain, pass the minimum
            // number of dependencies here; the rest are deferred to init().
            mProvider = getFactory().createWebView(this, new PrivateAccess());
        }
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebView.java中。

WebView類的成員函數ensureProviderCreated首先調用成員函數checkThread確保它是在WebView的創建線程中調用的,接下來又會判斷成員變量mProvider的值是否為null。如果為null,就表示它還沒有當前創建的WebView創建過Provider。在這種情況下,它首先會調用成員函數getFactory獲得一個WebView Factory。有了這個WebView Factory之后,就可以調用它的成員函數createWebView創建一個WebView Provider。

WebView類的成員函數getFactory的實現如下所示:

public class WebView extends AbsoluteLayout
        implements ViewTreeObserver.OnGlobalFocusChangeListener,
        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
    ......

    private static synchronized WebViewFactoryProvider getFactory() {
        return WebViewFactory.getProvider();
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebView.java中。

WebView類的成員函數getFactory返回的WebView Factory是通過調用WebViewFactory類的靜態成員函數getProvider獲得的,如下所示:

public final class WebViewFactory {
    ......

    private static WebViewFactoryProvider sProviderInstance;
    ......

    static WebViewFactoryProvider getProvider() {
        synchronized (sProviderLock) {
            // For now the main purpose of this function (and the factory abstraction) is to keep
            // us honest and minimize usage of WebView internals when binding the proxy.
            if (sProviderInstance != null) return sProviderInstance;

            ......
            try {
                ......
                loadNativeLibrary();
                ......

                Class<WebViewFactoryProvider> providerClass;
                ......
                try {
                    providerClass = getFactoryClass();
                } catch (ClassNotFoundException e) {
                    ......
                } finally {
                    ......
                }

                ......
                try {
                    sProviderInstance = providerClass.newInstance();
                    ......
                    return sProviderInstance;
                } catch (Exception e) {
                    .......
                } finally {
                    ......
                }
            } finally {
                ......
            }
        }
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

WebViewFactory類的靜態成員函數getProvider首先是判斷靜態成員變量sProviderInstance的值是否等于null。如果等于null,那么就說明當前的App進程還沒有加載過Chromium動態庫。在這種情況下,就需要加載Chromium動態庫,并且創建一個WebView Factory,保存在靜態成員變量sProviderInstance。接下來我們就先分析Chromium動態庫的加載過程,然后再分析WebView Factory的創建過程。

加載Chromium動態庫是通過調用WebViewFactory類的靜態成員函數loadNativeLibrary實現的,如下所示:

public final class WebViewFactory {
    ......

    private static void loadNativeLibrary() {
        ......

        try {
            String[] args = getWebViewNativeLibraryPaths();
            boolean result = nativeLoadWithRelroFile(args[0] /* path32 */,
                                                     args[1] /* path64 */,
                                                     CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
                                                     CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
            ......
        } catch (PackageManager.NameNotFoundException e) {
            ......
        }
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

WebViewFactory類的靜態成員函數loadNativeLibrary首先會調用我們前面分析過的成員函數getWebViewNativeLibraryPaths獲得要加載的Chromium動態庫的文件路徑,然后再調用另外一個靜態成員函數nativeLoadWithRelroFile對它進行加載。在加載的時候,會指定一個Chromium GNU_RELRO Section文件。這個Chromium GNU_RELRO Section文件就是前面通過啟動一個臨時進程生成的。

WebViewFactory類的靜態成員函數nativeLoadWithRelroFile是一個JNI方法,它由C++層的函數LoadWithRelroFile實現,如下所示:

jboolean LoadWithRelroFile(JNIEnv* env, jclass, jstring lib32, jstring lib64,
                           jstring relro32, jstring relro64) {
#ifdef __LP64__
  jstring lib = lib64;
  jstring relro = relro64;
  (void)lib32; (void)relro32;
#else
  jstring lib = lib32;
  jstring relro = relro32;
  (void)lib64; (void)relro64;
#endif
  jboolean ret = JNI_FALSE;
  const char* lib_utf8 = env->GetStringUTFChars(lib, NULL);
  if (lib_utf8 != NULL) {
    const char* relro_utf8 = env->GetStringUTFChars(relro, NULL);
    if (relro_utf8 != NULL) {
      ret = DoLoadWithRelroFile(lib_utf8, relro_utf8);
      env->ReleaseStringUTFChars(relro, relro_utf8);
    }
    env->ReleaseStringUTFChars(lib, lib_utf8);
  }
  return ret;
}

這個函數定義在文件frameworks/webview/chromium/loader/loader.cpp中。

函數LoadWithRelroFile判斷自己是32位還是64位的實現,然后從參數lib32和lib64中選擇對應的Chromium動態庫進行加載。這個加載過程是通過調用另外一個函數DoLoadWithRelroFile實現的,如下所示:

jboolean DoLoadWithRelroFile(const char* lib, const char* relro) {
  int relro_fd = TEMP_FAILURE_RETRY(open(relro, O_RDONLY));
  ......

  android_dlextinfo extinfo;
  extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_USE_RELRO;
  extinfo.reserved_addr = gReservedAddress;
  extinfo.reserved_size = gReservedSize;
  extinfo.relro_fd = relro_fd;
  void* handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo);
  close(relro_fd);
  ......

  return JNI_TRUE;
}

這個函數定義在文件frameworks/webview/chromium/loader/loader.cpp中。

函數DoLoadWithRelroFile的實現與前面分析的函數DoCreateRelroFile類似,都是通過Linker導出的函數android_dlopen_ext在Zyogote進程保留的地址空間中加載Chromium動態庫的。注意,App進程是Zygote進程fork出來的,因此它同樣會獲得Zygote進程預留的地址空間。

不過,函數DoLoadWithRelroFile會將告訴函數android_dlopen_ext在加載Chromium動態庫的時候,將參數relro描述的Chromium GNU_RELRO Section文件內存映射到內存來,并且代替掉已經加載的Chromium動態庫的GNU_RELRO Section。這是通過將指定一個ANDROID_DLEXT_USE_RELRO標志實現的。

之所以可以這樣做,是因為參數relro描述的Chromium GNU_RELRO Section文件對應的Chromium動態庫的加載地址與當前App進程加載的Chromium動態庫的地址一致。只要兩個相同的動態庫在兩個不同的進程中的加載地址一致,它們的鏈接和重定位信息就是完全一致的,因此就可以通過文件內存映射的方式進行共享。共享之后,就可以達到節省內存的目的了。

這一步執行完成之后,App進程就加載完成Chromium動態庫了。回到前面分析的WebViewFactory類的靜態成員函數getProvider,它接下來繼續創建一個WebView Factory。這個WebView Factory以后就可以用來創建WebView Provider。

WebViewFactory類的靜態成員函數getProvider首先要確定要創建的WebView Factory的類型。這個類型是通過調用另外一個靜態成員函數getFactoryClass獲得的,如下所示:

public final class WebViewFactory {

    private static final String CHROMIUM_WEBVIEW_FACTORY =
            "com.android.webview.chromium.WebViewChromiumFactoryProvider";

    ......

    private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
        Application initialApplication = AppGlobals.getInitialApplication();
        try {
            // First fetch the package info so we can log the webview package version.
            String packageName = getWebViewPackageName();
            sPackageInfo = initialApplication.getPackageManager().getPackageInfo(packageName, 0);
            ......

            // Construct a package context to load the Java code into the current app.
            Context webViewContext = initialApplication.createPackageContext(packageName,
                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
            ......

            ClassLoader clazzLoader = webViewContext.getClassLoader();
            ......
            try {
                return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
                                                                     clazzLoader);
            } finally {
                ......
            }
        } catch (PackageManager.NameNotFoundException e) {
            ......
        }
    }

    ......
}

這個函數定義在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。

從這里可以看到,WebViewFactory類的靜態成員函數getFactoryClass返回的WebView Factory的類型為com.android.webview.chromium.WebViewChromiumFactoryProvider。這個com.android.webview.chromium.WebViewChromiumFactoryProvider類是由前面提到的WebView Package提供的。

這意味著WebViewFactory類的靜態成員函數getProvider創建的WebView Factory是一個WebViewChromiumFactoryProvider對象。這個WebViewChromiumFactoryProvider的創建過程如下所示:

public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
    ......

    public WebViewChromiumFactoryProvider() {
        ......

        AwBrowserProcess.loadLibrary();

        .......
    }

    ......
}

這個函數定義在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java中。

WebViewChromiumFactoryProvider類的構造函數會調用AwBrowserProcess類的靜態成員函數loadLibrary對前面加載的Chromium動態庫進行初始化,如下所示:

public abstract class AwBrowserProcess {
    ......

    public static void loadLibrary() {
        ......
        try {
            LibraryLoader.loadNow();
        } catch (ProcessInitException e) {
            throw new RuntimeException("Cannot load WebView", e);
        }
    }

    ......
}

這個函數定義在文件external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java中。

AwBrowserProcess類的靜態成員函數loadLibrary又調用LibraryLoader類的靜態成員函數loadNow對前面加載的Chromium動態庫進行初始化,如下所示:

public class LibraryLoader {
    ......

    public static void loadNow() throws ProcessInitException {
        loadNow(null, false);
    }

    ......
}

這個函數定義在文件external/chromium_org/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java中。

LibraryLoader類的靜態成員函數loadNow又調用另外一個重載版本的靜態成員函數loadNow對前面加載的Chromium動態庫進行初始化,如下所示:

public class LibraryLoader {
    ......

    public static void loadNow(Context context, boolean shouldDeleteOldWorkaroundLibraries)
            throws ProcessInitException {
        synchronized (sLock) {
            loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries);
        }
    }

    ......
}

這個函數定義在文件external/chromium_org/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java中。

LibraryLoader類重載版本的靜態成員函數loadNow又調用另外一個靜態成員函數loadAlreadyLocked對前面加載的Chromium動態庫進行初始化,如下所示:

public class LibraryLoader {
    ......

    // One-way switch becomes true when the libraries are loaded.
    private static boolean sLoaded = false;
    ......

    private static void loadAlreadyLocked(
            Context context, boolean shouldDeleteOldWorkaroundLibraries)
            throws ProcessInitException {
        try {
            if (!sLoaded) {
                ......

                boolean useChromiumLinker = Linker.isUsed();

                if (useChromiumLinker) Linker.prepareLibraryLoad();

                for (String library : NativeLibraries.LIBRARIES) {
                    Log.i(TAG, "Loading: " + library);
                    if (useChromiumLinker) {
                        Linker.loadLibrary(library);
                    } else {
                        try {
                            System.loadLibrary(library);
                        } catch (UnsatisfiedLinkError e) {
                            ......
                        }
                    }
                }
                if (useChromiumLinker) Linker.finishLibraryLoad();

                ......
                sLoaded = true;
            }
        } catch (UnsatisfiedLinkError e) {
            ......
        }
        ......
    }

    ......
}

這個函數定義在文件external/chromium_org/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java中。

由于并不是所有的系統都支持在加載動態庫時,以文件內存映射的方式代替它的GNU_RELRO Section,因此Chromium自己提供了一個Linker。通過這個Linker加載動態庫時,能夠以文件內存映射的方式代替要加載的動態庫的GNU_RELRO Section,也就是實現前面提到的函數 android_dlopen_ext的功能。      

在Android 5.0中,由于系統已經提供了函數 android_dlopen_ext,因此,Chromium就不會使用自己的Linker加載動態庫,而是使用Android系統提供的Linker來加載動態庫。通過調用System類的靜態成員函數loadLibrary即可以使用 系統提供的Linker來加載動態庫。

LibraryLoader類的靜態成員函數loadAlreadyLocked要加載的動態庫由NativeLibraries類的靜態成員變量LIBRARIES指定,如下所示:

public class NativeLibraries {
    ......

    static final String[] LIBRARIES = { "webviewchromium" };

    ......
}

這個靜態成員變量定義在文件external/chromium_org/android_webview/java/generated_src/org/chromium/base/library_loader/NativeLibraries.java中。

從這里可以知道,LibraryLoader類的靜態成員函數loadAlreadyLocked要加載的動態庫就是Chromium動態庫。這個Chromium動態庫前面已經加載過了,因此這里通過調用 System類的靜態成員函數loadLibrary再加載時,僅僅是只會觸發它導出的函數JNI_OnLoad被調用,而不會重新被加載。

Chromium動態庫導出的JNI_OnLoad被調用的時候,Chromium動態庫就會執行初始化工作,如下所示:

JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  ......

  content::SetContentMainDelegate(new android_webview::AwMainDelegate());
  ......

  return JNI_VERSION_1_4;
}

這個函數定義在文件external/chromium_org/android_webview/lib/main/webview_entry_point.cc中。

其中的一個初始化操作是給Chromium的Content層設置一個類型為AwMainDelegate的Main Delegate。這個AwMainDelegate實現在Chromium的android_webview模塊中。Android WebView是通過Chromium的android_webview模塊加載和渲染網頁的。Chromium加載和渲染網頁的功能又是實現在Content層的,因此,Chromium的android_webview模塊又要通過Content層實現加載和渲染網頁功能。這樣,Chromium的android_webview模塊就可以設置一個Main Delegate給Content層,以便它們可以互相通信。

給Chromium的Content層設置一個Main Delegate是通過調用函數SetContentMainDelegate實現的,它的實現如下所示:

LazyInstance<scoped_ptr<ContentMainDelegate> > g_content_main_delegate =
    LAZY_INSTANCE_INITIALIZER;

......

void SetContentMainDelegate(ContentMainDelegate* delegate) {
  DCHECK(!g_content_main_delegate.Get().get());
  g_content_main_delegate.Get().reset(delegate);
}

這個函數定義在文件external/chromium_org/content/app/android/content_main.cc中。

從前面的分析可以知道,參數delegate指向的是一個AwMainDelegate對象,這個AwMainDelegate對象會被函數SetContentMainDelegate保存在全局變量g_content_main_delegate中。在接下來一篇文章中分析Chromium渲染引擎的啟動過程時,我們就會看到這個AwMainDelegate對象的作用。

這一步執行完成后,Chromium動態庫就在App進程中加載完畢,并且也已經完成了初始化工作。與此同時,系統也為App進程創建了一個類型為WebViewChromiumFactoryProvider的WebView Factory。回到前面分析的WebView類的成員函數ensureProviderCreated中,這時候就它會通過調用上述類型為WebViewChromiumFactoryProvider的WebView Factory的成員函數createWebView為當前創建的WebView創建一個WebView Provider,如下所示:

public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
    ......

    @Override
    public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
        WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);

        ......

        return wvc;
    }

    ......
}

這個函數定義在文件frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java中。

WebViewChromiumFactoryProvider類的成員函數createWebView創建的是一個類型為WebViewChromium的WebView Provider。這個WebView Provider將會返回給WebView類的成員函數ensureProviderCreated。WebView類的成員函數ensureProviderCreated再將該WebView Provider保存在成員變量mProvider中。

這樣,正在創建的WebView就獲得了一個類型為WebViewChromium的WebView Provider。以后通過這個WebView Provider,就可以通過Chromium來加載和渲染網頁了。

至此,我們也分析完成了Android WebView加載Chromium動態庫的過程。在接下來的一篇文章中,我們繼續分析Android WebView啟動Chromium渲染引擎的過程。Chromium渲染引擎啟動起來之后,Android WebView就可以通過它來加載和渲染網頁了。

 

來自:http://blog.csdn.net/luoshengyang/article/details/53209199

 

 本文由用戶 NgaTIH 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!