博观而约取,厚积而薄发。这篇文章主要讲述Android WebView加载Chromium动态库的过程分析相关的知识,希望能为你提供帮助。
Chromium动态库的体积比较大,
有27M左右,
其中程序段和数据段分别占据25.65M和1.35M。如果按照通常方式加载Chromium动态库,
那么当有N个正在运行的App使用WebView时,
系统需要为Chromium动态库分配的内存为(25.65 +
N x 1.35)M。这是非常可观的。为此,
android使用了特殊的方式加载Chromium动态库。本文接下来就详细分析这种特殊的加载方式。
老罗的新浪微博:
http://weibo.com/shengyangluo,
欢迎关注!
《Android系统源代码情景分析》一书正在进击的程序员网(
http://0xcc0xcd.com)
中连载,
点击进入!
为什么当有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的安装信息。关于Package Manager Service, 可以参考Android应用程序安装过程源代码分析这篇文章。
获得了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进程是一样的, 只不过它是一个被隔离的进程, 没有自己的权限。
从前面Android应用程序进程启动过程的源代码分析一文可以知道, 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获得的, 如下所示:
【Android WebView加载Chromium动态库的过程分析】
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://weibo.com/shengyangluo。
推荐阅读
- Android selector选择器的使用
- Android通知Notification全面剖析
- Android 7.0 ActivityManagerService 启动Activity的过程(一)
- 安卓 Android题目大全
- Android adb常用指令
- Android开发---MediaPlayer简单音乐播放器
- Android艺术开发探索第四章——View的工作原理(下)
- Android 数据库读取数据显示 [5]
- Android关于Theme.AppCompat相关问题的深入分析(转)