[原]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