目录
Window、WindowManager、WMS简单介绍
- Window
- 在Android视图体系中,Window就是一个窗口的概念.Android中所有的视图都是依赖于Window显示的.
- WindowManager
- 对Window进行管理,包括新增、更新和删除等.
- WMS
- 窗口的最终管理者,他负责窗口的启动、添加和删除,另外窗口的大小和层级也是由WMS进行管理的.
常见的Window有哪些?
- 系统错误窗口
- Dialog
- 应用程序窗口
- PopupWindow
- Toast
- 输入法窗口
Window分类
- Application Window
- Activity就是一个典型的应用程序窗口
- Sub Window
- 子窗口,顾名思义,他不能独立存在,需要附着于其他窗口才可以,PopupWindow就属于子窗口
- SystemWindow
- 输入法窗口、系统音量条窗口、系统错误窗口都属于系统窗口、Toast窗口、来电窗口
窗口的显示层级关系:
Application Window
-> Sub Window
-> System Window
系统窗口展示在最上面一层,子窗口展示在中间一层,而应用程序窗口展示在最下面一层.
很多窗口在WindowManager
类中都有定义一个int
值(比如:TYPE_TOAST
的值是2005
),这个int
值越大,这个窗口显示层级越靠上.
WindowManager对窗口的添加、更新
设计模式使用场景:
WindowManager
和WindowManagerImpl
之间使用到了桥接
设计模式.
RecyclerView
和LayoutManager
之间同样是用到了桥接
设计模式.
Activity
的生命周期中的attach
方法中用到了模版
设计模式.
Context
的与实现类之间用到了门面
设计模式.
xml
布局父控件与子控件之间中用到了组合
设计模式.
WindowManager
抽象类实现了ViewManager
接口,这个接口中包含了三个方法,addView()
、updateViewLayout()
和removeView()
,也就是说我们对窗口的新增、更新和删除,最终是对View
的新增、更新和删除.
- 添加Window
Activity.attach()
方法中包含了创建PhoneWindow
的代码,并且同时创建了WindowManagerImpl
负责维护PhoneWindow
中的内容Activity.onCreate()
方法中调用setContentView()
方法,这个方法内部创建了一个DecorView
实例作为PhoneWindow
的内容WindowManagerImpl
对DecorView
的管理,并且在WindowManagerGlobal
中创建一个个ViewRootImpl
实例,将ViewRootImpl
与View树
进行关联,这样ViewRootImpl
就可以指挥View树
的具体工作.
- 更新Window
WindowManagerImpl
中调用updateViewLayout
(decor, params)方法进行View
的更新,然后会依次调用如下方法:WindowManagerGlobal.updateViewLayout
(view, params);ViewRootImpl.setLayoutParams
(wparams, false);ViewRootImpl.scheduleTraversals();
- 这里是告知Window需要刷新,实际的刷新操作是在ViewRootImpl的performTraversals()
ViewRootImpl.performTraversals();
- 这里会执行View(UI)的刷新
重绘面试题
- 连续两次setTextView到底会触发几次UI重绘呢?
一次
因为UI必须至少等待16ms的间隔才会绘制下一帧,所以连续两次setTextView只会触发一次重绘
- 为什么Android App的帧率一般是60FPS?
帧率时60fps,也就是说1秒钟(s)会刷新60张画面,1000ms/60约等于16ms,也就是两张画面(两帧)之间的间隔时间大概是16ms.
UI刷新流程之setTextView
-> MainActivity.this.setText(“Hello”);//举例
-> TextView.setText(CharSequence text);
-> TextView.setText(CharSequence text, BufferType type);
-> TextView.setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)
-> TextView.checkForRelayout();
-> TextView.invalidate();
-> TextView.invalidate(boolean invalidateCache);
-> TextView.invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate);
-> ViewGroup.invalidateChild(this, damage);//TextView通知父容器ViewGroup需要刷新:
这里调用的接口ViewParent的invalidateChild方法,最终会交给实现类ViewGroup去刷新里面的孩子控件
-> ViewGroup.onDescendantInvalidated(child, child);
-> mParent.onDescendantInvalidated(this, target);//ViewGroup通过一层一层递归调用通知根Root需要刷新:
这里会一层一层获取ViewGroup的parent中这个方法,最终会调用到根Root也就是ViewRootImpl的onDescendantInvalidated()方法
-> ViewRootImpl.onDescendantInvalidated(child, child);
-> ViewRootImpl.invalidate();
-> ViewRootImpl.scheduleTraversals();
- -> Choreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//
发送垂直同步信号给显示屏系统
-> Choreographer.postCallbackDelayed(callbackType, action, token, 0);
-> Choreographer.postCallbackDelayedInternal(callbackType, action, token, delayMillis);
-> Choreographer.scheduleFrameLocked(now);
-> Choreographer.isRunningOnLooperThreadLocked();//请求Vsync垂直同步信号
- -> TraversalRunnable.run();//
接收到垂直同步信号,绘制UI
-> ViewRootImpl.doTraversal();
-> ViewRootImpl.performTraversals();- -> measureHierarchy()//
预布局
- -> relayoutWindow();//
通过IWindowSession将窗口布局到WMS上
mWindowSession.relayout()
- -> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//
测量
- -> performLayout(lp, mWidth, mHeight);//
布局
- -> performDraw();//
绘制
- -> measureHierarchy()//
UI局部重绘
某一个View
重新绘制,并不会导致所有的View
都进行一次measure
、layout
和draw
,只是这个待刷新View
链路需要调整,剩余的View
可能不需要浪费时间再来绘制一遍.
SurfaceFlinger
SurfaceFlinger
是整个Android
系统进行渲染的核心进程
.所有应用的渲染逻辑最终都会来到SurfaceFlinger
中进行处理,最终会把处理后的图像数据交给CPU或者GPU
进行绘制.
整体流程:
SurfaceFlinger
是以生产者以及消费者作为核心思想,把每一个进程作为生产者,生产的图元保存到SurfaceFlinger
图元队列中,SurfaceFlinger
作为消费者,依照一定的规则把生产者存放到SurfaceFlinger
中的队列中的图元进行处理.
Window相关面试题
- onResume生命周期中测量的宽高是否有效?
onResume这个生命周期方法其实会存在多次调用的情况,比如:当前App退到后台、或者从另外一个页面回来,都是会再次调用onResume方法的.
第一次调用onResume这个生命周期方法的时候是无法获取到测量的宽高的,因为View的绘制流程是在onResume生命周期方法的后面,在ActivityThread的handleResumeActivity()方法中,先执行的onResume生命周期相关逻辑,代码如下:
performResumeActivity(token, finalStateRequest, reason);
然后才会将DecorView添加到Window中,代码如下:
wm.addView(decor, l);
第二次调用onResume则能获取到测量的宽高,因为这个时候View已经绘制完成.
- Activity、Window、View三者的联系和区别?
三者是一个包含关系,Activity中包含了Window,而Window中包含了View.
Activity作为一个页面,里面是没有实际内容的,在Activity的attach方法中会创建我们的PhoneWindow窗体,需要通过PhoneWindow作为载体,在ActivityThread中将DecorView添加到PhoneWindow上面,就实现了将控件展示给用户的目的.
- 首次View的绘制流程是在什么时候触发的?
WindowManagerImpl.addView
->WindowManagerGlobal.addView
->ViewRootImpl.setView
->ViewRootImpl.requestLayout
->ViewRootImpl.scheduleTranversal
ViewRootImpl.java
中requestLayout()
方法源码分析:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检测创建ViewRootImpl的线程和刷新View的线程是否是同一个线程,不是则报错
checkThread();
mLayoutRequested = true;
//遍历所有View,绘制所有的View
scheduleTraversals();
}
}
- 我们调用invalidate()之后会马上进行屏幕刷新吗?
不会
需要等下一帧消息来的时候才会进行屏幕刷新,每一个画面是每隔一段时间一帧一帧绘制到屏幕上的.
- 我们说丢帧是因为主线程做了耗时操作,为什么主线程做了耗时操作就会引起丢帧?
主线程做耗时操作,影响到了绘制,虽然发送了屏障消息,防止后面的消息插入到消息队列;但是因为之前还有插入的消息存在消息队列中,仍然需要取出来刷新屏幕,所以主线程中做了耗时操作仍然会导致丢帧.