断断续续调试好几天,才把X5WebView的整体流程大概了解清除。本篇是上篇,侧重于讲java层的逻辑。
整个WebKit主要分为2个线程,一个是Ui线程,也就是应用程序使用WebView所在的主线程,另一个WebCore线程。webview.java运行在ui线程,webviewcore运行在webkit线程,之间通过消息通信。不同webview对应不同webviewcore(同一个webkit线程)。
WebView的消息处理,主要是Ui线程和WebCore线程的交互。一部分Ui线程向WebCore发送的命令操作消息,例如LOAD_URL,另一部分是来自Ui的touch消息。
从网上找来的一张图:(出处见文末)
其中,WebViewClassic是WebView的Provider,或者说是delegate,凡是涉及到消息处理或者需要跟WebCore交互的接口,都是直接调用WebViewClassic的同名函数。
WebViewCore是C层真正的webkit的代理层。位于另外一个线程。而相对的WebViewClassic属于ui线程。总体的逻辑是,WebViewClassic负责接收和处理来自UI的各种消息,如绘制、触屏消息,然后发送到WebViewCore。WebViewCore经过进一步处理后,再转发给c层。
这里要注意的是,Ui线程第一次向WebCore线程发送的消息,并没有直接被分发到WebCore线程中去。而是被缓存在WebViewCore中的mMessages list中,因为有可能在WebKit的消息处理框架还未初始化完毕,Ui线程就已经开始向WebCore线程发送消息了。所以,当WebViewCore最后初始化完毕之后,会调用transferMessages(),在transferMessages中将mMessages中的消息通过mHandler全部send到WebCore线程中去。
下面我们重点来看关于排版和宽高变化相关的逻辑。
排版和宽高变化相关的逻辑主要集中在WebViewClassic.java 、WebViewCore.java、ZoomManager.java、ScaleGestureDetector.java中。
当WebViewClassic.java:onHandleUiTouchEvent接收到触屏消息时,转发给ScaleGestureDetector,ScaleGestureDetector是个负责收集到双指放大和双击放大的手势的类,当检测到这两种消息后,通知ZoomManager。
例如
在WebViewClassic.onHandleUiEvent ->mZoomManager.handleDoubleTap中
实现了双击放大
ZoomManager负责存储关于缩放的各种参数,以及判断什么情况下应该缩放。最终缩放相关的消息会走到
ZoomManager.handleScale ->
ZoomManager.setZoomScale->
WebViewClassic.sendViewSizeZoom。
所有宽高以及缩放变化相关的逻辑最后都会走到
WebViewClassic.sendViewSizeZoom。
sendViewSizeZoom其实也是个代理函数,这里会收集当前页面的宽高、屏幕高度、缩放系数等,发送到WebViewCore线程的viewSizeChanged。viewSizeChanged再调用nativeSetSize,将参数设置到C层。
sendViewSizeZoom中有几个地方需要注意一下,下面看代码:
int viewWidth = getViewWidth();
int newWidth = Math.round(viewWidth * mZoomManager.getInvScale());
// This height could be fixed and be different from actual visible height.
int viewHeight = getViewHeightWithTitle() - getTitleHeight();
int newHeight = Math.round(viewHeight * mZoomManager.getInvScale());
int actualViewHeight = Math.round(getViewHeight() * mZoomManager.getInvScale());
这里的viewWidth即为webview控件自身的宽度(当然,还需要减去滚动条)。
viewHeight是view的高度+标题栏-标题栏,也就是说,无论标题栏怎么缩上放下,这个值不变。而下面的actualViewHeight就不一样,actualViewHeight代表的是屏幕可视区域的高度,也就是说,会随着标题栏上下移动而移动。
下面开始收集参数,发送给WebViewCore线程。
ViewSizeData data = new ViewSizeData();
data.mWidth = newWidth;
data.mHeight = newHeight;
这里发送的是newWidth,是乘以了缩放系数倒数的高宽,也就是说,如果屏幕被放大的话,webkit内核拿到的是一个缩小的矩形,排版的时候,也是根据这块区域进行。
再看viewSizeChanged,在这接收到sendViewSizeZoom发送过来的参数后,需要再进行一些加工。例如宽度data.mWidth就调用了calculateWindowWidth。calculateWindowWidth首先判断了getUseWideViewPort。如果为true,则进行了一系列如适屏排版、默认DPI等处理。
例如,如果设置了默认mViewportWidth,则一直使用mViewportWidth;也就是说,如果设置过UseWideViewPort,则缩放功能无效,宽度是通过一系列别的参数计算出来。
处理过后的width再拿出去用。如果width在刚才发生变化,则高度会根据之前ViewSizeData 保存的高宽比重新调整。
加工完后,viewSizeChanged调用nativeSetSize,以及contentDraw,开始刷新webview。
contentDraw中,会发送消息给ui线程通知刷新。
浏览器的排版宽度受上层ui设置的webview宽度影响。如果webview没设置或者是设置成0,浏览器内核会用默认的排版宽度320px进行排版。这个逻辑是在
WebViewCore.cpp 构造函数里写着
, m_screenWidth(320)
, m_screenHeight(240)
其中m_screenWidth,会在setSizeScreenWidthAndScale里设置。
而setSizeScreenWidthAndScale即是nativeSetSize调用过来。所以一直向上追溯的话,m_screenWidth是被sendViewSizeZoom的newWidth决定的,也就是说,而m_screenHeight是被actualViewHeight决定的。这两个都跟随着缩放而变化。
另一方面,滚动和屏幕绘制也会引起sendViewSizeZoom:
WebViewClassic.sendViewSizeZoom(boolean) line: 3954
WebViewClassic.contentSizeChanged(boolean) line: 5150
WebViewClassic.recordNewContentSize(int, int, boolean) line: 3787
WebViewClassic.setNewPicture(WebViewCore$DrawData, boolean) line: 12193
WebViewClassic$PrivateHandler.handleMessage(Message) line: 10971
WebViewClassic$PrivateHandler(Handler).dispatchMessage(Message) line: 102
Looper.loop() line: 136
ActivityThread.main(String[]) line: 5050,
标题栏浮动也会引起sendViewSizeZoom:
WebViewClassic.sendViewSizeZoom(boolean) line: 3952
WebViewClassic.onFloatAddressBarChanged(boolean) line: 14107
X5WebViewAdapter(WebView).onFloatAddressBarChanged(boolean) line: 3011
X5WebViewAdapter.onFloatAddressBarChanged(boolean) line: 1178
AddressBarDataSource.onFloatAddressBarAnimationEnd(boolean) line: 257
AddressBarController.onFloatAddressBarHideAnmationEnd() line: 2764
FloatAddressBar.computeScroll() line: 716
但sendViewSizeZoom中其实是有个判断的,只有确实宽高发生变化,才会发送消息到Core线程,除非强制刷新。这也避免了不必要的性能开销。
下面开始看屏幕绘制相关逻辑。
以下是网上说的(出处见文末)
webviewcore.cpp的contentInvalidate()会调用webviewcore.cpp的contentDraw(),这个函数会回调webviewcore.java中的contentDraw().
webviewcore.java contentDraw() 会发送消息EventHub.WEBKIT_DRAW。这个消息在webViewCore.java的EventHub的transferMessage()中处理,
调用WebViewCore.java的webkitDraw();
webkitDraw()中会先调用nativeRecordContent(),nativeRecordContent()会调用WebViewCore.cpp中recordContent().
recordContent()会调用recordPictureSet().
recordPictureSet()中会调用layoutIfNeededRecursive()以便得到最新的contentwidth和contentheight()
layoutIfNeededRecursive()会调用FrameView.cpp的Layout()从而触发整颗RenderTree的Layout.
recordPictureSet()在调用layoutIfNeededRecursive()后,会接着调用rebuildPicture()。
rebuildPicture()会创建一块新的SKPicture,
SkPicture 用来记录绘制命令,这些命令会在以后draw到一个指定的canvas上。
这个SKPicture将被保存在WebViewCore.cpp中的 m_content的WTF::Vector<Pictures> mPictures结构中。
SKPicture 的实例作为参数传给SkAutoPictureRecord的构造函数。
SKAutoPictureRecord的构造函数会调用SKPicture的beginRecording().
在SKPicture的beginRecording()函数中会创建一块SKBitmap,和一块SkPictureRecord。并将新创建的SKBitmap作为Device设置给SKPictureRecord.
SKPictureRecord继承自SKCanvas.
SKPictureRecord会作为参数传给PlatformGraphicsContext的构造函数,赋值给SKCanvas类型的变量mCanvas.
PlatformGraphicsContext会作为参数构造WebCore::GraphicsContext。
WebCore::GraphicsContext的构造函数会调用platformInit().
GraphicContext.cpp封装了平台相关的图形库信息,不同的平台会重新实现GraphicContext定义的接口。
Android平台的GraphicContext实现在文件GraphicContextAndroid.cpp中。
所以WebCore::GraphicsContext中调用的PlatformInit()的具体实现在GraphicContextAndroid.cpp中。
PlatformInit()会新创建一个GraphicsContextPlatformPrivate实例。这是典型的代理模式。
即GraphicsContext中含有一个与具体平台相关的私有成员变量GraphicsContextPlatformPrivate来处理平台相关的逻辑。
发送给GraphicsContext的请求都转发给这个代理类来执行。
传给WebCore::GraphicsContext构造函数的PlatformGraphicsContext实例变量的指针会保存在GraphicsContextPlatformPrivate实例变量的成员变量m_platformGfxCtx中。
这样我们就清楚通过GraphicsContext类得到的SKCanvas就是我们前面提到的调用SKPicture的beginRecording()时新建的一块SKPictureRecord.
它的Device是同一函数中创建的一块SKBitmap。
WebCore::GraphicsContext作为参数传给webFrameView的draw()函数。
之所以调用的是WebFrameView的draw()函数是因为WebFrameView的构造函数会调用 frameview->setplatformwidget(this),将自身作为platformwidget 设置给FrameView.
接下来就是WebFrameView的draw()函数触发的webkit的绘制过程,这个过程中的所有绘制命令都记录在上述新建的SKPicture中。
WebFrameView会调用FrameView的paintContents()同时将WebCore::GraphicsContext作为参数传进去。
FrameView的paintContents()会先调用needsLayout()判断下是否需要重新Layout,如果需要则进入Layout过程,停止Webkit的paint过程。
如果不需要重新Layout,paintContents()会调用RenderView对应的RenderLayer进行具体的paint过程。
RenderView是DOM Tree的根节点Document对应的RenderTree上的节点,也是RenderTree的根节点。
RenderView对应的RenderLayer是RenderLayer Tree上的根节点。
总结一下就是:
webviewcore.cpp.contentInvalidate() ->
webviewcore.cpp.contentDraw()-->
webviewcore.java.contentDraw().->
EventHub.WEBKIT_DRAW->
webViewCore.java EventHub.transferMessage()中处理,
WebViewCore.java.webkitDraw()->
WebViewCore.java.nativeRecordContent()
WebViewCore.cpp.recordContent().->
WebViewCore.cpp.recordPictureSet(). ->
WebViewCore.cpp.layoutIfNeededRecursive()以便得到最新的contentwidth和contentheight()
recordPictureSet->SKPicture
然后在异步的onDraw里面,通过drawContent->nativeCreateDrawGLFunction
绘制到canvas里。
contentInvalidate 在webviewcore.cpp很多地方都可以产生,如设置了全局背景色等。就会产生重绘消息。
这里要注意的是,
在WebViewCore.java.webkitDraw的 WebViewClassic.nativeRecordContent中才获取了真实的draw.mContentSize,然后再
发消息NEW_PICTURE_MSG_ID到WebViewClassic.setNewPicture;
setNewPicture是从WebViewCore.webkitDraw发出来的,
webview类或者页面自身通过contentInvalidate产生WEBKIT_DRAW消息,引起webcore绘制,绘制完后通过NEW_PICTURE_MSG_ID消息引起webview无效,从而使得webview的onDraw被调用,把内容显示到屏幕上。
另外一处有意思的是,webview控件整个过程其实位置是不会变的,一直都是顶着屏幕最上方,而标题栏的浮动,让webview看起来整体下移,是在onDraw里调用canvas.translate(0, getTitleHeight());实现的。当然,事件处理里面也需要做一些偏移。
当触屏消息让整个webview滚动的时候,其实是安卓系统负责滚动,C层的webkit,只需要在onDraw->drawContent更新可显示区域,让可显示区域整体(mVisibleContentRect)下移。这里我尝试注释掉calcOurVisibleRect的r.offset,则只能显示当前一屏幕的,下面的都是空白,说明在drawContent里,如果不通知c层绘制,则屏幕是滚动了,但没有绘制出内容。
另外,mContentHeightm才表示网页真正的大小,比如一个百度新闻的页面,有4000多,而是mContentHeightm正是在
WebViewClassic.recordNewContentSize里获取到的。
还有几个函数,如WebViewCore.java::setupViewport ,
WebViewCore.java::didFirstLayout,逻辑也很复杂,setupViewport 大体的意思就是通过外部设置的一系列参数,初始化一些viewport的变量,如mViewportWidth等。其中能影响到的因素有适屏排版,网页的mete,默认DPI等。感觉写的非常复杂,有点混乱,希望以后能整理一下。
其他一些零散点:
WebViewClassic.onOverScrolled (onOverScrolled->onScrollChanged
)处理了滚动的阻尼效应;
drawOverScrollBackground负责绘制网页由xx提供技术支持的隐藏背景。
mInOverScrollMode表示是否拉出界了;
WebViewCore.cpp:viewSizeChanged的 updateDisplayMetrics()
以及mBrowserFrame.nativeOrientationChanged(mOrientation);
处理了转屏相关;
参考文献:
http://biancheng.dnbcw.info/shouji/436999.html
http://www.anquanweb.com/anquan/xiaoyuananquan/daxueanquan/2011/1019/16246_4.html
http://blog.csdn.net/jaylinzhou/article/details/8579200
http://blog.csdn.net/jaylinzhou/article/details/27517471
http://dl2.iteye.com/upload/attachment/0077/6139/0cf989b2-c4d6-3ca8-bc98-e233640c9109.png