@rogeryi
2015-03-05T17:47:38.000000Z
字数 9157
阅读 14971
Android
WebView
作者: 易旭昕 (@roger2yi)
说明: 访问 Cmd Markdown 版本可以获得最佳阅读体验
Chromium Android WebView 研究系列文章
本文主要描述 Chromium Android WebView (下文简称CAW)是如何对 Chromium Content 进行封装,对外提供一个封装好的 Android View - WebView 供第三方应用使用。代码涉及 Android WebView 和 Chromium Content 模块其中的一部分。
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/webkit 和 frameworks/webview 目录下。
frameworks/base/core/java/android/webkit 主要为第三方应用提供公开的 WebView API,它的代码包括:
frameworks/webview 是 Android WebView API 与 Chromium Content 层之间的 glue layer,它隔绝了 frameworks/base 和 external/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 的代码组织如下:
Chromium source tree
Android source tree
android_webview.gyp 是 Chromium C++ 代码的 GYP 工程文件,GYP 工具会将它转换成编译文件,然后使用 Ninjia 编译工具进行编译,下面是它的部分示例代码,我们可以看到 libwebviewchromium.so target 是由 android_webview_common 静态库加上一个入口文件 webview_entry_point.cc 组成。
'targets': [
{
'target_name': 'libwebviewchromium',
'type': 'shared_library',
'android_unmangled_name': 1,
'dependencies': [
'android_webview_common',
],
...
'sources': [
'lib/main/webview_entry_point.cc',
],
},
webview_entry_point.cc 实际上只包括了一个 JNI_OnLoad 函数,在 libwebviewchromium.so 加载时完成部分 JNI 方法的注册。
// This is called by the VM when the shared library is first loaded.
// Most of the initialization is done in LibraryLoadedOnMainThread(), not here.
JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
base::android::InitVM(vm);
...
return JNI_VERSION_1_4;
}
放飞梦想的开源项目 ChromiumWebView 提供一种编译独立的 WebView 的方式,它具体的做法是,建立一个自己的工程 chromeview,这个工程包括:
ChromiumWebView 提供的 chromeview.gyp 工程文件如下:
{
'target_name': 'libchromeview',
'type': 'shared_library',
'android_unmangled_name': 1,
'dependencies': [
'../android_webview/android_webview.gyp:android_webview_common',
],
'sources': [
'native/jni_entry_point.cpp',
'native/draw_gl_functor.cpp',
],
...
},
它自带了一个编译好的 libchromeview.so,如果要自己编译 libchromeview.so,需要 checkout Chromium 的代码,把 chromeview 置于 Chromium 代码的第一级目录下,使用 chromeview.gyp 工程文件编译即可。另外注意的是 Chromium 的版本要跟 chromeview 里面的 Java 代码相吻合,当前的 chromeview 使用的是 1847 版本。
下图展示了 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 |
Content Start 调用图
当应用创建一个 WebView 时:
Content 模块的主要对象
当 WebView 开始加载一个 URL 时,Content 模块需要创建和初始化更多的 Render 对象,部分在 Host 端,部分在 Renderer 端:
bool RenderViewHostImpl::CreateRenderView(
const base::string16& frame_name,
int opener_route_id,
int32 max_page_id) {
ViewMsg_New_Params params;
...
params.view_id = GetRoutingID();
...
Send(new ViewMsg_New(params));
return true;
}
void RenderThreadImpl::OnCreateNewView(const ViewMsg_New_Params& params) {
EnsureWebKitInitialized();
// When bringing in render_view, also bring in webkit's glue and jsbindings.
RenderViewImpl::Create(
...
params.view_id,
...
params.allow_partial_swap);
}
当前的 Chromium 的代码,WebContents[Impl] 跟 RenderFrame,RenderView 之间的关系搞的非常复杂,这是因为 Chromium 在做 Out-of-Process iframes 支持改造的缘故,并且这部分改造目前还没有完全完成。
因为这部分代码太复杂,我自己也没完全搞明白,不过使用单进程模型的 CAW 是不支持 Out-of-Process iframes 的。有兴趣的读者可以自行阅读下面三篇文章 WebContents and frame tree dependencies,Design Plans for Out-of-Process iframes 和 Rendering 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.destroy 方法时,它会导致 Native 的 AwCotents::Destroy 被调用,AwContents 为了避免在 ShouldOverrideUrlLoading 中死锁,它不会马上销毁自己,而是通过发送一个 UI 线程的 DeleteSoon 消息延迟销毁。AwContents 被销毁会导致 WebContentsImpl,RenderViewHostManager,RenderFrameHostImpl,RenderViewHostImpl 等 Host 端对象被销毁,调用堆栈如下图:
在 Content Host 的销毁过程中,RenderViewHostImpl 的基类 RenderWidgetHostImpl 的 Shutdown 方法会被调用,它会发送一个 ViewMsg_Close IPC 消息给 Content Renderer,代码如下所示:
void RenderWidgetHostImpl::Shutdown() {
RejectMouseLockOrUnlockIfNecessary();
if (process_->HasConnection()) {
// Tell the renderer object to close.
bool rv = Send(new ViewMsg_Close(routing_id_));
DCHECK(rv);
}
Destroy();
}
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 自动被销毁。
void WebViewImpl::close()
{
if (m_page) {
// Initiate shutdown for the entire frameset. This will cause a lot of
// notifications to be sent.
m_page->willBeDestroyed();
m_page.clear();
}
// Should happen after m_page.clear().
if (m_devToolsAgent)
m_devToolsAgent.clear();
// Reset the delegate to prevent notifications being sent as we're being
// deleted.
m_client = 0;
deref(); // Balances ref() acquired in WebView::create
}
WebViewImpl::close 在 RenderViewImpl::Close 过程中被调用,它会销毁 Blink