WebView流程分析(上)

断断续续调试好几天,才把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

时间: 2024-10-06 21:45:57

WebView流程分析(上)的相关文章

Linux系统启动流程分析与关机流程

Linux 系统启动流程分析 Linux系统的启动过程并不是大家想象中的那么复杂,其过程可以分为5个阶段: 内核的引导. 运行 init. 系统初始化. 建立终端. 用户登录系统. init程序的类型: SysV: init, CentOS 5之前, 配置文件: /etc/inittab. Upstart: init,CentOS 6, 配置文件: /etc/inittab, /etc/init/*.conf. Systemd: systemd, CentOS 7,配置文件: /usr/lib/

Cocos2d-x3.3RC0的Android编译Activity启动流程分析

本文将从引擎源代码Jni分析Cocos2d-x3.3RC0的Android Activity的启动流程,以下是具体分析. 1.引擎源代码Jni.部分Java层和C++层代码分析 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXV4aWt1b18x/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" > watermark/2/text/aHR0cDov

基于linux与busybox的reboot命令流程分析

http://www.xuebuyuan.com/736763.html 基于Linux与Busybox的Reboot命令流程分析 *************************************************************************************************************************** 作者:EasyWave                                                

Android View measure (一) 流程分析

本篇模拟三个角色:Android 架构师-小福.Android  控件开发工程师-小黑. Android 开发工程师-小白,下面按照三个角色不同角度分析measure过程. 小福负责分享: measure的本质 - ok measure代码流程 - 分析FrameLayout.onMeasure onMeasure方法与MeasureSpec - ok 提出问题 Android 架构师-小福的分享 一.Measure本质 小福:我今天分享是的measure架构设计相关的,先问一个问题,measu

nova挂载cinder卷流程分析

Nova挂载cinder卷流程分析 1. nova通过命令nova volume-attach server volume device-name或者http请求 Req:POST /v2/{tenant-id}/servers/{server-id}/os-volume_attachments' Body:{'volumeAttachment': {'device': '/dev/vdb', 'volumeId': '951be889-b794-4723-9ac9-efde61cacf3a'}

region split流程分析

region split流程分析 splitregion的发起主要通过client端调用regionserver.splitRegion或memstore.flsuh时检查并发起. Client通过rpc调用regionserver的splitRegion方法 client端通过HBaseAdmin.split传入regionname与splitpoint(切分的rowkey,能够不传入), 通过meta得到此region所在的server,发起rpc请求,调用HRegionServer.spl

PPTP协议握手流程分析--转载

一  PPTP概述 PPTP(Point to Point Tunneling Protocol),即点对点隧道协议.该协议是在PPP协议的基础上开发的一种新的增强型安全协议,支持多协议虚拟专用网,可以通过密码验证协议,可扩展认证协议等方法增强安全性.远程用户可以通过ISP.直接连接Internet或者其他网络安全地访问企业网: 它能够将PPP(点到点协议)帧封装成IP数据包,以便能够在基于IP的互联网上进行传输.PPTP使用TCP是实现隧道的创建.维护与终止,并使用GRE(通用路由封装)将PP

教你如何做出一份报表:流程分析之报表模板

上周我们谈了流程分析之报表数据源,现在说说报表模板. 进入后台管理----H3管理中心----流程分析----报表模板. 把鼠标悬浮在报表模板上,会显现出五个icon,分别是新增目录.明细汇总表.交叉分析表.删除.刷新,这里主要讲明细汇总表,因为交叉分析表与此大同小异.选择第2个明细汇总表,即可新增. 在数据源下方的下拉框中选择已经建好的报表数据源,这里以上次新建的报表数据源为例,然后来到以下的界面. 从左边中挑选需要的数据项,拖到中间的蓝色矩形条中,如若系统中已有相关的流程数据,那么数据会自动

Android 7.0 ActivityManagerService(8) 进程管理相关流程分析(2)

前一篇博客进程管理相关流程分析(1)里, 我们介绍了AMS中updateLruProcessLocked函数相关的流程. updateLruProcessLocked只是按照进程中运行的组件,粗略地定义了不同进程的优先级. 实际上,Android根据进程的oom_adj进行了更加细致的进程分类, 而AMS中的updateOomAdjLocked函数,就是用于更新进程的oom_adj值. 本篇博客中,我们来看看AMS中updateOomAdjLocked相关的流程. 一.ProcessList.j