Android WebView加载Chromium动态库的过程分析.doc_第1页
Android WebView加载Chromium动态库的过程分析.doc_第2页
Android WebView加载Chromium动态库的过程分析.doc_第3页
Android WebView加载Chromium动态库的过程分析.doc_第4页
Android WebView加载Chromium动态库的过程分析.doc_第5页
已阅读5页,还剩22页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

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所示:如果我们将该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所示:从前面一文可以知道,所有的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动态库的。 从前面一文可以知道,Zygote进程在Java层的入口函数为ZygoteInit类的静态成员函数main,它的实现如下所示:java view plain copypublic 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动态库预保留加载地址,如下所示:java view plain copypublic 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动态库预保留加载地址的,后者的实现如下所示:java view plain copy 在CODE上查看代码片派生到我的代码片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”的动态库中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片jboolean ReserveAddressSpace(JNIEnv*, jclass, jlong size) return DoReserveAddressSpace(size); 这个函数定义在文件frameworks/webview/chromium/loader/loader.cpp中。 函数ReserveAddressSpace调用另外一个函数DoReserveAddressSpace预留大小为size的地址空间,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void* gReservedAddress = NULL; size_t gReservedSize = 0; jboolean DoReserveAddressSpace(jlong size) size_t vsize = static_cast(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进程完成的。从前面一文可以知道,System进程是由Zygote进程启动的,它在Java层的入口函数为SystemServer的静态成员函数main,它的实现如下所示:java view plain copy 在CODE上查看代码片派生到我的代码片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进程中启动各种系统服务,如下所示:java view plain copy 在CODE上查看代码片派生到我的代码片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启动完成后执行,如下所示:java view plain copy 在CODE上查看代码片派生到我的代码片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的实现如下所示:java view plain copy 在CODE上查看代码片派生到我的代码片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动态库的文件路径,如下所示:java view plain copy 在CODE上查看代码片派生到我的代码片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获得,如下所示:java view plain copy 在CODE上查看代码片派生到我的代码片public final class WebViewFactory . public static String getWebViewPackageName() return AppGlobals.getInitialApplication().getString( ernal.R.string.config_webViewPackageName); . 这个函数定义在文件frameworks/base/core/java/android/webkit/WebViewFactory.java中。 WebViewFactory类的静态成员函数getWebViewPackageName又是通过系统字符串资源ernal.R.string.config_webViewPackageName获得WebView Package的Package Name。 系统字符串资源ernal.R.string.config_webViewPackageName的定义如下所示:html view plain copy 在CODE上查看代码片派生到我的代码片 . com.android.webview . 这个字符串资源定义在文件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,可以参考这篇文章。 获得了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的实现如下所示:java view plain copy 在CODE上查看代码片派生到我的代码片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的实现如下所示:java view plain copy 在CODE上查看代码片派生到我的代码片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就会被调用,如下所示:java view plain copy 在CODE上查看代码片派生到我的代码片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(args0 /* path32 */, args1 /* 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实现,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片jboolean CreateRelroFile(JNIEnv* env, jclass, jstring lib32, jstring lib64, jstring relro32, jstring relro64) #i

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论