0. 相关分享
Android从屏幕刷新到View的绘制(一)之 Window、WindowManager和WindowManagerService之间的关系
Android从屏幕刷新到View的绘制(二)之Choreographer、Vsync与屏幕刷新
1. 相关类
WindowManagerService,下文简称WMS
这是一个系统服务,由SystemServer启动,运行在一个Binder线程,管理着Android系统中所有的Window。
这有什么实际作用呢?除了刷新View,它还可以为其他服务提供Window管理的支持,例如当触摸屏幕时产生输入事件,InputManangerService可以通过WMS来拿到所有Window信息,找到合适的Window进行输入事件的派发,此后,Window就会把这个输入事件传递给顶级View,也就是DecorView,接着就进入到熟悉的事件分发机制了。
Window
表示一个窗口的抽象的概念,这是一个空实现的抽象类,在APP进程中,它有实现类PhoneWindow。
PhoneWindow
是Window的实现类,一个Activity对应着一个PhoneWindow,在PhoneWindow中有一个顶级View——DecorView
DecorView
Activity的根View,继承自FrameLayout
ViewRootImpl
负责DecorView下所有View的调度,例如invalidate()等,Activity下的所有View都会向上找到DecorView,最终找到ViewRootImpl来处理
WindowManagerImpl
它是WindowManager接口的实现类,WindowManager接口又继承自ViewManager,顾名思义它是管理View和Window关系的。ViewManager中规范了三个方法:addView(), updateViewLayout(), removeView()。这三个任务最后也交到了WindowManagerGlobal来处理
WindowManagerGlobal
它是一个单例设计,一个APP进程对应一个WindowManagerGlobal,持有WMS的binder引用,可以通过它来与WMS进行IPC(跨进程通信)交互。
它还拥有许多集合,例如mViews包含了进程下所有View,mRoots包含了进程下所有ViewRootImpl,mDyingViews包含了进程下所有要销毁的View。
2. 上述类间的关系图
Window是View的载体,我们想要对Window进行添加、删除、更新View,就要通过WindowManager,实际管理着是WindowManagerGlobal,它与WMS通过Session进行IPC通信,具体的实现交给了WMS处理。
WMS也会为每个WIndow创建一个WindowState来管理它们,具体的渲染工作交给了SurfaceFinger处理。本文只讨论View、Window、WindowManager与WMS的关系。
3. Window对View的管理
Window是抽象的概念,它的实现类为PhoneWindow,内部维护着一个DecorView,换句话说,WIndow是以View的形式呈现给用户的。Window对View的操作,实际是通过ViewRootImpl实现。使用过程中,我们不会接触并访问到Window,而是通过WindowMananger来进行操作。
接下来说的Window对View的管理其实具体来说是对DecorView的管理,一个Window对应一个DecorView。其中DecorView的创建、删除,就相当于Window的添加、删除,所以也有的地方说,这部分的讨论叫做window的创建、更新、删除。
3.1 Window对View的添加
WindowManager的实现类是WindowManagerImpl
//WindowManagerImpl
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
WindowManagerImpl将对View的添加、删除、更新都交给了WIndowManagerGlobal,mGlobal是一个单例:
//WindowManagerGlobal
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
首先我们看到WindowManagerGlobal的addView(),具体步骤大概如下:
- 各类数据检查
- 更新mViews、mRoots等集合
- 创建一个ViewRootImpl,将要添加的view交给它
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//1.数据检查
//...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
//2. 更新mViews/mRoots等集合
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//3.把要添加的view交给ViewRootImpl
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
//...
}
}
}
ViewRootImpl添加到View之后,主要做了几件事:
- 调用requestLayout()异步刷新view
- 通过session与WMS通信,真正完成window的添加
//ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
//值维护一个view(DecorView)
if (mView == null) {
mView = view;
int res;
//1. 调用requestLayout绘制View
requestLayout();
//...
try {
//通过session与WMS通信,完成window的添加
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {...}
//...
}
首先,requestLayout()最后通过scheduleTraversals()来申请绘制,这部分我们在屏幕绘制的部分再详谈。只需要知道发起了重绘View的请求即可。
//ViewRootImpl
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查线程
checkThread();
//标志位
mLayoutRequested = true;
//请求绘制
scheduleTraversals();
}
}
当收到允许绘制的通知的时候,最终会进入到performTraversals(),从而对DecorView自顶向下地分发绘制流程,如measure/layout/draw。
requestLayout()之后,ViewRootImpl通过session通知WMS,去完成window的添加。IWindowSession是一个Binder引用,可以通过它来与WMS通信。WMS为每个应用创建一个单独的session,这个session可以通过WindowManagerGlobal获得。
public ViewRootImpl(Context context, Display display) {
mContext = context;
//实例化ViewRootImpl的时候,ViewRootImpl就从WIndowManagerGlobal中拿到了可用的session
mWindowSession = WindowManagerGlobal.getWindowSession();
...
}
看一下WindowManagerGlobal中如何提供session的:
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
//单例,如果已经有了,就不再创建了
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
//WMS的引用,这个是全局引用,和AMS一样,在ServiceManager中获取。
IWindowManager windowManager = getWindowManagerService();
//wms创建一个session交给当前客户端进程的WindowManagerGlobal。
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
首先我们发现WindowManagerGlobal首先会拿到WMS的引用,然后才通过WMS创建一个session,用于后续的通信。我们知道,系统服务由ServiceMananger管理,可以全局获取到WMS的binder引用。但为了让WMS知道和它通信的到底是哪个window,这久需要单独创建一个session,客户端window通过session向WMS发起通信。
我们来看一下WMS是如何处理这个openSession()请求的:
//WindowManagerService
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
IInputContext inputContext) {
//实例化了一个Session
Session session = new Session(this, callback, client, inputContext);
return session;
}
Session是一个binder实体,它持有WMS的直接引用,客户端window可以通过session来间接地通知WMS做一些操作。
获取到session后,ViewRootImpl的setView()就进入到了最后一步:session.addToDisplay():
//Session
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
//mServices就是WMS,让WMS来addWindow()
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
}
我们来到WMS的addWIndow()
//WindowManagerService
public int addWindow(Session session,IWindow client,...){
//为session建立一个WindowState,可以通过这个WindowState来与客户端通信。
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
}
至此,APP进程就成功将view注册到WMS中,同时,APP进程的WindowManagerGlobal可以通过session对WMS进行binder通信,WMS也可以通过WindowState来与WindowManagerGlobal进行binder通信。
3.2 Window对View的更新
我们再看到mGlobal.updateViewLayout(view,params);
//WindowManagerGlobal
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//1. 参数检查
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
//2.更新layoutParams以及mRoot中对应的ViewRootImpl
view.setLayoutParams(wparams);
synchronized (mLock) {
//找到这个view对应的ViewRootImpl是谁
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
//更新ViewRootImpl,这里setLayoutParams()最后也会调用到scheduleTraversals()来请求重绘,细节不在这里讨论
root.setLayoutParams(wparams, false);
}
}
最后到了ViewRootImpl.setLayoutParams(),最后也会调用到scheduleTraversals()来请求重绘,View的绘制、刷新的细节不在本文中讨论。
3.3 WIndow对View的删除
对View的删除大概分为以下几步:
- 首先让ViewRootImpl用die来删除
- 然后将要删除的view记录到mGlobal的mDyingViews集合中。
- View可能立即删除 doDie(),也可能不是立即删,就放入队列
- 移除各种回调
- 最后通知WMS移除这个window
我们直接看到最后:
//ViewRootImpl
mWindowSession.remove(mWindow);
4. 都有哪些Window会进行这样的创建、更新、删除操作?
Activity、Dialog、Toast等都需要View,而View都需要依附于WIndow
先从简单的Dialog对Window的创建谈起,最后再长篇大论到Activity的Window的创建
4.1 Dialog 的window创建
Dialog的构造方法明显看打了PhoneWindow的实例化、WindowManager的引用(借以与WMS通信)
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
//...
//获取windowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//实例化PhoneWindow
final Window w = new PhoneWindow(mContext);
mWindow = w;
//设置回调
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
//...
}
接着来到setContentView,就是把视图布局交给DecorView,细节我们在Activity.ssetContent()中讨论,几乎一样的。我们再来看一下show()方法
public void show() {
//...
mDecor = mWindow.getDecorView();
//...
WindowManager.LayoutParams l = mWindow.getAttributes();
boolean restoreSoftInputMode = false;
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
l.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
restoreSoftInputMode = true;
}
//使用WindowManager.addView
mWindowManager.addView(mDecor, l);
//...
}
mWindowManager.addView()我们知道最后会通过session通知到WMS,需要创建一个window来展示这个dialog。
4.2 长篇大论 Activity 的 Window创建
大概步骤如下:
- APP进程启动时,会通知AMS,application启动好了,并把applicationThread这个binder实体引用交给AMS
- AMS再以此通知app进程的第一个activity启动
- Activity启动之前初始化WindowManagerGlobal
- 最后onResume()执行完毕后,将window的添加通知给WMS
我们直接切入重点,AMS -> ActivityStarter-> ActivityStackSupervisor->realStartActivity()->app进程->handleLaunchActivity(),在这个方法中主要调用了Activity几个回调:
- attach()
- onCreate()
- onStart()
- onResume()
//ActivityThread
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// Initialize before creating the activity
if (!ThreadedRenderer.sRendererDisabled) {
GraphicsEnvironment.earlyInitEGL();
}
//创建单例,创建WMS引用 IWindowManager
WindowManagerGlobal.initialize();
//启动activity
performLaunchActivity();
//回调onResume()
handleResumeActivity();
}
Activity.attach()主要做了几件事:
- 建立了一个与Activity一一对应的PhoneWindow实例mWindow
- 为mWindow设置一个WMS引用
- Activity的mWindowManager也持有WMS引用
public class Activity implements Window.Callback,Window.OnWindowDismissedCallback,WindowControllerCallback,...{
private Window mWindow;//一个Activity有一个Window,实现类为PhoneWindow
private WindowManager mWindowManager;//WMS的引用
final void attach(...){
//一个activity对应一个PhoneWindow
mWindow = new PhoneWindow(Activit.this,window,...);
//设置一些回调
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
//...
//给Window设置一个WMS的引用
mWindow.setWindowManager(
//获取WMS的远程引用
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
//拿到WMS的引用
mWindowManager = mWindow.getWindowManager();
}
}
正常情况下,我们可能会在onCreate()中调用setContentView():
public void setContentView(int layoutResId){
getWindow().setContentView(layoutResId);
initWindowDecorActionBar();
}
getWindow()拿到的是mWindow,实现类是PhoneWindow,看到它的setContentView()
//PhoneWindow
private DecorView mDecor;
private ViewGroup mContentParent;
public void setContentView(int layoutResId){
if(mContentParent==null){
installDecor();//创建decorView,如果没有的话
}
//...
}
private void installDecor(){
//1. mDecore初始化
if(mDecor==null){
mDecore = generateDecor(-1);//new DecoreView(phoneWindow.this)
}
//2. mContentParent初始化,mDecoreView下的第一个ViewGroup
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//根据xml属性配置Activity的默认版型样式
}
}
protected ViewGroup generateLayout(DecorView decor){
//...
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//设置背景
//...
//设置title之类的
mDecor.finishChanging();
}
Activity的DecorView是由PhoneWindow来管理的,包括mContentParent。最后来到handleResumeActivity(),需要注意的是:
- 首先回调onResume()
- 然后使用设置了的DecorView,或者给一个默认的DecorView,通知WMS要addView()
performResumeActivity()->Activity.performResume()->Activity.onResume()
//ActivityThread
final void handleResumeActivity(){
//1.回调onResume
r = performResumeActivity();
//2.如果之前没有通过setContentView()设置mDecor,则给一个默认的
Activity a = r.activity;//当前这个activity
if(r.window==null){
//如果activity在onResume之后还没有mDecor,则会在这里给一个
View decor = r.window.getDecorView();//如果没有的话会PhoneWindow.installDecor()
decor.setVisibility(View.INVISIBLE);
//这个wm是一个WindowManager,它持有WMS的引用
//WindowManager也是一个接口,实现类是WindowManagerImpl
ViewManager wm = a.getWindowManager();
a.mDecor = decor;
if(a.mVisibleFromClient){
if(!a.mWindowAdded){
a.mWindowAdded= true;
wm.addView(decor,l);//l:WM.LayoutParams
}
}
}
}
如果在onResume()结束之前,用户都没调用setContentView(),那么会在这里给到一个mDecor。
wm.addView(decor,l)-> WindowManagerGlobal.addView()
此后的工作我们之前就讨论过了,不过是创建一个ViewRootImpl来维护这个DecorView,请求绘制之后通过session让WMS来添加window。
Activity.onDestroy()时,进入到ActivityThread.handleDestroyActivity(),其中通知了WMS去removeView(),也就是移除这个Activity的Window。最后再通知AMS自己的销毁,ActivityManager.getService().activityDestroyed();
参考文献:
本文基于Android8.0源码分析。结合一些博客的思路进行编排。
https://juejin.cn/post/6863756420380196877
https://blog.csdn.net/hfy8971613/article/details/103241153