Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析(下)

       ViewRoot类的成员函数invalidateChild首先调用另一个成员函数checkThread来检查当前正在执行的是不是一个UI线程。若是不是的话,ViewRoot类的成员函数checkThread就会抛出一个异常出来。这是由于全部的UI操做都必需要在UI线程中执行。java

       ViewRoot类的成员函数invalidateChild接下来还会检查当前正在处理的应用程序窗口在Y轴上是否出现有滚动条,即成员变量mCurScrollY的值不等于0, 或者前正在处理的应用程序窗口是否运行在兼容模式之下,即成员变量mTranslator的值不等于null。当一个应用程序窗口运行在兼容模式时,它显示出来的大小和它实际被设置的大小是不同的,要通过相应的转换处理。对于上述这两种状况,ViewRoot类的成员函数invalidateChild都须要调整参数dirty所描述的一个须要从新绘制的区域的大小和位置。android

       调整好参数dirty所描述的一个须要从新绘制的区域以后, ViewRoot类的成员函数invalidateChild就将它所描述的一个区域与成员变量mDirty所描述的一区域执行一个合并操做,而且将获得的新区域保存在成员变量mDirty中。从这个操做就能够看出,ViewRoot类的成员变量mDirty描述的就是当前正在处理的应用程序窗口下一次所要从新绘制的总区域。canvas

       设置好当前正在处理的应用程序窗口下一次所要从新绘制的总区域以后,ViewRoot类的成员函数invalidateChild最后就检查成员变量mWillDrawSoon的值是否不等于true。若是ViewRoot类的成员mWillDrawSoon的值等于true的话,那么就说明UI线程的消息队列中已经有一个DO_TRAVERSAL消息在等待执行了,这时候就不须要调用ViewRoot类的成员函数scheduleTraversals来向UI线程的消息队列发送一个DO_TRAVERSAL消息了,不然的话,就须要调用ViewRoot类的成员函数scheduleTraversals来向UI线程的消息队列发送一个DO_TRAVERSAL消息。app

       ViewRoot类的成员函数scheduleTraversals在前面Android应用程序窗口(Activity)的绘图表面(Surface)的建立过程分析一文中已经分析过了,这里再也不详述。框架

       这一步执行完成以后,返回到前面的Step 1中,即View类的成员函数layout中,接下来它就会调用另一个成员函数onLayout来从新布局当前视图的子视图的布局了。View类的成员函数onLayout是由子类来重写的,而且只有当该子类描述的是一个容器视图时,它才会重写父类View的成员函数onLayout。前面咱们已经假设当前正在处理的是应用程序窗口的顶层视图,它的类型为DecorView,而且它描述的是一个容器视图,所以,接下来咱们就会继续分析DecorView类的成员函数onLayout的实现。ide

       事实上,DecorView类是经过FrameLayout类来间接继承View类的,而且它的成员函数onLayout是从FrameLayout类继承下来的,所以,接下来咱们实际上要分析的是FrameLayout类的成员函数onLayout的实现。函数

       Step 5. FrameLayout.onLayout布局

publicclassFrameLayout extendsViewGroup {
......
protectedvoidonLayout(booleanchanged, intleft, inttop, intright, intbottom) {
finalintcount = getChildCount();
finalintparentLeft = mPaddingLeft + mForegroundPaddingLeft;
finalintparentRight = right - left - mPaddingRight - mForegroundPaddingRight;
finalintparentTop = mPaddingTop + mForegroundPaddingTop;
finalintparentBottom = bottom - top - mPaddingBottom - mForegroundPaddingBottom;
mForegroundBoundsChanged = true;
for(inti = 0; i < count; i++) {
finalView child = getChildAt(i);
if(child.getVisibility() != GONE) {
finalLayoutParams lp = (LayoutParams) child.getLayoutParams();
finalintwidth = child.getMeasuredWidth();
finalintheight = child.getMeasuredHeight();
intchildLeft = parentLeft;
intchildTop = parentTop;
finalintgravity = lp.gravity;
if(gravity != -1) {
finalinthorizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
finalintverticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch(horizontalGravity) {
caseGravity.LEFT:
childLeft = parentLeft + lp.leftMargin;
break;
caseGravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2+
lp.leftMargin - lp.rightMargin;
break;
caseGravity.RIGHT:
childLeft = parentRight - width - lp.rightMargin;
break;
default:
childLeft = parentLeft + lp.leftMargin;
}
switch(verticalGravity) {
caseGravity.TOP:
childTop = parentTop + lp.topMargin;
break;
caseGravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2+
lp.topMargin - lp.bottomMargin;
break;
caseGravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
......
}

       这个函数定义在文件frameworks/base/core/java/android/widget/FrameLayout.java中。post


       FrameLayout类的成员变量mPaddingLeft、mPaddingRight、mPaddingTop、mPaddingBottom和mForegroundPaddingLeft、mForegroundPaddingRight、mForegroundPaddingTop、mForegroundPaddingBottom的含义咱们在前面分析Android应用程序窗品的测量过程时已经解释过了,它们描述的是当前视图的内边距,而参数left、top、right和bottom描述的是当前视图的外边距,即它与父窗口的边距。经过上述这些参数,咱们就能够获得当前视图的子视图所能布局在的区域。学习

       FrameLayout类的成员函数onLayout经过一个for循环来布局当前视图的每个子视图。若是一个子视图child是可见的,那么FrameLayout类的成员函数onLayout就会根据当前视图能够用来显示子视图的区域以及它所设置的gravity属性来获得它在应用程序窗口中的左上角位置(childeLeft,childTop)。

       当一个子视图child在应用程序窗口中的左上角位置肯定了以后,再结合它在前面的测量过程当中所肯定的宽度width和高度height,咱们就能够彻底地肯定它在应用程序窗口中的布局了,便可以调用它的成员函数layout来设置它的位置和大小了,这恰好就是前面的Step 1所执行的操做。注意,若是当前正在布局的子视图child描述的也是一个视图容器,那么它又会重复执行Step 5的操做,直到它的全部子孙视图都布局完成为止。

      至此,咱们就分析完成Android应用程序窗口的布局过程了,接下来咱们继续分析Android应用程序窗口的绘制过程。

      ViewRoot类的成员函数draw首先会建立一块画布,接着再在画布上绘制Android应用程序窗口的UI,最后再将画布的内容交给SurfaceFlinger服务来渲染,这个过程如图4所示:

图4 Android应用程序窗口的绘制过程

       Step 1. ViewRoot.draw

publicfinalclassViewRoot extendsHandler implementsViewParent,
View.AttachInfo.Callbacks {
......
privatevoiddraw(booleanfullRedrawNeeded) {
Surface surface = mSurface;
......
intyoff;
finalbooleanscrolling = mScroller != null&& mScroller.computeScrollOffset();
if(scrolling) {
yoff = mScroller.getCurrY();
} else{
yoff = mScrollY;
}
if(mCurScrollY != yoff) {
mCurScrollY = yoff;
fullRedrawNeeded = true;
}
floatappScale = mAttachInfo.mApplicationScale;
booleanscalingRequired = mAttachInfo.mScalingRequired;
Rect dirty = mDirty;
......
if(mUseGL) {
if(!dirty.isEmpty()) {
Canvas canvas = mGlCanvas;
if(mGL != null&& canvas != null) {
......
intsaveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
try{
canvas.translate(0, -yoff);
if(mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired
? DisplayMetrics.DENSITY_DEVICE : 0);
mView.draw(canvas);
......
} finally{
canvas.restoreToCount(saveCount);
}
......
}
}
if(scrolling) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
return;
}
if(fullRedrawNeeded) {
......
dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
......
if(!dirty.isEmpty() || mIsAnimating) {
Canvas canvas;
try{
......
canvas = surface.lockCanvas(dirty);
......
} catch(Surface.OutOfResourcesException e) {
......
return;
} catch(IllegalArgumentException e) {
......
return;
}
try{
if(!dirty.isEmpty() || mIsAnimating) {
.....
mView.mPrivateFlags |= View.DRAWN;
......
intsaveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
try{
canvas.translate(0, -yoff);
if(mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired
? DisplayMetrics.DENSITY_DEVICE : 0);
mView.draw(canvas);
} finally{
......
canvas.restoreToCount(saveCount);
}
......
}
} finally{
surface.unlockCanvasAndPost(canvas);
}
}
......
if(scrolling) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
......
}

       这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。


       1. 将成员变量mSurface所描述的应用程序窗口的绘图表面保存在变量surface中,以便接下来能够经过变量surface来操做应用程序窗口的绘图表面。

       3. 成员变量mScrollY用来描述应用程序窗口下一次绘制时在Y轴上应该滚动到的位置,所以,若是应用程序窗口不是处于正在滚动的状态,那么它在下一次绘制时,就应该直接将它在Y轴上的即时滚动位置yoff设置为mScrollY。

       5. 成员变量mAttachInfo所描述的一个AttachInfo对象的成员变量mScalingRequired表示应用程序窗口是否正在请求进行大小缩放,若是是的话,那么所请求的大小缩放因子就保存在这个AttachInfo对象的另一个成员变量mApplicationScale中。函数将这两个值保存在变量scalingRequired和appScale中,以便接下来可使用。

       7. 成员变量mUseGL用来描述应用程序窗口是否直接使用OpenGL接口来绘制UI。当应用程序窗口的绘图表面的内存类型等于WindowManager.LayoutParams.MEMORY_TYPE_GPU时,那么就表示它须要使用OpenGL接口来绘制UI,以即可以利用GPU来绘制UI。当应用程序窗口须要直接使用OpenGL接口来绘制UI时,另一个成员变量mGlCanvas就表示应用程序窗口的绘图表面所使用的画布,这块画布一样是经过OpenGL接口来建立的。

      9. 使用OpenGL接口来绘制完成UI后,若是变量scrolling的值等于true,即应用程序窗口是处于正在滚动的状态,那么就意味着应用程序窗口接下来还须要立刻进行下一次重绘,并且是全部的区域都须要重绘,所以,函数接下来就会将成员变量mFullRedrawNeeded的值设置为true,而且调用另一个成员函数scheduleTraversals来请求执行下一次的重绘操做。

     11. 参数fullRedrawNeeded用来描述是否须要绘制应用程序窗口的全部区域。若是须要的话,那么就会将应用程序窗口的脏区域的大小设置为整个应用程序窗口的大小(0,0,mWidth,mHeight),其中,成员变量mWidth和mHeight表示应用程序窗口的宽度和高度。注意,若是应用程序窗口的大小被设置了一个缩放因子,即变量appScale的值不等于1,那么就须要将应用程序窗口的宽度mWidth和高度mHeight乘以这个缩放因子,而后才能够获得应用程序窗口的实际大小。

     13. 绘制完成以后,应用程序窗口的UI就都体如今前面所建立的画布canvas上了,所以,这时候就须要将它交给SurfaceFlinger服务来渲染,这是经过调用用来描述应用程序窗口的绘图表面的一个Surface对象surface的成员函数unlockCanvasAndPost来实现的。

      在本文中,咱们只关注使用非OpenGL接口来绘制应用程序窗口的UI的步骤,其中,第12步和第13步是关键所在。第12步调用了Java层的Surface类的成员函数lockCanvas来为应用程序窗口的绘图表面建立了一块画布,而且调用了DecorView类的成员函数draw来在这块画布上绘制了应用程序窗口的UI,而第13步调用了Java层的Surface类的成员函数unlockCanvasAndPost来将前面已经绘制了应用程序窗口UI的画布交给SurfaceFlinger服务来渲染。接下来,咱们就分别分析Java层的Surface类的成员函数lockCanvas、DecorView类的成员函数draw和Java层的Surface类的成员函数unlockCanvasAndPost的实现。

Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析一文,这里再也不详述。

      至此,咱们就分析完成Android应用程序窗口的渲染过程了,从中就能够看出:

      1. 渲染Android应用程序窗口UI须要通过三步曲:测量、布局、绘制。

      2. Android应用程序窗口UI首先是使用Skia图形库API来绘制在一块画布上,实际地是绘制在这块画布里面的一个图形缓冲区中,这个图形缓冲区最终会被交给SurfaceFlinger服务,而SurfaceFlinger服务再使用OpenGL图形库API来将这个图形缓冲区渲染到硬件帧缓冲区中。

      Android应用程序窗口的渲染过程分析完成以后,Android应用程序窗口的实现框架就分析完成了,从新学习请回到Android应用程序窗口(Activity)实现框架简要介绍和学习计划一文中。

      在Android应用程序窗口(Activity)实现框架简要介绍和学习计划这一系列文章中,咱们主要是从单个应用程序窗口的角度来分析的。可是,Android系统在运行的过程当中,须要管理的是一系列的应用程序窗口,而且这些应用程序窗口的类型可能各不相同,而且相互影响。所以,Android的窗口管理系统是很是复杂的。在接下来的一个系列的文章中,咱们就将详细地分析Android窗口管理服务WindowManagerService的实现,以即可以从系统的角度来分析应用程序窗口的实现。敬请关注!

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

相关文章
相关标签/搜索