[关闭]
@rogeryi 2015-03-05T09:47:38.000000Z 字数 9157 阅读 10569

WebView of Chromium Android WebView

Android WebView

作者: 易旭昕 (@roger2yi)
说明: 访问 Cmd Markdown 版本可以获得最佳阅读体验

Chromium Android WebView 研究系列文章

  1. Debugging of Chromium Android WebView
  2. Threading of Chromium Android WebView
  3. Rendering of Chromium Android WebView
  4. WebView of Chromium Android WebView
  5. Event Handling of Chromium Android WebView
  6. Resources of Chromium Android WebView

本文主要描述 Chromium Android WebView (下文简称CAW)是如何对 Chromium Content 进行封装,对外提供一个封装好的 Android View - WebView 供第三方应用使用。代码涉及 Android WebView 和 Chromium Content 模块其中的一部分。

Android WebView 的代码组织

Android WebView 代码组织图

这一章内容主要根据官方文档 Organization of code for Android WebView 编写,也参考了放飞梦想的文章 Android 4.4 WebView实现分析

总的来说 Android WebView 适配的代码大概可以分作两部分,一部分属于 Chromium source tree,一部分属于 Android source tree。

只属于 Android source tree,不属于 Chromium source tree 的代码,分别位于 AOSP 源码树的 frameworks/base/core/java/android/webkitframeworks/webview 目录下。

frameworks/base/core/java/android/webkit 主要为第三方应用提供公开的 WebView API,它的代码包括:

frameworks/webview 是 Android WebView API 与 Chromium Content 层之间的 glue layer,它隔绝了 frameworks/baseexternal/chromium_org 代码之间的相互依赖,它实现了 frameworks/base 下的 WebViewProvider 等接口,将 WebView 发送过来的请求转发给 Chromium,并通过注册回调的方式为 Chromium 提供一些必须使用 Android 非公开 API 的代码。

Chromium 的代码位于 AOSP 源码树的 external/chromium_org 目录下,它是 Chromium 代码仓库某个分支的一个 Clone,其中 android_webview 子目录下就是 Chromium 为 Android WebView 这个 Platform Configuration 适配的代码(如果是为 Android 平台适配的代码,一般位于各个模块的 android 子目录下,这部分是 CAW 和 Chrome for Android 共用的)。Chromium 的代码可以使用标准的 Android SDK/NDK 编译,它不依赖于上面列举的 Android source 的部分,是完全独立的,甚至代码里面还包括了一个 TestShell 可以进行简单的测试(external/chromium_org/android_webview/test)。

Android WebView 的编译

如前所述,Android WebView 的代码组织如下:

Chromium source tree

Android source tree

android_webview.gyp 是 Chromium C++ 代码的 GYP 工程文件,GYP 工具会将它转换成编译文件,然后使用 Ninjia 编译工具进行编译,下面是它的部分示例代码,我们可以看到 libwebviewchromium.so target 是由 android_webview_common 静态库加上一个入口文件 webview_entry_point.cc 组成。

  1. 'targets': [
  2. {
  3. 'target_name': 'libwebviewchromium',
  4. 'type': 'shared_library',
  5. 'android_unmangled_name': 1,
  6. 'dependencies': [
  7. 'android_webview_common',
  8. ],
  9. ...
  10. 'sources': [
  11. 'lib/main/webview_entry_point.cc',
  12. ],
  13. },

webview_entry_point.cc 实际上只包括了一个 JNI_OnLoad 函数,在 libwebviewchromium.so 加载时完成部分 JNI 方法的注册。

  1. // This is called by the VM when the shared library is first loaded.
  2. // Most of the initialization is done in LibraryLoadedOnMainThread(), not here.
  3. JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  4. base::android::InitVM(vm);
  5. ...
  6. return JNI_VERSION_1_4;
  7. }

放飞梦想的开源项目 ChromiumWebView 提供一种编译独立的 WebView 的方式,它具体的做法是,建立一个自己的工程 chromeview,这个工程包括:

  1. C++ 代码包括平台适配的代码 draw_gl_functor.cpp 和一个取代 webview_entry_point.cc 自己的入口文件 jni_entry_point.cpp;
  2. Java 代码包括从 Android source tree 和 Chromium source tree 的 Java 代码的拷贝,Android source tree 的部分更改了包名避免跟 Android SDK 冲突;
  3. 只有一个 libchromeview.so,它是使用 Chromium source tree 的 C++ 代码和 1 中的 C++ 代码编译而成。

ChromiumWebView 提供的 chromeview.gyp 工程文件如下:

  1. {
  2. 'target_name': 'libchromeview',
  3. 'type': 'shared_library',
  4. 'android_unmangled_name': 1,
  5. 'dependencies': [
  6. '../android_webview/android_webview.gyp:android_webview_common',
  7. ],
  8. 'sources': [
  9. 'native/jni_entry_point.cpp',
  10. 'native/draw_gl_functor.cpp',
  11. ],
  12. ...
  13. },

它自带了一个编译好的 libchromeview.so,如果要自己编译 libchromeview.so,需要 checkout Chromium 的代码,把 chromeview 置于 Chromium 代码的第一级目录下,使用 chromeview.gyp 工程文件编译即可。另外注意的是 Chromium 的版本要跟 chromeview 里面的 Java 代码相吻合,当前的 chromeview 使用的是 1847 版本。

Android WebView 的主要对象

下图展示了 Android WebView 相关的一些主要对象,绿色标识的是 Android WebView API,浅绿色标识的是 Android WebView glue layer,黄色标识是 Chromium 为 Android WebView 适配的对象,蓝色的部分是 Chromium Content 模块。

Android WebView 相关的主要对象

对象 说明
WebView.java 对外公开的 WebView API,继承 Android View,可以嵌入 UI 界面显示一个网页
WebViewChromium.java WebView 与 Chromium Content 之间的桥接
AwContents.java WebView 真正的实现,Native AwContents 对象的 Java 封装,访问 ContentViewCore 对象和 WebView API 需要的 Browser components 的入口
AwContents 访问 Chromium Content 模块的主要入口,访问 WebView API 需要的 Browser components 的入口
WebContents[Impl] Chromium Content 对外提供的主要封装对象,通过它创建,管理和使用其它 Content 模块对象
WebContentsOberver 实现 WebContentsOberver 接口的对象可以接收 WebContents[Impl] 转发的 Blink 内核事件,和通过 WebContents[Impl] 发送事件给 Blink 内核
ContentViewCore.java Native ContentViewCore 对象的 Java 封装
ContentViewCore[Impl] Chromium Content 在 Android 平台的主要适配对象,通过它使用 Content 模块的主要功能,为 Chromium Content 提供平台相关的适配,在 Android 平台上一些调用通过它转发给 WebContents[Impl],比如 LoadURL,相当于 WebContents[Impl] 的 Wrapper 和 Observer

WebView 的创建和 Content 初始化

Content Start 调用图

当应用创建一个 WebView 时:

  1. WebView.ensureProviderCreated 会在构造函数被调用到,它会先创建 WebViewChromiumFactoryProvider,WebViewChromiumFactoryProvider 是一个全局单例,只需要创建一次;
    1.1 WebViewChromiumFactoryProvider 的构建函数会调用 AwBrowserProcess.loadLibrary 加载内核库,这时 JNI_Onload 方法会被调用,注册部分 JNI 方法;
  2. WebView 通过 WebViewChromiumFactoryProvider 创建一个 WebViewChromium 对象作为自己的 provider;
  3. WebView 创建 WebViewChromium 后需要对它进行初始化,WebViewChromium 初始化时会先调用 WebViewChromiumFactoryProvider.startYourEngines 初始化内核(只需调用一次);
    3.1 library_loader_hooks.cc 的 LibraryLoaded 会被调用,继续其它 JNI 方法的注册,完成内核库的加载;
    3.2 ContentMain.initApplicationContext 会被调用,将 Android Context 对象的引用传给 Native 端;
    3.3 ContentMain.start 会被调用,初始化 Content 模块全局的部分(ContentMainRunner[Impl],BrowserMainRunner[Impl]),包括初始化 Browser Process,创建 Browser Threads 等等;
    3.4 WebViewChromiumFactoryProvider.initPlatSupportLibrary 注册一些平台相关的回调方法到内核;
  4. WebViewChromium 创建 AwContents 对象并初始化,初始化时创建 Native 的 AwContents 和 WebContents[Impl] 对象;
  5. WebContents[Impl] 对象创建后会进行初始化,一些 Render Host 端的对象会被创建,包括 RenderFrameHostManager (Render Manager)和 RenderViewHostImpl(Render View);

Content 模块的主要对象

当 WebView 开始加载一个 URL 时,Content 模块需要创建和初始化更多的 Render 对象,部分在 Host 端,部分在 Renderer 端:

  1. WebContents[Impl] 需要创建 Renderer 端的 RenderView[Impl] 对象,它跟 Host 端的 RenderViewHost[Impl] 对应,它们之间通过 Chromium 的 IPC 机制进行通讯;
    1.1 如果运行在多进程架构下,Chromium 需要启动一个新的 Render 进程,但是 CAW 使用的是单进程架构,它的 Browser 进程同时也是 Render 进程,不过 CAW 还是会创建 RenderProcessHost[Impl] 对象;
    1.2 RenderProcessHost[Impl] 对象创建 InProcessRendererThread 对象,启动一个 Render 线程;
    1.3 在 Render 线程里面,CAW 会创建 Renderer 端的 RenderProcess[Impl] 和 RenderThread[Impl] 对象,它们是 RenderViewHost[Impl] 和 RenderView[Impl] 之间进行 IPC 通讯的桥梁;
    1.4 在 CAW 里面,所有的 WebView 会共用一个 Render 线程,所以上述的过程只在第一个 WebView 的第一次 LoadURL 才会发生,相关的 Render Process 和 Thread 对象也是全局唯一的;
  2. WebContents[Impl] 通过 RenderViewHost[Impl] 请求创建 RenderView[Impl],如下方代码所示,RenderViewHost[Impl] 通过 IPC 发送一个 ViewMsg_New 消息给 RenderThread[Impl],RenderThread[Impl] 在 Render 线程中创建了 RenderView[Impl];
  3. RenderView[Impl] 创建后需要初始化,此时会初始化 WebKit/Blink,创建和初始化相关对象;
  4. RenderView[Impl] 创建并初始化后,WebContents[Impl] 需要初始化 Compositor,创建和初始化 cc 模块的对象;
  1. bool RenderViewHostImpl::CreateRenderView(
  2. const base::string16& frame_name,
  3. int opener_route_id,
  4. int32 max_page_id) {
  5. ViewMsg_New_Params params;
  6. ...
  7. params.view_id = GetRoutingID();
  8. ...
  9. Send(new ViewMsg_New(params));
  10. return true;
  11. }
  12. void RenderThreadImpl::OnCreateNewView(const ViewMsg_New_Params& params) {
  13. EnsureWebKitInitialized();
  14. // When bringing in render_view, also bring in webkit's glue and jsbindings.
  15. RenderViewImpl::Create(
  16. ...
  17. params.view_id,
  18. ...
  19. params.allow_partial_swap);
  20. }

Content 进程间通讯

当前的 Chromium 的代码,WebContents[Impl] 跟 RenderFrame,RenderView 之间的关系搞的非常复杂,这是因为 Chromium 在做 Out-of-Process iframes 支持改造的缘故,并且这部分改造目前还没有完全完成。

因为这部分代码太复杂,我自己也没完全搞明白,不过使用单进程模型的 CAW 是不支持 Out-of-Process iframes 的。有兴趣的读者可以自行阅读下面三篇文章 WebContents and frame tree dependenciesDesign Plans for Out-of-Process iframesRendering and compositing out of process iframes

Out-of-Process iframes 支持改造前 Content 层进程间通讯的架构

Out-of-Process iframes 支持改造后 Content 层进程间通讯的架构

总的说来,从上图可以看出,改造前和改造后最大的区别在于,Host 端和 Renderer 端 IPC 通讯的主体从 RenderView[Host][Impl] 变成了 RenderFrame[Host][Impl],RenderView[Host][Impl] 被移除,如果一个页面包含多个 Frame,并且它们来源自不同的站点,每个 Frame 都会有自己的 Render 进程,在 Frame 这个级别支持跨进程的页面导航,WebContents[Impl] 需要支持 Browser 进程和多个 Render 进程之间的通讯,包括不同 Render 进程之间的通讯。不过因为现在改造还未完成的缘故,在代码中 RenderView[Host][Impl] 和 RenderFrame[Host][Impl] 仍然同时存在,在 CAW 里面 RenderView[Host][Impl] 仍然是 IPC 通讯的主要对象。

WebView 的销毁

当应用关闭一个 WebView,调用 WebView.destroy 方法时,它会导致 Native 的 AwCotents::Destroy 被调用,AwContents 为了避免在 ShouldOverrideUrlLoading 中死锁,它不会马上销毁自己,而是通过发送一个 UI 线程的 DeleteSoon 消息延迟销毁。AwContents 被销毁会导致 WebContentsImpl,RenderViewHostManager,RenderFrameHostImpl,RenderViewHostImpl 等 Host 端对象被销毁,调用堆栈如下图:

在 Content Host 的销毁过程中,RenderViewHostImpl 的基类 RenderWidgetHostImpl 的 Shutdown 方法会被调用,它会发送一个 ViewMsg_Close IPC 消息给 Content Renderer,代码如下所示:

  1. void RenderWidgetHostImpl::Shutdown() {
  2. RejectMouseLockOrUnlockIfNecessary();
  3. if (process_->HasConnection()) {
  4. // Tell the renderer object to close.
  5. bool rv = Send(new ViewMsg_Close(routing_id_));
  6. DCHECK(rv);
  7. }
  8. Destroy();
  9. }

ViewMsg_Close 消息导致 Renderer 的 RenderViewImpl::OnClose 被调用,它会调用基类的实现 RenderWidget::OnClose,RenderWidget 先做一些 IPC 相关的清理,然后自己给自己发送一个 Close 的线程消息。

RenderViewImpl::Close 会被调用,它先调用基类的实现 RenderWidget::Close,再发送 ViewHostMsg_Close_ACK IPC 消息回 Host,RenderProcessHostImpl::OnCloseACK 将会处理这个消息。在 RenderWidget::Close 里面,RenderWidgetCompositor,还有 WebViewImpl 和它所封装的 Blink 都会被销毁,最后当 Close 执行完毕后,携载这个消息的 Callback 对象在被 MessageLoop 销毁的过程中,会导致它携带的 RenderViewImpl 对象的引用计数变为 0 ,然后 RenderViewImpl 自动被销毁。

  1. void WebViewImpl::close()
  2. {
  3. if (m_page) {
  4. // Initiate shutdown for the entire frameset. This will cause a lot of
  5. // notifications to be sent.
  6. m_page->willBeDestroyed();
  7. m_page.clear();
  8. }
  9. // Should happen after m_page.clear().
  10. if (m_devToolsAgent)
  11. m_devToolsAgent.clear();
  12. // Reset the delegate to prevent notifications being sent as we're being
  13. // deleted.
  14. m_client = 0;
  15. deref(); // Balances ref() acquired in WebView::create
  16. }

WebViewImpl::close 在 RenderViewImpl::Close 过程中被调用,它会销毁 Blink

参考索引

  1. Organization of code for Android WebView
  2. Android 4.4 WebView实现分析
  3. 关于chromium_webview项目
  4. WebContents and frame tree dependencies
  5. Design Plans for Out-of-Process iframes
  6. Rendering and compositing out of process iframes
  7. chromium for android Browser进程创建过程分析
  8. chromium for android Browser进程结构分析
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注