在Android系统中,Layout view window WindowManager Canvas Surface SurfaceFlinger这些组件协同工作,以实现图形的绘制和显示。需要搞明白这些组件是什么时候创建的以及他们之间的结构关系。
- 从上到下的层级关系:用户在View上进行操作,View通过Canvas进行绘制,绘制结果存储在Surface中。每个Window都可以关联一个或多个Surface。
- 合成和显示:SurfaceFlinger获取所有活跃的Surface,并将它们合成为最终的屏幕显示内容。
- 性能和异步处理:SurfaceView允许在后台线程中通过其Surface进行绘制,与主UI线程分离,这有助于处理复杂或资源密集型的图形操作,而不阻塞用户界面。
1 LayoutInflater
创建和获取
inflate方法用于将XML布局文件转换为应用程序中的View对象。这个过程是通过LayoutInflater类实现的,它是Android SDK的一部分,专门负责解析XML布局文件,并将其实例化为应用程序界面的层次结构。
setContentView最后会调用mLayoutInflater.inflate来创建了自定义xml中的布局视图,添加到mContentParent中。inflate方法是讲xml文件反射成一个View,但是并不执行View的绘制。
LayoutInflater是一个用于将定义在XML中的布局文件(如activity_main.xml)转化为View对象的类。每个由XML文件定义的布局在运行时都需要被“充气”成一个View树,这样用户才能与之交互。基本功能是读取XML布局文件,并将其转换成为相应的View对象。这个方法通常在Activity、Fragment或视图组件初始化时调用,以生成用户界面。
LayoutInflater提供了几个版本的inflate方法,其中最常用的包括:
View inflate(int resource, ViewGroup root)
View inflate(int resource, ViewGroup root, boolean attachToRoot)
- resource:这是一个指向布局文件的资源ID,例如R.layout.my_layout。
- root:这是新View应该附加到的父View。这个参数可以是null。
- attachToRoot:如果是true,则将加载的View添加到root作为子项;如果是false,则不添加,但仍然会使用root来正确处理布局参数。
inflate过程详解
-
解析XML:
- LayoutInflater读取指定的XML布局文件,解析其中定义的各种UI组件(如Button、TextView等)和它们的属性(如android:layout_width)。
-
创建View对象:
- 对于XML中的每一个组件,LayoutInflater会创建对应的View对象。例如,一个标签会被实例化为一个Button对象。
-
处理属性:
- 每个View对象会根据XML中定义的属性进行配置。这些属性包括尺寸、边距、对齐方式等。
-
构建视图树:
- 如果inflate调用中提供了root且attachToRoot为true,解析出的View会被添加到root中,形成一个完整的视图树。如果attachToRoot为false,则不添加,但root仍然用于生成正确的布局参数。
inflate使用场景
-
在Activity中:通常在onCreate()方法中使用inflate来加载布局。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} -
在Fragment中:在onCreateView()方法中使用inflate来为Fragment加载视图。
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_example, container, false);
}
通常我们使用inflate有以下三种方式:
LayoutInflater inflater = LayoutInflater.from(this);
View view1 = inflater.inflate(R.layout.view1, null);
View view2 = inflater.inflate(R.layout.view2, null, false);
View.inflate:只有一种形式如下:其实质是通过LayoutInflater来创建View的
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
Context.getSystemService:通过Context.getSystemService来获得LayoutInflater,实质也是通过LayoutInflater来创建View
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view1 = inflater.inflate(R.layout.view1, null);
2 View
创建和获取
创建: View 是 Android 中的基础绘图单元,通常在 XML 布局文件中定义或通过代码直接创建。每个 View 在被创建时会初始化其绘图属性(如大小、颜色等)。
获取: 通常,View 通过其父容器(如 Activity 或 Fragment 的布局)进行管理和访问。
View是Android中UI组件的基础,如按钮、文本框等。负责自己的绘制(通过onDraw方法)和事件处理。View组成ViewGroup(如LinearLayout)进而构建复杂的用户界面。
View机制
-
自定义绘制的基础: Android中的所有可视化组件(如按钮(Button)、文本框(TextView)、输入框(EditText)等)都是基于View类或其子类构建的。View是UI组件的基类,它封装了与用户交互、布局管理和绘制的基本功能。
-
Canvas绘图上下文: 每个需要在屏幕上展示的View,在其生命周期中的某个时刻,都会获得一个Canvas对象。这个Canvas就像是一个画布,提供了丰富的绘图方法,如画线、画圆、填充颜色、绘制文本等。通过Canvas,每个View能够实现自定义的图形绘制逻辑,从而展现出独特的视觉效果和交互行为。
-
绘制流程: 当UI需要更新或初次展示时,系统会要求相关的View执行其draw()方法。在draw()方法内部,View会使用分配给它的Canvas来执行绘制操作。这个过程包括但不限于背景绘制、内容绘制(比如文本或图像)以及绘制子视图(如果有的话)。通过重写onDraw()方法,开发者可以完全控制View的绘制逻辑,实现自定义的UI组件。
自定义View的意义
- 个性化UI设计: 开发者可以创造出与系统默认组件外观迥异的UI元素,满足特定应用的设计需求或品牌风格。
- 动态效果实现: 利用自定义绘制,可以轻松实现复杂的动画效果、图形变换等,增强用户体验。
- 高效性能控制: 在一些高性能要求的场景(如游戏、复杂动画)中,直接操作Canvas进行绘制能更精细地控制渲染流程,提高应用性能。
3 Window
创建和获取
创建: 在 Android 中,Window 是一个抽象概念,代表屏幕上的一部分区域,可以承载视图内容。每个 Activity 自动关联一个 Window,通常是通过 PhoneWindow 实现的。
获取: 可以通过 Activity 的 getWindow() 方法获取到当前活动的 Window。
机制
Android手机中所有的视图都是通过Window来呈现的,像常用的Activity,Dialog,PopupWindow,Toast,他们的视图都是附加在Window上的。Window是一个更高级的UI概念,代表Android中的一个全屏窗口,比如一个活动(Activity)。它不直接参与内容的绘制,而是为放置视图提供一个容器或框架。
每一个Activity都包含一个Window对象(dialog,toast 等也是新添加的window对象),而Window是一个抽象类,具体实现是PhoneWindow。在Activity中的setContentView实际上是调用PhoneWindow的setContentView方法。并且PhoneWindow中包含着成员变量DecorView。
在Android中,Window不是一个具体的类,而是通过WindowManager服务管理的一个概念性框架。它代表了一个可以包含视图和布局的容器,这个容器可以完整地填充设备的屏幕或者只是屏幕的一部分(如对话框)。Window作为最顶层的UI容器,主要负责承载应用的视图层次结构。每个Android应用至少有一个Window(通常是主Activity的窗口),复杂应用可能有多个Window(如弹出的对话框,菜单等)。
每个Activity通常都会有一个与之关联的Window,这个Window负责展示Activity的内容。当你创建一个Activity时,系统会为其提供一个Window,这个Window实际上是由Activity的setContentView()方法填充的。
站在系统的角度上看,系统是不知道有View对象这个说法的!作为系统,不去管你Window如何搬砖、如何砌墙,只给你地皮。而这时,Window为了绘制出用户想要的组件(按钮、文字、输入框等等),于是就定义了View机制,给每个View提供Canvas,让不同的View自己绘制具有自己特色的组件。同时,为了更好的管理View,通过定义ViewGroup等等
在Activity的attach()方法里,系统会创建Activity所属的Window对象并为其设置回调接口,由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法。Callback接口中的方法很多。Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法
public interface Callback {
public boolean dispatchTouchEvent(MotionEvent event);
public View onCreatePanelView(int featureId);
public boolean onMenuItemSelected(int featureId, MenuItem item);
public void onContentChanged();
public void onWindowFocusChanged(boolean hasFocus);
public void onAttachedToWindow();
public void onDetachedFromWindow();
}
4 WindowManager
创建和获取
创建: WindowManager 是一个系统服务,负责管理应用窗口的创建、销毁、更新等。它是在系统启动时由 SystemService 创建的。
获取: WindowManager 可以通过 Context.getSystemService(Context.WINDOW_SERVICE) 获得。
机制
WindowManager是Android系统中管理窗口的服务,负责管理所有的Window,包括它们的创建、销毁、大小调整和Z顺序(即窗口堆叠的顺序)。WindowManager通过WindowManager.LayoutParams类提供对窗口特性的详细控制,如其尺寸、透明度、位置等。在LayoutParams中,有2个比较重要的参数: flags,type。
PhoneWindow 只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View,作为窗口添加到 WmS 的过程是由 WindowManager 来完成的。WindowManager 的具体实现是 WindowManagerImpl。这个WindowManagerImpl都是交由WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供自己的实例。这种工作模式是桥接模式,将所有的操作全部委托给WindowManagerGlobal来实现。
添加窗口是通过WindowManagerGlobal的addView方法操作的,这里有三个必要参数。view,params,display。
display : 表示要输出的显示设备。
view : 表示要显示的View,一般是对该view的上下文进行操作。(view.getContext())
params : 类型为WindowManager.LayoutParams,即表示该View要展示在窗口上的布局参数。其中有一个重要的参数type,用来表示窗口的类型。
在WindowManagerGlobal的addView()方法里,最后调用ViewRootImpl的setView方法,处理添加过程。
在ViewRootImpl的setView()方法里,执行requestLayout()方法完成View的绘制流程,并且通过WindowSession将View和InputChannel添加到WmS中,从而将View添加到Window上并且接收触摸事件。
当手动调用 invalidate(),postInvalidate(),requestInvalidate() 也会最终调用performTraversals(),来重新绘制 View。
在setView方法中,首先会调用到 requestLayout(),表示添加 Window 之前先完成第一次 layout 布局过程,以确保在收到任何系统事件后面重新布局。ViewRootImpl 调用到 requestLayout() 来完成 View 的绘制操作,view 的绘制首先会调用 checkThread() 来判断当前线程。通过requestLayout()向主线程发送了一条触发遍历操作的消息,performTraversals方法开始遍历整个View树,执行View的measure,layout,draw流程。ViewRootImpl中接收的各种变化,如来自WmS的窗口属性变化、来自控件树的尺寸变化及重绘请求等都引发performTraversals()的调用,并在其中完成处理。View类及其子类中的onMeasure()、onLayout()、onDraw()等回调也都是在performTraversals()的执行过程中直接或间接的引发。
performMeasure : 会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传到子元素中了,这样就完成了一次measure过程。measure完成以后,可以通过getMeasuredWidth和getMeasureHeight方法来获取到View测量后的宽高。
performLayout : 和performMeasure同理。Layout过程决定了View的四个顶点的坐标和实际View的宽高,完成以后,可以通过getTop/Bottom/Left/Right拿到View的四个顶点位置,并可以通过getWidth和getHeight方法来拿到View的最终宽高。
performDraw : 和performMeasure同理,唯一不同的是,performDraw的传递过程是在draw方法中通过dispatchDraw来实现的。Draw过程则决定了View的显示,只有draw方法完成以后View的内容才能呈现在屏幕上。
它只提供三个接口方法:addView、updateViewLayout、removeView,这些方法都是针对View的。
5 Surface
创建和获取
当一个应用程序窗口被创建时,系统会为该窗口创建一个Surface。这个Surface是由窗口管理器(Window Manager)和表面合成器(SurfaceFlinger)共同管理的。
在应用程序的ViewRootImpl类中,有一个名为mAttachInfo的内部类,它持有当前窗口的Surface引用。这个引用是在窗口创建时由系统设置的,并在窗口的生命周期中保持有效。
Surface 表示一个具体的可绘制区域,通常是由 SurfaceView 或者窗口系统(如 Window)来创建和管理。Surface 与底层的图形缓冲区关联,用于存储实际的像素数据。
可以通过 SurfaceView.getHolder().getSurface() 获得,或者从 Window 的某些方法中间接获取。
Surface 的作用:
在draw阶段,每个视图通过调用其draw方法将自身绘制到一个Canvas对象上。这个Canvas实际上是通过Surface的lockCanvas方法获取的,它代表了Surface的一块画布。
视图的绘制操作(如画线、画圆、贴图等)都是在这个Canvas上执行的。完成绘制后,通过Surface的unlockCanvasAndPost方法将结果显示到屏幕上。
表示一个具体的绘图表面,这个表面可以存储绘制好的图像数据。在更底层,与一个被称为SurfaceHolder的缓冲区相关联,这使得SurfaceView一个特殊的View可以在一个独立的线程中更新其内容,而不影响主UI线程的性能。
在Android中,Window与Surface一一对应。 如果说Window关心的是层次和布局,是从设计者角度定义的类,Surface则从实现角度出发,是工程师关心和考虑的类。Window的内容是变化 的,Surface需要有空间来记录每个时刻Window的内容。在Android的SurfaceFlinger实现里,通常一个Surface有两块 Buffer, 一块用于绘画,一块用于显示,两个Buffer按照固定的频率进行交换,从而实现Window的动态刷新。
每个Window在Android中都关联着一个Surface,这个Surface是应用程序用来绘制其用户界面(UI)的地方。当应用程序需要更新其显示内容时,它会在对应的Surface上绘制新的UI元素,如按钮、文本或者图像。这一过程可以发生在主线程中,也可以在专门的渲染线程中,特别是对于需要复杂动画或视频播放的场景。
Surface的工作流程通常包括以下几个关键步骤:
-
创建:Surface在窗口创建时被创建,为应用程序提供一块可供绘制的“空画布”。
-
锁定和绘制:应用通过锁定Surface,获得一个Canvas对象,然后在这个Canvas上进行绘制操作。
-
提交:绘制完成后,Surface会被解锁并提交,这意味着绘制的内容已经准备好用于显示。
-
合成与显示:SurfaceFlinger系统服务负责从不同的Surface中读取已提交的缓冲区数据,进行合成(如果存在多个叠加的窗口),然后输出到显示屏上。
SurfaceView是一种特殊的View,它有自己的Surface,允许在单独的线程中进行高效的后台绘制,这对于视频播放或游戏等高性能图形应用特别有利,因为它减少了主线程的负担,并且支持在不阻塞UI更新的情况下进行快速连续的图像更新。
6 Canvas
创建和获取
Canvas是一个提供绘图功能的类,它定义了绘制文本、线条、图形、图片等的方法。在View的onDraw方法中,Canvas对象被传递进来,开发者通过这个Canvas来绘制自定义的UI内容。
performTraversals方法中ViewRootImpl就会去创建Surface,而此后的渲染则可以通过Surface的lockCanvas方法获取Surface的Canvas来进行,然后遍历ViewHierachy把需要绘制的View通过Canvas(View.onDraw(Canvas canvas))绘制到Surface上,绘制完成后解锁(Surface.unlockCanvasAndPost)让SurfaceFlinger将Surface绘制到屏幕上。我们onDraw(Canvas canvas)方法中传入的Canvas对象大致就是这么来的……Surface对应了一块屏幕缓冲区,每个window对应一个Surface,任何View都是画在Surface上的,传统的view共享一块屏幕缓冲区,所有的绘制必须在UI线程中进行.但是viewgroup在依次绘制子view的时候,都会先对canvas进行save操作,绘制完之后restore,所以其实虽然绘制都共用同一个canvas,但是子view之间的绘制是互不影响的
机制
一个Canvas对象有四大基本要素:
1、一个用来保存像素的Bitmap
2、一个Canvas在Bitmap上进行绘制操作
3、绘制的东西
4、绘制的画笔Paint
Canvas类提供了一系列的draw…方法
填充 public void drawARGB(int a, int r, int g, int b)
绘制几何图像 canvas.drawArc (扇形)
绘制图片 canvas.drawBitmap (位图)
7 SurfaceFlinger
创建和获取
创建: SurfaceFlinger 是系统级的服务,由系统在启动时初始化。它负责管理所有应用和系统的 Surface,并进行屏幕内容的合成。
获取: 作为系统服务,开发者通常不直接与 SurfaceFlinger 交互;它在后台运行,通过系统调用间接与之交互。
机制
SurfaceFlinger是系统级别的服务,用于管理所有的Surface。它负责将来自不同应用和系统界面的多个Surface合成到一个帧缓冲区中,并输出到设备的显示屏上。通过硬件加速和优化算法来有效地进行这种图像合成,以实现高性能的图形显示。
SurfaceFlinger是一个独立的Service,它接收所有Window的Surface作为输入,根据ZOrder, 透明度,大小,位置等参数,计算出每个Surface在最终合成图像中的位置,然后交由HWComposer或OpenGL生成最终的显示Buffer, 然后显示到特定的显示设备上。
Layer——Layer是SurfaceFlinger 进行合成的基本操作单元。Layer在应用请求创建Surface的时候在SurfaceFlinger内部创建,因此一个Surface对应一个 Layer, 但注意,Surface不一定对应于Window,Android中有些Surface并不跟某个Window相关,而是有程序直接创建,比如说 SurfaceView, 用于显示有硬件输出的视频内容等。当多个Layer进行合成的时候,并不是整个Layer的空间都会被完全显示,根据这个Layer最终的显示效果,一个Layer可以被划分成很多的Region
应用程序的每个Surface都对应一个SurfaceFlinger端的Layer,每个Layer都有两个buffer可以用,一个前端buffer,一个后端buffer,前端buffer用于显示,后端buffer用于绘制,SurfaceFlinger所要做的事情就是把后端buffer绘制完成后需要显示的buffer进行合成,合成到framebuffer上,然后丢给Display去显示。
双缓冲的主要目的是减少画面撕裂和避免渲染过程中的视觉闪烁。通过在后台缓冲区进行绘制,然后一次性将完成的画面切换到前台,可以提供更平滑和更稳定的视觉效果。
- 当SurfaceFlinger作为消费者时,它会取用Surface的frontBuffer中的图像,将其与系统中其他的Surface图像合成,然后输出到屏幕。
- 当MediaCodec作为消费者时,frontBuffer中的内容被用于视频编码过程,编码后的数据通常用于存储或网络传输。
工作流程:
- Lock:通过lockCanvas()方法获取对backBuffer的访问,这允许在后台缓冲区上进行绘制。这个过程中,应用程序可以在这个画布上绘制图像、文本、动画等内容。
- Draw:在backBuffer上完成所有绘制操作。由于这些操作是在后台执行的,它们不会影响当前用户看到的屏幕内容。
- Unlock and Post:完成绘制后,调用unlockCanvasAndPost(Canvas canvas),这个操作不仅释放画布资源,同时触发缓冲区的交换。此时,backBuffer(新绘制的内容)和frontBuffer(当前显示的内容)的身份互换,新的内容变为可显示的,而旧的前台缓冲区则转为后台,准备下一轮绘制。
Surface 和 SurfaceFlinger 的基本关系
-
Surface: 在Android中,
Surface
代表一块可显示内容的区域。它是一个虚拟的显示层,可以接收绘制命令(通过Canvas),并保存绘制的内容。 -
SurfaceFlinger:
SurfaceFlinger
是Android系统的底层服务,负责接收各个应用和系统服务的图像数据(即Surface
数据),并将这些数据合成到屏幕上。SurfaceFlinger
工作在系统级别,管理着所有的Surface
,并负责最终的屏幕渲染。
Surface 到 SurfaceFlinger 的数据流动
-
应用程序通过
Canvas
对象在Surface
上进行绘制。这些操作实质上是在修改与Surface
关联的硬件缓冲区的内容。 -
绘制完成后,通过
Surface
的方法(如unlockCanvasAndPost(Canvas canvas)
)将修改后的缓冲区标记为待显 -
Surface
内部实现了与SurfaceFlinger
的通信机制。每个Surface
都是通过Binder IPC机制注册到SurfaceFlinger
服务的。 -
当
Surface
内容更新时,Surface
会通知SurfaceFlinger
其内容已更新。这通常通过发送一个同步信号(例如,一个时间戳或帧标识)来实现。 -
SurfaceFlinger
接收到更新通知后,会将新的Surface
内容拉取到其合成流程中。SurfaceFlinger
根据所有可用的Surface
图层和它们的Z顺序进行图像合成,然后输出到显示硬件。 -
SurfaceFlinger
合成的结果会发送到显示硬件(如LCD屏幕)。这一步通常涉及到VSYNC信号,确保屏幕刷新与内容更新同步,从而减少图像撕裂或闪烁现象。
surfaceflinger的工作流程
当Surface绘制完成后会发出一个Invalidate的消息给Surfaceflinger的等待线程,当waitForEvent接收到消息后就会交给onMessageReceivered去处理,处理过程中会依次调用handleMessageTransaction、handleMessageInvalidate、handleMessageRefresh接口。
handleMessageTransaction——主要处理之前对屏幕和应用程序窗口的改动。窗口状态的改变只能在一个Transaction中进行。因为窗口状态的改变可能造成本窗口和其他窗口的可见区域变化,所以就必须重新来计算窗口的可见区域。在这个处理子过程中Android会根据标志位来对所有layer进行遍历,一旦发现哪个窗口的状态发生了变化就设置标志位以在将来重新计算这个窗口的可见区域。
handleMessageInvalidate——主要调用handlePageFlip()函数,该函数主要是从各Layer对应的BufferQueue中拿图形缓冲区数据,并根据内容更新脏区域。
handleMessageRefresh——就是合并和渲染输出了。