1.Android UI架构
对于我们一个Activity,其对应的UI架构如下:
而UI类图关系是:
2.应用程序窗口的视图对象及其所关联的ViewRoot对象是什么时候开始创建的呢?
Activity组件在启动的时候,系统会为它创建窗口对象(Window),同时,系统也会为这个窗口对象创建视图对象。另一方面,当Activity组件被激活的时候,系统如果发现与它的应用程序窗口视图对象所关联的ViewRoot对象还没有创建,那么就会先创建这个ViewRoot对象,以便接下来可以将它的UI渲染出来。
上述操作执行完成之后,ViewRoot类的成员函数setView就可以将成员变量mAdded的值设置为true了,表示当前正在处理的一个ViewRoot对象已经关联好一个View对象了。接下来,ViewRoot类的成员函数setView还需要执行两个操作:
1.调用ViewRoot类的另外一个成员函数requestLayout来请求对应用程序窗口视图的UI作第一次布局。
2.调用ViewRoot类的静态成员变量sWindowSession所描述的一个类型为Session的Binder代理对象的成员函数add来请求WindowManagerService增加一个WindowState对象,以便可以用来描述当前正在处理的一个ViewRoot所关联的一个应用程序窗口。
3.那么应用程序窗口的绘图表面又是什么时候创建的呢?
一般是在不存在的时候就创建,因为应用程序窗口在运行的过程中,它的绘图表面会根据需要来销毁以及重新创建的,例如,应用程序窗口在第一次显示的时候,就会请求WindowManagerService服务为其创建绘制表面。当一个应用程序窗口被激活并且它的视图对象创建完成之后,应用程序进程就会调用与其所关联的一个ViewRoot对象的成员函数requestLayout来请求对其UI进行布局以及显示。由于这时候应用程序窗口的绘图表面尚未创建,因此,ViewRoot类的成员函数requestLayout就会请求WindowManagerService服务来创建绘图表面。
注意:在relayout的过程中才真正去NewSurface,创建真正的有效Surface对象,并通知WMS重新做relayout,整个过程完了之后,就可以请求SurfaceFlinger服务将它们渲染到硬件帧缓冲区中去,这样我们就可以看到应用程序窗口的UI了。
简单来说,WindowManagerService类的成员函数relayoutWindow根据应用程序进程传递过来的一系列数据来重新设置由参数client所描述的一个应用程序窗口的大小和可见性等信息,而当一个应用程序窗口的大小或者可见性发生变化之后,系统中当前获得焦点的窗口,以及输入法窗口和壁纸窗口等都可能会发生变化,而且也会对其它窗口产生影响,因此,这时候WindowManagerService类的成员函数relayoutWindow就会对系统中的窗口的布局进行重新调整。对系统中的窗口的布局进行重新调整的过程是整个WindowManagerService服务最为复杂和核心的内容。
4.Android
UI的渲染过程是怎么的?
Android应用程序窗口一般不会直接去操作分配给它的图形缓冲区,而是通过一些图形库API来操作。对于使用Java来开发的Android应用程序来说,它们一般是使用Skia图形库提供的API来绘制UI的。在Skia图库中,所有的UI都是绘制在画布(Canvas)上的,因此,Android应用程序窗口需要将它的图形缓冲区封装在一块画布里面,然后才可以使用Skia库提供的API来绘制UI。
我们知道,一个Android应用程序窗口里面包含了很多UI元素,这些UI元素是以树形结构来组织的,即它们存在着父子关系,其中,子UI元素位于父UI元素里面,因此,在绘制一个Android应用程序窗口的UI之前,我们首先要确定它里面的各个子UI元素在父UI元素里面的大小以及位置。确定各个子UI元素在父UI元素里面的大小以及位置的过程又称为测量过程和布局过程。因此,Android应用程序窗口的UI渲染过程可以分为测量、布局和绘制三个阶段,如图1所示:
measure:递归测量所有视图的大小
layout:整个layout过程比较容易理解,从顶层父View向子View的递归调用view.layout方法的过程,即父View根据布局大小和布局参数,将子View放在合适的位置上。
Draw:绘制过程就是把View对象绘制到屏幕上,如果该View是一个ViewGroup,则需要递归绘制其所包含的所有子View;View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。Android应用程序窗口UI首先是使用Skia图形库API来绘制在一块画布上,实际地是绘制在这块画布里面的一个图形缓冲区中,这个图形缓冲区最终会被交给SurfaceFlinger服务,而SurfaceFlinger服务再使用OpenGL图形库API来将这个图形缓冲区渲染到硬件帧缓冲区中。
注意:成员变量mSurface所指向的Surface对象在创建的时候,还没有在C++层有一个关联的Surface对象,因此,这时候它描述的就是一个无效的绘图表面。这个时候还没有创建真正的Surface对象,Surface对象是在后面的relayoutWindow里面创建的。
5.如何避免View的invalidate()导致重复刷新问题?
注意:View的invalidate(invalidateInternal)方法实质是将要刷新区域直接传递给了父ViewGroup的invalidateChild方法,在invalidate中,调用父View的invalidateChild,这是一个从当前向上级父View回溯的过程,每一层的父View都将自己的显示区域与传入的刷新Rect做交集。
invalidate系列方法请求重绘View树(也就是draw方法),如果View大小没有发生变化就不会调用layout过程,并且只绘制那些“需要重绘的”View,也就是哪个View(View只绘制该View,ViewGroup绘制整个ViewGroup)请求invalidate系列方法,就绘制该View。
常见的引起invalidate方法操作的原因主要有:
1、直接调用invalidate方法.请求重新draw,但只会绘制调用者本身。
2、触发setSelection方法。请求重新draw,但只会绘制调用者本身。
3、触发setVisibility方法。当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。
4、触发setEnabled方法。请求重新draw,但不会重新绘制任何View包括该调用者本身。
5、触发requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。