Android角度理解WebKit的探究介绍

郭鹏

前言:

本文主要介绍站在Android开发者角度理解的WebKit工作机制以及相关用法,众所周知由于浏览器内核的差异化,在很多情况下需要兼容性的处理,在app端中所用到的webview因为系统所支持内核的不同同样存在兼容性问题,下面主要介绍下比较常见的webkit内核的相关内容。

原理:

WebKit的层次结构如下图所示:

1.当点击链接时,FrameLoader会创建一个新的DocumentLoader对象,并置于“policy”状态,客户端会告诉FrameLoader将加载操作视为一次导航。

2.FrameLoader将DocumentLoader置于“provisional”状态,此时将开始网络请求并等待结论:这个网络请求最终是下载一个文件还是一份可解析的文档。

3.DocumentLoader接下来会创建MainResourceLoader对象,它的作用是与浏览器所运行的系统所提供的网络库打交道,网络库通过ResourceHandle接口提供。一旦加载系统通过网络获得足够多信息,以便把文档进行呈现,FrameLoader将DocumentLoader置于“committed”状态,这时Frame对象就要显示这个新加载的文档了。状态变迁过程如下图所示:

主资源加载

由WebView控件的loadUrl()触发的,WebView控件是应用开发者直接使用的显示网页的控件WebViewClient和WebChromeClient是framework层回调的由开发者实现的接口。这两个类使开发者可以对自己感兴趣的下载和显示状态加入自定义的处理,并通过 WebView的setWebViewClient和setChromeClient接口注册给Framework,Framework中
通过CallbackProxy来管理这两个类,即应用中实现的接口是通过CallbackProxy来回调的,CallBackProxy同时又注册给了WebViewCore和BrowserFrame:

1.java层的类WebViewCore及其native层是连接WebView和WebKit内核的桥梁

2.BrowserFrame是WebKit内核中的Frame概念在java层的投射。BrowserFrame的native层是WebFrame.这个类的实现在webcoreframebridge.cpp中WebViewCore,WebFrame,Page,Frame,BrowserFrame,FrameLoaderClientAndroid之间关系建立是在webcoreframebridge.cpp的createFrame()函数中下载过程是FrameLoader调用FrameLoaderClientAndroid得到DocumentLoader后,由DocumentLoader调用MainResourceLoader完成的,MainResourceLoader调用ResourceHandle后的流程与subResourceLoader调用ResourceHandle的流程相同主资源数据从网络返回后会调用MainResourceLoader的didReceiveData()。
MainResourceLoader会将数据通过DocumentLoader传给DocumentWriter,DocumentWriter再将数据传给HTMLDocumentParser,至此主资源的加载完成,接下来进入DOM tree的创建过程,主资源加载流程图如下图所示:

子资源加载

网页上的子资源包括:Image, Script, cssStyleSheet, Font, XSLStyleSheet等,这些子资源是以document的元素或元素属性形式出现的,解析 Html 页面的时候,解析到XXX标签,调用HTMLXXXElement::create 创建 HTMLXXXElement 对象,由HTMLXXXElement负责加载CachedResourceLoader是WebKit中管理子资源加载的类,WebKit中任何想加载子资源的 类都通过调用CachedResourceLoader提供的接口来实现子资源加载,如:

1.HTMLLinkElement调用CachedResourceLoader的接口requestCSSStyleSheet()来加载cssStyleSheet子资源。

2.ScriptElement调用CachedResourceLoader的接口requestScript()来加载script子资源。

3.ImageLoader调用CachedResourceLoader的接口requestImage()来加载image子资源。

CachedResourceClient是注册给CachedResource的回调类,以便需要加载子资源的类可以知道子资源的加载状态(请求的子资源数据是否到来,加载是否完成),所以HTMLLinkElement,ScriptElement,ImageLoader都需要继承自CachedResourceClient,并通过CachedResource提供的接口setClient()将自己注册给CachedResource,CachedResource在接收到子资源数据时回调CachedResourceClient接口CachedResource是表示子资源的一般类,每一种特定的子资源都继承自它。如:CachedScript, CachedImage, CacheFont, CachedCSSStyleSheet。

1.CachedResourceClient与CachedResource是典型的监听者模式。

2.CachedResourceRequest与CachedResourceLoader,CachedResource,SubResourceLoader之间是一个中介者模式,CachedResourceRequest作为具体中介者负责协调三者之间的协作,SubResourceLoaderClient提供接口供SubResourceLoader回调,以通知子资源下载状态。

1.CachedResourceRequest需要了解子资源的下载状态以便协调CachedResourceLoader, CachedResource的工作,所以它继承自SubResourceLoaderClient。

2.CachedResourceRequest通过ResourceLoadScheduler的接口创建SubResourceLoader 的实例时,将自身作为参数传给SubResourceLoader。SubResourceLoader在子资源下载状态发生变化时回调CachedResourceRequest的接口。

3.每加载一个子资源CachedResourceLoader就会创建一个CachedResourceRequest的实例。

由于涉及多个下载请求的异步处理,所以WebKit采用ResourceLoadScheduler来管理下载请求,即管理各个SubResourceLoader的下载顺序。

1.SubResourceLoader与MainResourceLoader都继承自ResourceLoader,ResourceLoader
使用ResourceHandle来实现具体的下载功能。

2.ResourceLoader为了获知下载状态继承了ResourceHandleClient类,并在创建ResourceHandle类时将自身作为参数传给ResourceHandle。

3.ResourceHandleClient提供接口供ResourceHandle调用来通知下载状态,网络下载是平台相关的,WebKit使用ResourceHandleInternal封装了平台差异性。

  1. ResourceHandle包含一个ResourceHandleInternal的实例的引用,两者是典型的代理模式,子资源加载类图如下:

子资源缓存机制

网页不仅仅是由HTML组成。我们还需要加载其中的图片、脚本等等。DocLoader对象就来 负责加载这些资源(注意DocLoader和DocumentLoader名字很像,但是分工是不同的,为了加载一张图片,首先询问Cache是否已经有该图片的副本(CachedImage对象)。

1.如果存在,则可直接加载。为了更加高效,Cache经常在内存中保存解码之后的图片数据,这样避免解码两次。

2.如果图片没有在Cache中,Cache会创建一个CachedImage对象来表示这个图片,并发起网络请求,会创建SubResourceLoader来做这个事情,缓存机制如下图所示:

DOM Tree解析

HTMLDocumentParser封装了创建DOM tree要用到的主要类:

1.HTMLInputStream,存放网络上取到的原始数据。

2.HTMLTokenizer,将原始数据分成HTMLToken。

3.HTMLSourceTracker,用于跟踪原始数据的当前位置。

4.HTMLTreeBuilder,利用HTMLToken创建DOM树,具体的创建过程通过调用类HTMLConstructionSite的接口完成。

1.得到主资源(text/html),DocumentLoader将主资源传给DocumentWriter,DocumentWriter最终将数据传给了DecodedDataDocumentParser。

2.将主资源解码为Unicode字符。从网络上取到的主资源数据是原始的字符数据经过特定的字符编码形成的字节流,需先将编码后的字节流解码成原始的字符,再交给Tokenizer进一步处理,这项工作是由DecodedDataDocumentParser在appendBytes() 函数中通过调用TextResourceDecoder类的具体字符解码器完成的解码后的字符数据保存在类HTMLInputStream的实例中,在创建DOM Tree的过程中可能会执行脚本document.write(), 这会改变文档的结构,HTMLInputStream提供接口供HTMLDocuemntParser调用来临时增加Document内容, InsertionPointRecord用来记录通过Document.write()增加的内容在HTMLInputStream中的插入信息。

3.将解码后的主资源分割为HTMLToken,HTMLTokenizer负责将HTMLInputStream中的数据分割成HTMLToken。 HTMLToken 可以看作是html文档的基本组成单位,有以下几种类型: Uninitialized,DOCTYPE,StartTag, EndTag, Comment, Character, EndOfFile,DOCTYPE 对应 html 文档中的 StartTag对应 html 文档中的head、body等, EndTag对应 html 文档中的head、body等,Comment ,Character对应html 标签之间的字符内容。

4.创建DOMTree,RenderTree,HTMLTreeBuilder提供私有方法处理不同类型的HTMLToken,来创建DOMTree:

void processDoctypeToken(AtomicHTMLToken&);  
void processStartTag(AtomicHTMLToken&);  
void processEndTag(AtomicHTMLToken&);  
void processComment(AtomicHTMLToken&);  
void processCharacter(AtomicHTMLToken&);  
void processEndOfFile(AtomicHTMLToken&);  

DOM Tree解析过程如下图所示:

Android WebKit框架

主要类介绍:

1.WebView
该类是WebKit模块Java层的视图类,所有需要使用Web浏览功能的Android应用程序都要创建该视图对象显示和处理请求的网络资源,WebView作为应用程序的UI 接口。

2.WebViewDatabase
该是WebKit模块中针对SQLiteDatabase对象的封装,用于存储和获取运行时浏览器保存的缓冲数据、历史访问数据、浏览器配置数据等。该对象是一个单实例对象, 通过getInstance方法获取WebViewDatabase的实例,WebViewDatabase是WebKit模块中的内部对象,仅供WebKit框架内部使用。

3.WebViewCore
该类是Java层与C层WebKit核心库的交互类,客户程序调用WebView的网页浏览相关操作会转发给BrowserFrame对象。当WebKit核心库完成实际的数据分析和处理后会回调WebViweCore中定义的一系列JNI接口,这些接口会通过CallbackProxy将相关事件通知相应的UI对象。

4.CallbackProxy
该类是一个代理类,用于UI线程和WebCore线程交互。该类定义了一系列与用户相关的通知方法,当WebCore完成相应的数据处理,则会调用CallbackProxy类中对应的方法,这些方法通过消息方式间接调用相应处理对象的处理方法。

5.BrowserFrame
该类负责URL资源的载入、访问历史的维护、数据缓存等操作,该类会通过JNI接口直接与WebKit C层库交互。

6.JWebCoreJavaBridge
该类为Java层WebKit代码提供与C层WebKit核心部分的Timer和Cookies操作相关的方法。

7.WebSettings
该类描述了WEB浏览器访问相关的用户配置信息。

8.WebViewClient
该类定义了一系列事件方法,如果Android应用程序设置了WebViewClient派生对象, 则在页面载入、资源载入、页面访问错误等情况发生时,该派生对象的相应方法会被调用。

9.WebChromeClient
该类定义了与浏览窗口修饰相关的事件,例如接收到Title、接收到Icon、进度变化时,WebChromeClient的相应方法会被调用。

WebKit初始化

当新建WebView对象时,会初始化WebKit库(Java层和C层),通过操作WebView对象完成 网络或者本地资源的访问WebView对象的生成主要涉及4个类CallbackProxy、WebViewCore、WebViewDatabase以及BrowserFrame。

1.CallbackProxy,为WebKit模块中UI线程和WebKit类库提供交互功能。

2.WebViewCore,是WebKit的核心层,负责与C层交互以及WebKit模块C层类库初始化。

3.WebViewDatabase,为WebKit模块运行时缓存、cookie等数据存储提供支持。

4.BrowserFrame,用于创建WebCore中的Frame,并为Frame提供Java层回调方法。

WebKit模块初始化流程如下:

1.创建CallbackProxy对象。

2.创建WebViewCore对象。

3.调用System.loadLibrary载入WebCore相关类库(C层)该过程由Dalvik虚拟机完成,它会从动态链接库目录中寻找libWebCore.so类库, 载入到内存中,并且调用WebKit模块的JNI_OnLoad方法WebCoreJniOnLoad.cpp)。

4.如果是第一次初始化WebViewCore对象,创建WebCoreTherad线程。

5.创建EventHub对象,处理WebViewCore事件。

6.获取WebIconDatabase对象实例。

7.向WebCoreThread发送初始化消息。

8.创建BrowserFrame对象。

9.向WebView发送WEBCOREINTIALIZEDMSG_ID消息,通知初始化完成。

10.获取WebViewDatabase实例。

11.调用init初始化WebView。

12.收到WEBCOREINITIALIZEDMSG_ID消息后,调用nativeCreate。

WebKit主要类调用流程如下:

后语:

由于Android端的趋势是JS化学,无论是之前的H5支持还是后来的Weex框架,对Web框架的理解与研究都会对我们在该领域的成长有很大的帮助,欢迎同学们探讨交流。