任何一个操作系统实现界面绘制,都需要处理应用层、系统层和硬件层的分工协作:
- 应用层负责定义画面的内容
- 系统层负责综合整个屏幕的画面并保证流畅
- 硬件层负责把数据输出到显示设备上
应用层
除了系统窗口(Toast),主要在Activity中绘制界面,需要解决两个问题
- 定义显示内容。基本原理就是在Canvas上绘制界面,然后调用surfaceholder.unlockCanvasAndPost函数,渲染到Surface中(视频是解码出视频帧,渲染到Surface上),Surface实际处于系统层,通过Ashmem共享内存传给Activity使用。
- 定义显示位置、层次和生命周期。基本原理就是Activity的PhoneWindow利用Bindler机制和系统层通信,交给系统层去统一管理,如addView、removeView等都是通过WMS去做的。
系统Framework层
对于应用开发来说,最重要的是系统层中的Framework层,主要包括WMS和SurfaceFlinger两个系统服务,都运行在SystemServer进程中:
-
WindowManagerService,负责两件事:Window的层级,Window的管理。
层级上,WMS把所有界面分为应用window,子window和系统window三种,分别有自己的层级范围(1 - ~,1000 - ~, 2000 - ~).
管理上,WMS要负责添加和删除window,管理这些window的的位置、大小和生命周期变化。
另外,WMS在调整window的时候,哟啊通知SurfaceFlinger去更新界面,由此用户才能看到调整后的效果。 -
SurfaceFlinger,负责两件事:为应用提供Surface,整合图形数据:
- Activity获取Surface时,是WMS代为向SurfaceFlinger做的请求
- 根据WMS的窗口层级,把相关的Surface整合起来,并放到BufferQueue里,供底层绘制界面,实际上起到了生产者的作用。
系统HAL层,系统Linux Kernel层和硬件层
把系统层的这两部分和硬件层放在一起说,是因为他们联系更紧密,更偏底层,平时做应用开发时也基本不涉及到。
HAL层:是个抽象接口,处理界面的是Gralloc接口,HAL是为了解决linux硬件驱动的版权问题(Android开源,但是有些厂商的硬件驱动不开源,用HAL可以规避这些问题)。
Linux Kernel层:Linux 内核使用帧缓冲FrameBuffer来实现显示功能,作为内存缓冲区(有32个Slot),既是操作硬件设备的接口,又可以缓解画面流畅和完整性的问题(队列、生产和消费)。
硬件层:利用驱动把数据输出到显示设备上。
Activity与Framework层的合作
Activity需要与Framework层的SurfaceFlinger和WMS合作,才能实现界面绘制,这个合作机制需要解决这样几个问题:
- Window如何创建与使用
- Surface如何获取与使用
- View如何创建与绘制
Window的创建与使用
Window的管理核心在WMS,所以Window的创建和使用都需要与WMS建立通信,并交给WMS管理和调度。
- 创建:Activity启动时创建PhoneWindow,并与WMS建立通信,以便统一管理。
- 使用: 在ViewRootImpl中调用WMS的addToDisplay,实现添加窗口。
具体流程如下:
- 主线程ActivityThread启动Activity时,调用的performLaunchActivity会执行activity的attach函数关联context,application等,这时就会创建PhoneWindow,并把PhoneWindow和WMS关联,还会给WMS提供反向访问的Bindler参数mToken。
- PhoneWindow持有一个WindowManagerImpl实例,WindowManagerImpl中有一个WindowManagerGolbal的静态变量,是一个单例。
- 在WindowManagerGlobal中执行addView的操作(ActivityThread的handleResumeActivity方法中会调用WindowManagerGlobal的addView方法,将DecorView传入,并在方法内部实例化一个ViewRootImpl实例,将其作为DecorView的ParentView(ViewTree的绘制都是从ViewRootImpl开始的),ViewRootImpl的setView方法中,会调用WMS的addToDisplay方法,实现添加这个窗口)。
所以,App内部的所有窗口由WindowManagerGlobal统一管理,而android系统的所有窗口由WMS统一管理。
Surface的获取与使用
Surface是ViewRootImpl通过Bindler机制从SurfaceFlinger中通过Ashmem共享内存获取到的。
实际上,ViewRootImpl持有一个Surface对象,所以问题在于,ViewRootImpl中如何为Surface关联到了SurfaceFlinger中的对象。
具体流程如下:
- 在ViewRootImpl中不管是setView方法还是requestLayout方法,最后都会调用scheduleTraversals方法,并在TraversalRunnable中执行performTraversals方法(这里就是ViewTree绘制的起点);在performTraversals方法中首先会调用relayoutWindow方法。relayoutWindow方法内部会调用mWindowSession.relayout,进行Binder通信。
- WMS会先创建一个SurfaceControl,然后利用copyFrom获取其中的Surface。这里会执行native函数nativeCreateFromSurfaceControl。最后,native层会通过SurfaceComposerClient去访问SurfaceFlinger,SurfaceFlinger从BufferQueue中dequeueBuffer,最终返回Surface。
View的创建与绘制前获取Canvas
我们定义的ViewTree其实是DecorView中R.id.content那一部分,所以View的创建与绘制,核心在于建立与Window的关联,并能访问Surface。
具体流程如下:
- View是从Activity(实际是PhoneWindow)的setContentView创建的(xml资源转换为ViewTree)
- 然后DecorView和PhoneWindow相互关联。并且DecorView关联了ViewRootImpl对象,ViewRootImpl会帮忙DecorView完成绘制的工作。
总结
除了Activity之外,最重要的就是ViewRootImpl、PhoneWindow和WindowManagerGlobal。
- ViewRootImpl是绘制的起点(控制DectorView的绘制),也是绘制的目标(mSurface),每次WindowManagerGlobal中addView,都会生成并保存一个ViewRootImpl对象;最终的绘制canvas,也是渲染到ViewRootImpl持有的mSurface中去。
- PhoneWindow夹在Activity和DecorView之间,主要起到解耦和减负的作用,可以把Activity与View的管理/window的管理切割开。
例如,添加View实际上是交给WindowManager去addView/removeView/updateView,但是Activity不需要直接与WindowManager交互,而是让PhoneWindow去setContentView,PhoneWindow再去调用WindowManager的addView操作。 - WindowManager是个抽象类,只有WindowManagerImpl一个实现,而WindowManagerImpl实际上。
ViewRootImpl被WindowManagerGlobal严密地管理了起来,WMS管理window时,也是通过操纵ViewRootImpl实现的,所以都说ViewRootImpl是WindowManager和DecorView的连接纽带。
扩展
场景1
自定义系统开机画面,虽然没有启动Android,但是可以操作硬件驱动、或操作linux的framebuffer实现界面绘制,这也是各厂商自己定制系统时的修改方法。
场景2
侧滑App,为什么可以向一侧滑动整个App界面,考虑到App实际上是向DecorView添加了ViewTree,这就可以在DecorView和ViewGroup中间插一层透明的View,这样就能滑动原有的ViewTree,达到侧滑效果。
public class SwapeBackLayout extends FrameLayout{
...
//用当前ViewGroup代替ViewTree的根节点
ViewGroup decorView= (ViewGroup) activity.getWindow().getDecorView();
View child=decorView.getChildAt(0);
decorView.removeView(child);
addView(child);
decorView.addView(this);
...
}
场景3
我们知道事件分发是从Activity开始的,但是悬浮窗也可以设置为允许响应事件,但是悬浮窗是没有Activity的,只做了addView,那么悬浮窗的事件是如何响应的?
硬件层拦截到事件,会从WMS传递到ViewRootImpl,而ViewRootImpl是WindowManagerGlobal在addView时创建的,所以悬浮窗虽然只做了addView,也有ViewRootImpl,也能响应事件。
有Activity的情况下,ViewRootImpl的mView是DecorView,所以会把事件传递给DecorView,DecorView处理事件的函数为:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
其中,mWindow是持有DecorView的PhoneWindow对象,而这个PhoneWindow对象的Callback,是在Activity的attach中,创建出PhoneWindow后,把Activity作为了Callback。
//Activity源码
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
...
mWindow = new PhoneWindow(this, window);
...
mWindow.setCallback(this);
所以,在有Activity的情况下,事件传递是ViewRootImpl-->DectorView-->PhoneWindow.Callback也就是Activity,然后再传递给PhoneWindow-->DectorView-->ViewTree,这也可以解释为什么DectorView里同时存在dispatchTouchEvent和superDispatchTouchEvent两个函数。
而在没有Activity的情况下,ViewRootImpl的mView是我们addView时传入的ViewTree,事件就直接传递给ViewTree了。
网友评论