当前位置: 首页 > news >正文

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处理。

VeznzR.png

WMS也会为每个WIndow创建一个WindowState来管理它们,具体的渲染工作交给了SurfaceFinger处理。本文只讨论View、Window、WindowManager与WMS的关系。

img

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(),具体步骤大概如下:

  1. 各类数据检查
  2. 更新mViews、mRoots等集合
  3. 创建一个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之后,主要做了几件事:

  1. 调用requestLayout()异步刷新view
  2. 通过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的删除大概分为以下几步:

  1. 首先让ViewRootImpl用die来删除
  2. 然后将要删除的view记录到mGlobal的mDyingViews集合中。
  3. View可能立即删除 doDie(),也可能不是立即删,就放入队列
  4. 移除各种回调
  5. 最后通知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创建

大概步骤如下:

  1. APP进程启动时,会通知AMS,application启动好了,并把applicationThread这个binder实体引用交给AMS
  2. AMS再以此通知app进程的第一个activity启动
  3. Activity启动之前初始化WindowManagerGlobal
  4. 最后onResume()执行完毕后,将window的添加通知给WMS

我们直接切入重点,AMS -> ActivityStarter-> ActivityStackSupervisor->realStartActivity()->app进程->handleLaunchActivity(),在这个方法中主要调用了Activity几个回调:

  1. attach()
  2. onCreate()
  3. onStart()
  4. 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()主要做了几件事:

  1. 建立了一个与Activity一一对应的PhoneWindow实例mWindow
  2. 为mWindow设置一个WMS引用
  3. 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(),需要注意的是:

  1. 首先回调onResume()
  2. 然后使用设置了的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

http://www.lryc.cn/news/21613.html

相关文章:

  • #多源数据融合#:HSI与Lidar
  • android 权限控制与进程隔离
  • 链表(一):移除链表元素、设计链表等力扣经典链表题目
  • 计算机网络 第4章 作业1
  • Redis-Java代码使用示例
  • acwing3485最大异或和(trie树,贪心)
  • EasyRecovery16免费的电脑的数据恢复工具
  • 银行数字化转型导师坚鹏:平安银行数字化转型—橙E网战略研究
  • tun驱动之open
  • 计算机网络体系结构
  • 基础夯实,字节内部总结240道算法LeetCode刷题笔记,直呼太全
  • Three.js使用WebWorker进行八叉树碰撞检测
  • 【教程】Notion笔记多平台设置中文显示
  • [牛客Hot101]链表篇
  • Vue3 核心模块源码解析(上)
  • 【C进阶】指针的高级话题
  • 无源晶振匹配电容—计算方法
  • 【测试】自动化测试03(JUnit)
  • 《计算机视觉和图像处理简介 - 中英双语版》:神经网络中的激活函数 ReLU vs Sigmoid
  • (三十七)大白话SQL标准中对事务的4个隔离级别,都是如何规定的呢?
  • 全国计算机等级考试三级网络技术考试大纲(2022年版)
  • 服务器部署—若依【vue】如何部署到nginx里面?nginx刷新页面404怎么办?【完美解决建议收藏】
  • 算法练习(特辑)算法常用的数据结构、集合和方法总结
  • Apk转Aab(Android-App-Bundle)
  • 大学物理期末大题专题训练总结-热学大题
  • 有趣的Hack-A-Sat黑掉卫星挑战赛——卫星平台内存dump
  • OAK相机如何将yoloV8模型转换成blob格式?
  • Python解题 - CSDN周赛第32期 - 运输石油(三维背包)
  • JVM - G1垃圾收集器深入剖析
  • 角度制与弧度制的相互转换np.deg2radnp.rad2deg