《深入理解Android》一2.3 WebKit架构概览

简介:

本节书摘来自华章出版社《深入理解Android》一书中的第2章,第2.3节,作者孟德国 王耀龙 周金利 黎欢,更多章节内容可以访问云栖社区“华章计算机”公众号查看

2.3 WebKit架构概览

我们从浏览器的发展历程和现状分析中可以看出,WebKit是一个功能完备、性能优良、相对轻巧、使用广泛的排版内核,是诸多操作系统上开发浏览器的不二之选。本书集中论述Android 4.2平台上的WebKit移植版本,本节将从组成架构、工作流程和设计风格三个方面对Android WebKit做初步介绍,为读者简单梳理其庞大代码的整体样貌,也是对后续章节的引导。

2.3.1 整体组成架构

Android系统中的WebKit如图2-14所示既是一个Native的动态库也是Framework的一部分,从结构上大致可以划分为7个较为独立的组成部分:
WTF:Web Template Framework,公共基础库;
JS Engine: JavaScript引擎,负责JS脚本的执行,Android采用V8而非JavaScript Core;
WebCore:核心所在,负责HTML/CSS解析、排版和显示以及JS的DOM绑定;
Chromium-Net:Chrome浏览器源码中抽取的网络库;
Skia:Android和Chrome共同使用的图形库,负责底层的图形文字绘制工作;
WebKit:封装所有C++层代码,提供与Java层交互的接口;
Framework:对以上Native层代码的Java层封装,对外提供Framework层的WebKit API。

image

WebCore如图2-15所示是WebKit中最大最复杂的部分,也是排版引擎核心所在,其内部结构可大致进一步细分为如下若干子系统,每个子系统包含大小不一功能上彼此关联的若干模块:
Page:提供对外的总入口以及页面相关的Chrome、Setting、History等模块;
Document:解析html/xml/svg,生成DOM树的子系统;
CSS:负责CSS的解析和匹配;
Render:实现Layout和Render过程的子系统;
Graphics:封装底层图形库,对上提供一套平台无关的绘制接口;
Loader: 负责网络IO、Form Submission、Memory Cache等;
Script: 提供JS执行入口以及从C++对象到JS对象转换的Binding和Bridge;
Extension Modules:实现非DOM的HTML 5扩展JS功能模块,如Worker、WebSocket、WebStorage、FileAPI等,可能需要相应的Custom Binding代码;
Plugin:用于加载NPAPI插件,需要Bridge模块完成和JS引擎的互动;
Interaction: Editing、Accessibility等相对较独立的与交互、命令有关的模块。

image

下面重点介绍WebKit渲染主线上的一些关键概念和核心对象,理解了这些对象就等于抓住了主要节点,纲举目张事半功倍,读者可以从源码层面一一对照阅读分析。
Page模块如图2-16所示,Page类(位于Source/WebCore/page/Page.h)代表整个页面,一个Page对象包含一个作为MainFrame 的Frame对象(Source/WebCore/page/Frame.h)。每个Frame表示某URI对应的主文档资源在一个WebKit页面中加载后的可以呈现出可视化结果的一帧,因此对应一个FrameLoader类(Source/WebCore/loader/FrameLoader)、一个文档模型Document类(Source/WebCore/dom/Document.h)和一个视图FrameView类(Source/WebCore/page/FrameView.h)。Frame可以拥有子Frame,Frame之间可能构成一棵FrameTree,对应于网页中的iframe或frameset标签。和Page对象相关联的对象主要还有Setting对象(Source/WebCore/page/Setting.h)和Chrome对象(Source/WebCore/page/Chrome.h),分别表示页面设置和内容以外的外框。

image

WebKit将资源请求分成两类,分别交由主资源MainResourceLoader和子资源SubResourceLoader来处理。MainResourceLoader负责的是能够载入到Frame的HTML文档,SubResourceLoader对应的则是其余的JavaScript/CSS/Image等资源。ResourceLoader将请求和应答交由ResourceHandle类处理,ResourceHandle负责将网络和IO工作交给更底层的平台相关的对象去处理。Android 4.x系统中WebKit已经开始使用Chromium-Net网络库,网络模块单独移出,网络调用接口也直接上移至ResourceLoaderAndroid中。
Load过程都有各自的请求发起者,WebKit还根据请求对象生存期的不同,将Loader类拆分成不同类型。Page和Frame对应于页面标签和暴露给Shell层的WebView,它们请求URL加载,由FrameLoader负责处理。CachedResource类代表那些能够被缓存的资源,如JS/CSS/Image等,它们可以静态的持续存在,并和Cache模块关联,因此其加载由一个全局静态生存期的Loader类来负责。Frame对象load了某个URL之后,会根据其页面HTML生成对应的Document对象,Document对象的加载请求和应答则是通过DocumentLoader来转发完成。
Loader模块核心类的生存期和调用关系如图2-17所示。
文档对象模型(Document Object Model,DOM)是页面文档在浏览引擎内部的模型表示,Page/Frame/FrameView/WebView则相当于是页面的视图表示,Browser端JavaScript定义的事件方法等脚本则可看成是控制器,三者构成了类MVC的架构。WebKit中文档被Document类对象表示,HTML文档则是继承于Document类的HTMLDocument。文档节点由Node类对象表示,HTML页面元素是HTMLElement类,各种HTML标签元素对应的类都继承于HTMLElement。HTMLElement类和Document类中包含了DOM模型大部分接口内容。构成了浏览器端JavaScript的根对象是Window,在WebKit中对应的实现类则是DOMWindow。图2-18描绘了一些核心DOM对象的类型继承体系。
image

image

WebKit中Render树节点也以一系列具有继承体系的类来表示,其基本类型是RenderObject,具备CSS盒子模型的类型是RenderBoxModelObject,以Inline方式和Block方式排版的Render对象类型分别是RenderInline和RenderBox。为了处理CSS动画、页面内Video、定位方式和包含Z-Index元素的排版渲染,WebKit又引入了RenderLayer类。一个RenderLayer代表一个渲染中的分层,RenderLayer也构成一棵树。每个RenderObject都隶属于某个RenderLayer,通常具有共同坐标系的Render节点属于同一个RenderLayer。图2-19描绘了RenderObject部分重要派生类的继承结构。

image

2.3.2 核心工作流程

介绍完Android WebKit内部的架构组成和重点模块的关键对象之后,本节将根据2.1.2节中介绍的排版引擎工作原理来简单说明WebCore排版引擎各阶段工作的流程入口函数,读者可据此分析源码,顺藤摸瓜,一览究竟。
图2-20从整体上勾勒出了WebCore部分的工作流程图,可以看出其中包括HTML Parser、CSS Parser、Render树创建Attachment、Render树排版Layout和Render树绘制Painting这几个核心过程。
网络IO:加载一个URL的流程,在Framework中从WebView的loadUrl开始,在WebCore中则从FrameLoader::loadURL开始。

image

页面解析:HTML文本由DocumentWriter::addData方法传入,调用HTML DocumentParser类来解析页面。后者使用HTMLTokenizer类做词法分析,获取Token,再交由HTMLTreeBuilder去构建DOM树。
DOM树和Render树创建:建树过程从HTMLTreeBuilder::constructTreeFromTok-en开始,然后调用HTMLContructionSite类的insert类函数,依次把根据Token类型创建的HTMLElement对象加入到DOM树。而Render树的创建过程则以Element::attach方法开始,通过RenderObject::createObject静态方法创建,同时会计算出Element的CSSStyle。
布局排版:排版流程以一系列类的layout方法完成,以FrameView::layout方法为布局起始点,调用Render树根节点的layout,递归触发Render树节点的layout过程。FrameView::layout可以被布局timer或者Shell层的WebViewCore::layout方法触发。
绘制显示:在Android WebKit中,Render树的内容排版后要经过Paint和Draw两个阶段才能显示给最终用户,paint阶段是指把RenderObject的内容画到Bitmap上的过程,Draw阶段则使Bitmap内容最终显示出来,这两者分别在WebCore线程和UI线程中完成。Paint阶段和排版类似,以一系列类的paint方法完成,从FrameView::paint开始,调用RenderLayer树根节点的paintLayer,再依次递归调用Render树Object的paint方法绘制节点的内容。Draw阶段则由WebView类的draw或者drawGL方法触发。
脚本执行:WebKit中JS引擎的封装类为ScriptController,脚本执行的入口函数为ScriptController::evaluate方法。
事件处理:WebKit中的UI事件从Framework层传入给WebViewCore类,主要有keyEvent、mouseEvent、dragEvent和touchEvent等。事件处理的入口类是EventHandler,各种事件一般经EventHandler分发给DOM元素依次处理,按照冒泡事件消息传递的方法递归寻找EventHandler,最终被DefaultEventHandler吸收。

2.3.3 代码设计风格

WebKit开源项目在目前看来虽然体量很大,但是可读性很好,这与该项目从一开始就有着清晰的架构设计和良好的代码风格分不开。相比较而言,更加庞大的Gecko内核因种种原因则设计相对复杂,架构比较臃肿。正因如此苹果公司在开发自有Safari浏览器时,才抛弃了同样是开源的老牌Gecko而选择了不够完善但设计精巧的KHTML,即WebKit的前身。

  1. 代码风格
    下面简单说明WebKit中常用的一些代码技巧和风格,读者在阅读源码和本书的同时可加以留意。

内存管理:WebKit C++代码中内存管理最基本的模式是引用计数,RefCounted模板类定义了ref()和unref()计数加减方法,很多类都继承了该模板类。为了避免不正确或者遗漏的计数使用,又进一步引入了RefPtr和PassRefPtr两个智能指针模板类,它们重载了赋值操作符,自动完成有关计数加减的工作。WebCore中需要传递赋值的共享对象大多使用了智能指针代替指针持有,基本没有了内存泄露的可能。关于智能指针更详细的介绍参见第3章。
代码自动生成:WebKit中的DOM在C++有关模块中实现,同时需要把DOM接口暴露给JavaScript执行引擎。从C++对象到JS对象的Binding实现,WebKit采用了代码自动生成方法,使用Perl脚本根据DOM对象的IDL(Interface Description Language)接口描述文件完成code generator的工作,以机器代替手工编写了大量的Wrap代码。此外,CSS和HTML解析过程也或多或少地采用了机器代码生成的技巧,使得CSS属性值和HTML标签属性的自定义添加变得相当容易,只需修改相应的.in配置文件即可。IDL代码自动生成的细节参见第9章,HTML和CSS相关代码自动生成参见第5章。
代码编写风格:WebKit代码书写十分规范,包括缩进、空格、换行和括号的使用,变量的骆驼命名法(CamelCase),成员和静态成员的前缀区分,条件和分支判断,指针和引用,乃至头文件注释名字空间等都有涉及。代码阅读起来整齐优雅,根据命名大致能猜出含义,读者可参考网络上的官方coding style文档(https://www.webkit.org/coding/coding-style.html)。此外,代码文件的组织分布也有其规则,各模块文件层次有序地按目录结构分隔,平台有关的文件代码基本位于Source/WebCore/platform目录或Source/WebKit目录下对应平台名的子目录中。

  1. 设计模式
    设计模式是软件架构中针对普遍存在、反复出现的需求问题而提出的可复用的解决方案。这里给出一些WebKit中出现的设计模式例子,其具体实现方案与GOF经典设计模式可能会有所差异,但面对的问题域基本是一致的,读者可据此更快速地领会其代码设计意图和工作原理。

单例:单例模式是为了确保某个类仅有一个实例并提供全局访问。WebKit中Loader模块负责管理CachedResource的Memory Cache就是一个典型的单例模式,可缓存资源的管理器理所当然有且只有一个全局访问。
工厂方法:工厂方法模式也是一种创建型设计模式,用于解决在不指定对象具体类型的情况下创建对象的问题,WebKit中Parser模块最终创建实际的HTMLElement所面临的情况正与此类似。HTMLTreeBuilder类和HTMLConstructionSite类将HTML标签名传入工厂类HTMLElementFactory的createElement接口,由它来决定实际创建哪个类型的HTMLElement对象。
观察者模式:观察者模式让对象管理自己的观察者对象,发布事件消息或通知状态改变。WebKit中有很多名称的末尾为Client的类,如FrameLoaderClient、CachedResourceClient等,可以将它们看成是一种观察者类,被观察者如FrameLoader、CachedResource向其 Client观察者类通知自身的状态改变。
组合模式:组合模式通常用于将具有树状结构的对象组织成一个组合对象,WebKit中存在多个重要的树状数据结构,如DOM树、Render树、RenderLayer树等,它们的基类如ContainerNode、RenderObject、RenderLayer都有一套Children和Parent的管理方法,也可以视为组合模式的一个具体例子。
代理模式:代理模式使用一个代理类替原始类完成具体的操作,WebKit中Audio/Video有关元素的实现用到了该模式。HTMLMediaElement类包含一个MediaPlayer类对象来完成具体的多媒体播放功能,而MediaPlayer类则通过MediaPlayerPrivate类来实现实际真正的操作,这里MediaPlayerPrivate就可以看作MediaPlayer的Proxy。
命令模式:命令模式将动作及其参数封装起来用面向对象的设计来表示,WebKit中DOM模块的Event类和Editing模块的Command类都可以看作典型的命令模式实现。
责任链模式:责任链模式可以将命令对象在一系列处理对象中传递,处理对象自行决定如何处理命令或将不能处理的命令传递给链条中的下一个处理对象。WebKit对DOM事件处理的实现与该模式有关,Event对象(作为命令对象)在DOM树(作为处理对象)中按照一定规则(如冒泡)传递并寻找其Event Handler/Listener。

相关文章
|
1月前
|
数据库 Android开发 开发者
构建高性能微服务架构:从理论到实践构建高效Android应用:探究Kotlin协程的优势
【2月更文挑战第16天】 在当今快速迭代和竞争激烈的软件市场中,微服务架构以其灵活性、可扩展性和独立部署能力而受到企业的青睐。本文将深入探讨如何构建一个高性能的微服务系统,涵盖从理论基础到具体实现的各个方面。我们将重点讨论服务拆分策略、通信机制、数据一致性以及性能优化等关键主题,为读者提供一个清晰、实用的指南,以便在复杂多变的业务环境中构建和维护健壮的微服务体系结构。 【2月更文挑战第16天】 在移动开发领域,性能优化和流畅的用户体验是至关重要的。随着技术的不断进步,Kotlin作为一种现代编程语言,在Android开发中被广泛采用,尤其是其协程特性为异步编程带来了革命性的改进。本文旨在深入
241 5
|
3天前
|
传感器 Java Android开发
Android HAL深入探索(1): 架构概述
Android HAL深入探索(1): 架构概述
21 1
|
14天前
|
存储 数据库 Android开发
构建高效安卓应用:采用Jetpack架构组件优化用户体验
【4月更文挑战第12天】 在当今快速发展的数字时代,Android 应用程序的流畅性与响应速度对用户满意度至关重要。为提高应用性能并降低维护成本,开发者需寻求先进的技术解决方案。本文将探讨如何利用 Android Jetpack 中的架构组件 — 如 LiveData、ViewModel 和 Room — 来构建高质量的安卓应用。通过具体实施案例分析,我们将展示这些组件如何协同工作以实现数据持久化、界面与逻辑分离,以及确保数据的即时更新,从而优化用户体验并提升应用的可维护性和可测试性。
|
25天前
|
移动开发 前端开发 数据管理
构建高效Android应用:采用MVVM架构与LiveData的全面指南
在移动开发领域,构建一个既快速又可靠的应用对于开发者来说至关重要。随着Android Jetpack组件的推出,MVVM(Model-View-ViewModel)架构和LiveData已成为实现响应式、可测试且易于维护应用的首选解决方案。本文将深入探讨如何在Android应用中实施MVVM模式,以及如何利用LiveData来优化UI组件的数据更新流程,确保用户界面与业务逻辑之间的高度解耦和流畅交互。
18 4
|
1月前
|
存储 SQL 分布式计算
TiDB整体架构概览:构建高效分布式数据库的关键设计
【2月更文挑战第26天】本文旨在全面概述TiDB的整体架构,深入剖析其关键组件和功能,从而帮助读者理解TiDB如何构建高效、稳定的分布式数据库。我们将探讨TiDB的计算层、存储层以及其他核心组件,并解释这些组件是如何协同工作以实现卓越的性能和扩展性的。通过本文,读者将能够深入了解TiDB的整体架构,为后续的学习和实践奠定坚实基础。
|
3月前
|
存储 前端开发 测试技术
Android 官方架构中的 UseCase 该怎么写?
Android 官方架构中的 UseCase 该怎么写?
66 0
|
6月前
|
设计模式 网络协议 Java
《移动互联网技术》 第十章 系统与通信: 掌握Android系统的分层架构设计思想和基于组件的设计模式
《移动互联网技术》 第十章 系统与通信: 掌握Android系统的分层架构设计思想和基于组件的设计模式
65 0
|
8月前
|
供应链 监控 数据可视化
|
8月前
|
供应链 架构师 双11
|
8月前
|
API Android开发 Kotlin
安卓MVI架构真的来了?动手试着封装吧(三)下
安卓MVI架构真的来了?动手试着封装吧(三)
82 0

热门文章

最新文章