以下为启动一个activity时,跟WMS相关的流程图。如果对启动activity有兴趣,可以参看一些AMS的文章,推荐老罗的android 之旅。老罗的文章可能基于的是android 比较老的版本,跟最新的系统可能会有些差别, 但是基本原理大同小异。
该流程图基于android L, android N 上改动应该不大,所以没有重新画,仅供参考。
为了大家有一个快速的概要的了解,下面会以这个流程图为基础,概略的介绍主要的流程,然后才会详细的就一个点展开。
总体来说,在添加一个新窗口的过程中,WMS主要跟AMS、 activity打交道。PhoneWindowManager会协助WMS完成一些功能,例如添加starting window.
所以添加窗口的过程,按顺序主要分为两大部分,会在后面分别介绍:
1. AMS通知WMS将要添加新窗口。
2. Activity添加窗口到WMS并绘制显示窗口。
在这个过程中会涉及到WMS的几大基本功能:添加窗口、AppToken管理、计算Z-order,计算窗口大小、显示转换动画、计算configuration后面会分别介绍。
AMS会按顺序调用WMS以下函数:
1) moveTaskToTop()。 将新的activity所在的task移动最前面。 如果这个task是已经存在的task, 将该task 移动最前面。如果没有该task, 这个函数什么都不做,而是在addAppToken()中新建该task 并移动到最前。
2) prepareAppTransition()。新的activity启动时,会显示一个动画从旧的界面过渡到新的activity界面, 这个叫appTransition。 该函数通知WMS根据当前启动的activity以及task情况准备动画, 例如是新启动一个activity在当前activity之上,还是退出当前activity返回以前的一个activity。关于AppTransition的类型,可以参考AppTransition.java。
3) addAppToken()。
这里要介绍一个在WMS、AMS和Activity交互中很重要的一个概念AppToken。 AMS为每个activity创建一个AppToken。这个AppToken会传送给WMS和activity, 通过匹配AppToken, 从而确定从AMS发出的指令是针对哪个activity的。具体AppToken 的管理参加后面AppToken管理。
如moveTaskToTop()所述,如果该activity是在一个新的task中启动,那么WMS会在这个函数中新创建新的task并放在最上面。
4) setAppStartingWindow()
在这个函数中,WMS会通过PhoneWindowManager为这个activity添加一个starting window。 关于starting window上篇曾有介绍。
5) setFocusedApp() 顾名思义,告诉WMS哪个APP 是focus的,用于接收input信息。
6) setAppVisibility()
在AMS的resumeTopActivity()函数中,会调用WMS的setAppVisibility(), 一方面是设置前一个activity的visibility到 false,也就是隐藏前一个activity。 另一方面设置当前要启动的activity的visibility 到true。那么通过这个过程, 以前的StartingWindow就可以先显示出来了。
关于AMS resume activity的过程,请参阅相关的AMS的文档。
7) excuseAppTransiction()
告诉WMS执行AppTransiction,显示动画。注意,这个动画是从上一个activity过渡到Starting Window的动画。
到目前为止,都是AMS在做一些前期准备工作, 还没有activity与WMS的任何交互,也没有任何Activity自己绘制的界面显示到屏幕上。
那么下面就涉及到activity的工作了。
为什么要介绍appToken呢,如果只是简单的了解一下WMS工作原理,不需要知道这个概念,所以不需要看WMS代码的同学可以绕过这一部分。但是如果想要看WMS的代码,就一定要知道appToken是什么,因为每个地方都在用。
1) AMS为每个activity创建一个AppToken, 这个appToken 可以在WMS和activity端来标示一个activity。如果AMS要调用WMS函数时,一般都会传送appToken去标示针对的是哪个activity的操作, 例如在调用setFocusedApp时就要传送这个参数来标示要设置该activity为focused。
AppToken 继承自IApplicationToken.stub. 从名字就可以看出来这是一个aidl类型的接口。
2) 创建一个AppToken过程为:ActivityStackSupervisor.startActivityLocked() --〉 ActivityRecord()--〉 New Token()。
3) AMS通过WindowManagerService.addAppToken() 将这个token加入到WMS中。
在WMS中,会新建一个AppWindowToken类型的实例,这个AppWindowToken按照WMS的需求描述一个activity。 例如是否全屏,是否要求固定的orientation。 WMS将其加入到mTokenMap中。
下面是mTokenMap的定义. 其中的key值IBinder就是对应的从AMS传过来的appToken.
Value值对应的就是为WindowToken.一般为AppWindowToken, 或者针对system window的WindowToken。 mTokenMap的顺序与AM中stack中activity的顺序相同。
/**
*Mapping from a token IBinder to a WindowToken object.
*/
final HashMap
mTokenMap = newHashMap<>(); 4) AMS通过setFocusedApp()设置focused app, 参数为appToken.
5) 当一个activity 销毁时,AMS负责通知WMS去移除这个appToken。
6) AMS也会将这个appToken传送给activity。那么当activity需要与WMS交互时,也要传送这个参数,例如当activity 需要向WMS中添加窗口时,调用addWindow(),appToken作为一个参数传送给WMS, WMS 通过appToken 可以确定该窗口属于哪个activity.
1) addWindow(): 添加一个activity 的主窗口 到WMS, 建立起和inputManagerService的联系。
2) relayoutWindow(): 有WM创建用于绘制的surface, 并返回给activity, WMS 计算activity主窗口在屏幕上的位置,大小并返回给activity。用于activity 去measure 和layout 各个view。3) finishDrawingWindow():activity绘制完窗口后,调用该函数通知WMS开始显示该窗口。
以上是化繁为简的一个描述,真正的处理还是很复杂的,下面会详细介绍。
要了解Activity端如何添加窗口到WMS,首先要先对activity端的机制有个简单的了解。下面介绍Activity端与窗口管理相关的类。
下面是一个DecorView的简单示意图。
实际上Decorview是一个FrameLayout。setContentView()设置的是DecorView中的ID为com.android.internal.R.id.content的一部分。也就是最后加入到WMS中的被显示出来的视图是DecorView. DecorView负责跟ViewRootImpl通信,负责分发消息,meature layout, 绘制视图等。ViewRootImpl是activity端负责与WMS交互的类,详见后面会再介绍。
• ViewRootImpl: 这个类是直接与WMS交互的类,这个类是WMS与Activity 交互的桥梁。ViewRootImpl持有两个成员变量:
final IWindowSession mWindowSession;
final W mWindow;// W extends IWindow.Stub
IWindowSession 是WMS的代理类,activity通过IWindowSession向WMS端发送消息。IWindow.Stub是activity 端的代理类,WMS通过IWindow.Stub接口发送消息给activity。 mWindow传送给WMS后,在WMS还承担了一个索引作用。通过mWindow查找相应的WMS端的WindowState。WindowState是WMS中的一个重要类,一个WindowState实例,描述一个窗口的状态。是WMS的基本的管理单元。
ViewRootImpl有两个主要作用:
a. 分发用户的按键,触摸等消息到DecorView.
b. 同WMS通信,添加窗口到WMS,从WMS获得窗口Layout,通知WMS窗口绘制完成可以显示等。另外接受从WMS发送过来的消息,例如窗size变化,焦点变化等。
ViewRootImpl 持有一个handler的子类ViewRootHandler, 与UI有关的操作都由该handler处理,运行在activity主线程中。
• Window:顾名思义,activity端的窗口类,但是这个类只是一个定义activity端基本policy的类,没有过多实质功能。
• PhonwWindow: 基类为Window. 是特定用于android的Window. 这个类的实例在activity.attach()中被创建.主要的功能:
a.创建DecorView,PhoneWindow持有DecorView的实例。
b.创建和移除 panel window。 pop up menu 就是一种panel window.
c.接受从WMS传过来的rotation change 消息, 处理panel rotation。
d.处理一些按键触屏事件,例如menu key, 如果activity涉及mediaplayer操作,响应volume 可以设置相应mediaplayer的 volume 等。
• WindowManager: 接口类。定义一些app 和WMS中通用的参数,比如窗口类型,绘制时用的LayoutParams。
• WindowManagerImpl: WindowManager的具体实现。 也可以认为针对某个特定activity的的Window Manager。跟context 相关,也就是与特定activity相关。一个重要的功能就是负责绑定子窗口到app主窗口。
• WindowManagerGlobal:这个类名字里有个Global,顾名思义,WindowManagerGlobal并不针对某个特定的context,也就是activity。 在一个进程中WindowManagerGlobal是单例的,是负责整个app进程中的全局的窗口管理的类。例如一个app可能有多个activity, 那么WindowManagerGlobal并不是针对某个activity而是作为整个app的window Manager。管理整个app的全部窗口(decorview)和每个窗口对应的ViewRootImpl。 一个进程中只有一个WindowManagerGlobal的实例。 提供了一个重要的函数getWindowManagerService()。 可以直接获得WMS的服务接口(iWindowManager)。从而与WMS直接通讯。
WindowManagerImpl通过addView()添加DecorView到WindowManagerGlobal,WindowManagerGlobal为该decorView创建一个ViewRootImpl, 然后调用ViewRootImpl的setView ()函数将该DecorView设置给ViewRootImpl, 从而引发跟WMS的一系列添加绘制窗口的通信。WindowManagerGlobal中两个成员变量mViews 和mRoots 定义如下, 其中的view 和ViewRootImpl是一一对应的:
ArrayList
mViews; //all DécorView in this app ArrayList mRoots; // all ViewRootImpl in this app
然后看看各个类之间的关系,基本上就是继承,组合,聚合之类的。下图为一个类关系视图。
Activity持有的一个PhoneWindow和WindowManagerImpl的实例。
PhoneWindow是这个activity的主窗口对应的PhoneWindow, 这个PhoneWindow创建持有一个DecorView实例,这个DecorView就是整个activity要呈现出的样子。要添加WMS管理的窗口针对的就是这个DecorView。 WMS管理这个窗口的大小, 边界,在整个系统中所有窗口的Z-order。至于窗口中要呈现的内容,WMS并不管,而是由actitiy负责,也就是DecorView对应的内容。
前面说过WindowManagerImpl是与特定Activity相关的。这里的WindowManagerImpl也就是与这个activity关联的WindowManagerImpl。 每个activity都有自己的WindowManagerImpl。WindowManagerImpl也会持有一个WindowManagerGlobal的实例,这个WindowManagerGlobal是单例的,前面也说过,WindowManagerGlobal是整个process全局的负责窗口管理的类, 持有process内所有的decorview和对应的ViewRoomImpl。
1)创建WindowManager:
Activity.attach(): new a phonewindow
create WindowManagerImpl
2)Activity 添加主窗口的DecorView到WindowManager,函数为handleResumeActivity()@ActivityThread.java, 代码如下:
r = performResumeActivity(token, clearHide, reason);
……
r.window = r.activity.getWindow(); //得到activity的phoneWindow
View decor = r.window.getDecorView(); //得到DecorView
decor.setVisibility(View.INVISIBLE); //设置界面为不可见
ViewManager wm = a.getWindowManager(); //得到activity对应的WindowManagerImpl实例
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; //设置窗口类型为主窗口
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient && ! a.mWindowAdded ) {
a.mWindowAdded = true;
wm.addView(decor, l); //添加DecorView到WindowManager, 进而触发添加Window到WMS的一系列操作。
}
……
r.activity.makeVisible();//设置activity为可见。
以添加Dialog为例, 代码为dialog代码 @Dialog.java:
1) 初始化:
mWindowManager =(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);//得到WindowManagerImpl实例
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w
w.setWindowManager(mWindowManager, null, null);
2) 显示,show()@Dialog.java:
mDecor = mWindow.getDecorView();
mWindowManager.addView(mDecor, l);
WindowManagerGlobal.addView() -->parrentWindow.adjustLayoutParamsForSubWindow ()
在这里会建立主窗口和dialog的联系:
View decor =peekDecorView();
if (decor != null) {
wp.token =decor.getWindowToken();// token 是主窗口的ViewRootImpl中持有的IWindow. WMS持有该IWindow, 所以在WMS端,会检查
//wp.token的值,进而将该dialog与activity的主窗口相关联。
}
这几个函数前面已经介绍过了,那么下面主要看一下WMS端addWindow的实现。
• WindowState:一个实例代表一个WMS中的窗口。维护窗口的状态,大小,surface,inputchannel 等。
• WindowStateAnimator:对应单一的WindowState, 负责该窗口的动画,surface操作。
• WindowAnimator:单例类,负责整个WMS的动画和Surface操作。
• AppWindowToken:WMS端描述一个activity的类。一个实例代表一个有窗口正在显示的activity。
• mWindowMap: HashMap
。持有所有的窗口。 Key值为一个 IBinder对象,实际就是 ViewRootImpl中传过来的IWindow,value 是对应的 WindowState对象。 • mTokenMap: HashMap
。前面appToken管理是介绍过, Mapping其中的key值IBinder就是对应的从AMS传过来的 appToken。Value值对应的就是为WindowToken。一般为AppWindowToken, 或者针对system window的WindowToken。 mTokenMap的顺序与AM中stack中activity的顺序相同。• DisplayContent.mWindows:ArrayList
。 在特定的显示屏幕上的以Z-order排序的所有窗。
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel)
下面来看一下WMS中addWindow的大概的业务流程,为了避免看soucecode,更清晰直观的理解addWindow的流程,我使用下面的的流程图来说明,基本上从函数名字上就可以理解在做什么的,我都用了函数名, 或者理解不了的在旁边做了说明,在阅读代码是可以容易的对应上。有兴趣 可以参照soucecode来看。这个流程中只考虑了添加一个activity窗口的流程,并不包括一些系统窗口,比如wallpaper或者inputmethod窗口等, 了解了activity窗口流程,那么其他的也很容易看懂。如果感兴趣可以自行参阅addWindow()代码中的相关部分。
需要注意的是:
好了,到此添加一个窗口到WMS的过程就算讲完了,这里只是讲了怎么“添加”窗口到WMS, 至于添加窗口中涉及的细节,如WMS的Z-Order, configuration change, 以及后续的确定窗口大小,显示窗口到surface flinger等,会在后续文章中介绍。• Add Startingwindow and base applicationi window, don’t change focus.
• Win.attach(): create mSurfaceSession for app. 用于跟surface flinger通信。