WebView流程分析(上)

来源:互联网 发布:软件制作入门教程 编辑:程序博客网 时间:2024/05/16 05:50

断断续续调试好几天,才把X5WebView的整体流程大概了解清除。本篇是上篇,侧重于讲java层的逻辑。

 

整个WebKit主要分为2个线程,一个是Ui线程,也就是应用程序使用WebView所在的主线程,另一个WebCore线程。webview.java运行在ui线程,webviewcore运行在webkit线程,之间通过消息通信。不同webview对应不同webviewcore(同一个webkit线程)。

 

WebView的消息处理,主要是Ui线程和WebCore线程的交互。一部分Ui线程向WebCore发送的命令操作消息,例如LOAD_URL,另一部分是来自Uitouch消息。

从网上找来的一张图:(出处见文末)

 

 

其中,WebViewClassicWebViewProvider,或者说是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全部sendWebCore线程中去。

 

下面我们重点来看关于排版和宽高变化相关的逻辑。

 

排版和宽高变化相关的逻辑主要集中在WebViewClassic.java WebViewCore.javaZoomManager.javaScaleGestureDetector.java中。

WebViewClassic.javaonHandleUiTouchEvent接收到触屏消息时,转发给ScaleGestureDetectorScaleGestureDetector是个负责收集到双指放大和双击放大的手势的类当检测到这两种消息后,通知ZoomManager

例如

在WebViewClassic.onHandleUiEvent ->mZoomManager.handleDoubleTap中

实现了双击放大

ZoomManager负责存储关于缩放的各种参数,以及判断什么情况下应该缩放。最终缩放相关的消息会走到

ZoomManager.handleScale -> 

ZoomManager.setZoomScale->

WebViewClassic.sendViewSizeZoom

所有宽高以及缩放变化相关的逻辑最后都会走到

WebViewClassic.sendViewSizeZoom

sendViewSizeZoom其实也是个代理函数,这里会收集当前页面的宽高、屏幕高度、缩放系数等,发送到WebViewCore线程的viewSizeChangedviewSizeChanged再调用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控件自身的宽度(当然,还需要减去滚动条)。

viewHeightview的高度+标题栏-标题栏,也就是说,无论标题栏怎么缩上放下,这个值不变。而下面的actualViewHeight就不一样,actualViewHeight代表的是屏幕可视区域的高度,也就是说,会随着标题栏上下移动而移动。

下面开始收集参数,发送给WebViewCore线程。

ViewSizeData data = new ViewSizeData();

data.mWidth = newWidth;

data.mHeight = newHeight;

这里发送的是newWidth,是乘以了缩放系数倒数的高宽,也就是说,如果屏幕被放大的话,webkit内核拿到的是一个缩小的矩形,排版的时候,也是根据这块区域进行。

再看viewSizeChanged,在这接收到sendViewSizeZoom发送过来的参数后,需要再进行一些加工。例如宽度data.mWidth就调用了calculateWindowWidthcalculateWindowWidth首先判断了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是被sendViewSizeZoomnewWidth决定的,也就是说,而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.cppcontentInvalidate()会调用webviewcore.cppcontentDraw(),这个函数会回调webviewcore.java中的contentDraw().

webviewcore.java contentDraw()  会发送消息EventHub.WEBKIT_DRAW。这个消息在webViewCore.javaEventHubtransferMessage()中处理,

调用WebViewCore.javawebkitDraw(); 

webkitDraw()中会先调用nativeRecordContent()nativeRecordContent()会调用WebViewCore.cpprecordContent().

recordContent()会调用recordPictureSet(). 

recordPictureSet()中会调用layoutIfNeededRecursive()以便得到最新的contentwidthcontentheight()

layoutIfNeededRecursive()会调用FrameView.cppLayout()从而触发整颗RenderTreeLayout. 

 recordPictureSet()在调用layoutIfNeededRecursive()后,会接着调用rebuildPicture()。 

 rebuildPicture()会创建一块新的SKPicture, 

SkPicture 用来记录绘制命令,这些命令会在以后draw到一个指定的canvas上。 

这个SKPicture将被保存在WebViewCore.cpp中的 m_contentWTF::Vector<Pictures> mPictures结构中。 

SKPicture 的实例作为参数传给SkAutoPictureRecord的构造函数。 

SKAutoPictureRecord的构造函数会调用SKPicturebeginRecording(). 

SKPicturebeginRecording()函数中会创建一块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就是我们前面提到的调用SKPicturebeginRecording()时新建的一块SKPictureRecord.

 

它的Device是同一函数中创建的一块SKBitmap

WebCore::GraphicsContext作为参数传给webFrameViewdraw()函数。 

之所以调用的是WebFrameViewdraw()函数是因为WebFrameView的构造函数会调用 frameview->setplatformwidget(this),将自身作为platformwidget 设置给FrameView.

 接下来就是WebFrameViewdraw()函数触发的webkit的绘制过程,这个过程中的所有绘制命令都记录在上述新建的SKPicture中。 

 WebFrameView会调用FrameViewpaintContents()同时将WebCore::GraphicsContext作为参数传进去。 

 FrameViewpaintContents()会先调用needsLayout()判断下是否需要重新Layout,如果需要则进入Layout过程,停止Webkitpaint过程。

如果不需要重新Layout,paintContents()会调用RenderView对应的RenderLayer进行具体的paint过程。 

 RenderViewDOM Tree的根节点Document对应的RenderTree上的节点,也是RenderTree的根节点。 

RenderView对应的RenderLayerRenderLayer 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()以便得到最新的contentwidthcontentheight()

recordPictureSet->SKPicture

然后在异步的onDraw里面,通过drawContent->nativeCreateDrawGLFunction

绘制到canvas里。

 

contentInvalidate webviewcore.cpp很多地方都可以产生,如设置了全局背景色等。就会产生重绘消息。

 

这里要注意的是,

WebViewCore.java.webkitDraw的 WebViewClassic.nativeRecordContent中才获取了真实的draw.mContentSize,然后再

发消息NEW_PICTURE_MSG_IDWebViewClassic.setNewPicture

 

setNewPicture是从WebViewCore.webkitDraw发出来的,

webview类或者页面自身通过contentInvalidate产生WEBKIT_DRAW消息,引起webcore绘制,绘制完后通过NEW_PICTURE_MSG_ID消息引起webview无效,从而使得webviewonDraw被调用,把内容显示到屏幕上。

 

另外一处有意思的是,webview控件整个过程其实位置是不会变的,一直都是顶着屏幕最上方,而标题栏的浮动,让webview看起来整体下移,是在onDraw里调用canvas.translate(0, getTitleHeight());实现的。当然,事件处理里面也需要做一些偏移。

当触屏消息让整个webview滚动的时候,其实是安卓系统负责滚动,C层的webkit,只需要在onDraw->drawContent更新可显示区域,让可显示区域整体(mVisibleContentRect)下移。这里我尝试注释掉calcOurVisibleRectr.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

0 0
原创粉丝点击