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

简介:

  ViewRoot类的成员函数invalidateChild首先调用另外一个成员函数checkThread来检查当前正在执行的是否是一个UI线程。如果不是的话,ViewRoot类的成员函数checkThread就会抛出一个异常出来。这是因为所有的UI操作都必须要在UI线程中执行。

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

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

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

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

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

       事实上,DecorView类是通过FrameLayout类来间接继承View类的,并且它的成员函数onLayout是从FrameLayout类继承下来的,因此,接下来我们实际上要分析的是FrameLayout类的成员函数onLayout的实现。

       Step 5. FrameLayout.onLayout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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中。


       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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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的实现,以便可以从系统的角度来分析应用程序窗口的实现。敬请关注!





本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/1242901,如需转载请自行联系原作者
目录
相关文章
|
4月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
490 4
|
4月前
|
安全 Android开发 数据安全/隐私保护
深入探讨iOS与Android系统安全性对比分析
在移动操作系统领域,iOS和Android无疑是两大巨头。本文从技术角度出发,对这两个系统的架构、安全机制以及用户隐私保护等方面进行了详细的比较分析。通过深入探讨,我们旨在揭示两个系统在安全性方面的差异,并为用户提供一些实用的安全建议。
|
4月前
|
安全 Android开发 数据安全/隐私保护
深入探索Android与iOS系统安全性的对比分析
在当今数字化时代,移动操作系统的安全已成为用户和开发者共同关注的重点。本文旨在通过比较Android与iOS两大主流操作系统在安全性方面的差异,揭示两者在设计理念、权限管理、应用审核机制等方面的不同之处。我们将探讨这些差异如何影响用户的安全体验以及可能带来的风险。
154 21
|
5月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
155 15
Android 系统缓存扫描与清理方法分析
|
3月前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文深入探讨了这两个平台的开发环境,从编程语言、开发工具到用户界面设计等多个角度进行比较。通过实际案例分析和代码示例,我们旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和个人偏好做出明智的选择。无论你是初涉移动开发领域的新手,还是寻求跨平台解决方案的资深开发者,这篇文章都将为你提供宝贵的信息和启示。
62 8
|
5月前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
135 6
|
5月前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
Android面试高频知识点(4) 详解Activity的启动流程
49 3
|
5月前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
42 0
|
5天前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
53 19

热门文章

最新文章