Android WebView加载Chromium动态库的过程分析

本文深入分析了Android系统中WebView加载Chromium动态库的特殊方式,以节省内存。通过在Zygote进程预留地址空间,然后通过临时进程加载并Dump GNU_RELRO Section,最后App进程通过内存映射共享此部分,实现内存优化。详细阐述了从预加载到动态库加载、重定位、内存映射的全过程。
摘要由CSDN通过智能技术生成

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

       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;        Conten
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值