Android从屏幕刷新到View的绘制(一)之 Window、WindowManager和WindowManagerService之间的关系
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交给ViewRootImpltry {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绘制ViewrequestLayout();//...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中拿到了可用的sessionmWindowSession = 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() {@Overridepublic 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) {//实例化了一个SessionSession 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中对应的ViewRootImplview.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) {//...//获取windowManagermWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//实例化PhoneWindowfinal 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.addViewmWindowManager.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 activityif (!ThreadedRenderer.sRendererDisabled) {GraphicsEnvironment.earlyInitEGL();}//创建单例,创建WMS引用 IWindowManagerWindowManagerGlobal.initialize();//启动activityperformLaunchActivity();//回调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,实现类为PhoneWindowprivate WindowManager mWindowManager;//WMS的引用final void attach(...){//一个activity对应一个PhoneWindowmWindow = 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下的第一个ViewGroupif (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.回调onResumer = performResumeActivity();//2.如果之前没有通过setContentView()设置mDecor,则给一个默认的Activity a = r.activity;//当前这个activityif(r.window==null){//如果activity在onResume之后还没有mDecor,则会在这里给一个View decor = r.window.getDecorView();//如果没有的话会PhoneWindow.installDecor()decor.setVisibility(View.INVISIBLE);//这个wm是一个WindowManager,它持有WMS的引用//WindowManager也是一个接口,实现类是WindowManagerImplViewManager 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