天天看点

Android 窗口管理:如何添加窗口到WMS2. AppToken管理

        本文以启动一个新的activity为例,说明如何添加新的窗口到WMS。

        以下为启动一个activity时,跟WMS相关的流程图。如果对启动activity有兴趣,可以参看一些AMS的文章,推荐老罗的android 之旅。老罗的文章可能基于的是android 比较老的版本,跟最新的系统可能会有些差别, 但是基本原理大同小异。

Android 窗口管理:如何添加窗口到WMS2. AppToken管理

        该流程图基于android L, android N 上改动应该不大,所以没有重新画,仅供参考。

        为了大家有一个快速的概要的了解,下面会以这个流程图为基础,概略的介绍主要的流程,然后才会详细的就一个点展开。

        总体来说,在添加一个新窗口的过程中,WMS主要跟AMS、 activity打交道。PhoneWindowManager会协助WMS完成一些功能,例如添加starting window.

        所以添加窗口的过程,按顺序主要分为两大部分,会在后面分别介绍:

1.      AMS通知WMS将要添加新窗口。

2.      Activity添加窗口到WMS并绘制显示窗口。

        在这个过程中会涉及到WMS的几大基本功能:添加窗口、AppToken管理、计算Z-order,计算窗口大小、显示转换动画、计算configuration后面会分别介绍。

1. AMS准备启动一个新的activity

        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的工作了。

2. AppToken管理

        为什么要介绍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<IBinder, WindowToken> 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.

3. Activity添加窗口到WMS并绘制显示窗口

        我们一直知道,Activity是onResume()之后才界面才显示出来的,那么这个过程是怎么样的呢?AMS会调用scheduleResumeActivity(),这个函数会触发activity端的一系列函数,包括onCreate() 和onResume()。在activity的onResume之后, activity端会调用WMS的三个函数:

1)     addWindow(): 添加一个activity 的主窗口 到WMS, 建立起和inputManagerService的联系。

2)     relayoutWindow(): 有WM创建用于绘制的surface, 并返回给activity, WMS 计算activity主窗口在屏幕上的位置,大小并返回给activity。用于activity 去measure 和layout 各个view。

3)     finishDrawingWindow():activity绘制完窗口后,调用该函数通知WMS开始显示该窗口。

        以上是化繁为简的一个描述,真正的处理还是很复杂的,下面会详细介绍。

3.1 Activity端与窗口管理相关的类

        要了解Activity端如何添加窗口到WMS,首先要先对activity端的机制有个简单的了解。下面介绍Activity端与窗口管理相关的类。

  • DecorView:我们知道,在activity 的onCreate()函数中,通常会调用setContentView()设置activity的layout。但是这个layout并不是activity的视图的全部, activity的窗口中可能还会有title, action bar 等。所有的这些组成了activity的完整的视图, 这个完整的视图在activity 中叫DecorView。
下面是一个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<View> mViews; //all DécorView in this app
ArrayList<ViewRootImpl> mRoots; // all ViewRootImpl in this app
           

        然后看看各个类之间的关系,基本上就是继承,组合,聚合之类的。下图为一个类关系视图。

Android 窗口管理:如何添加窗口到WMS2. AppToken管理

        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。

3.2 Activity 添加主窗口的过程:

   有了前面对各个类的理解的基础,我们通过代码来看看activity如何添加一个主窗口到WMS:

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为可见。
           

3.3 添加一个Sub Window

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">       以添加Dialog为例, 代码为dialog代码 @<span style="font-family:monospace;font-size:14px;color:#202062;">Dialog.java</span>:</span>
           
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);
           

              上面 mWindowManager.addView()会调用:

               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的主窗口相关联。
      }
           

4. WMS端添加窗口

        在activity端, 最 终当调用ViewRootImpl中的setView()时,会触发对WMS的一系列调用, 主要为:

  •          addWindow()
  •          relayoutWindow()
  •          finishDrawingWindow()

       这几个函数前面已经介绍过了,那么下面主要看一下WMS端addWindow的实现。

4.1. 首先认识一下WMS中主要的一些类和成员

• WindowState:一个实例代表一个WMS中的窗口。维护窗口的状态,大小,surface,inputchannel 等。 
• WindowStateAnimator:对应单一的WindowState, 负责该窗口的动画,surface操作。      
• WindowAnimator:单例类,负责整个WMS的动画和Surface操作。      
• AppWindowToken:WMS端描述一个activity的类。一个实例代表一个有窗口正在显示的activity。      

• mWindowMap:  HashMap<IBinder, WindowState> 。持有所有的窗口。 Key值为一个 IBinder对象,实际就是 ViewRootImpl中传过来的IWindow,value 是对应的 WindowState对象。

• mTokenMap: HashMap<IBinder,WindowToken>。前面appToken管理是介绍过, Mapping其中的key值IBinder就是对应的从AMS传过来的appToken。Value值对应的就是为WindowToken。一般为AppWindowToken, 或者针对system window的WindowToken。  mTokenMap的顺序与AM中stack中activity的顺序相同。

• DisplayContent.mWindows:ArrayList<WindowState>。 在特定的显示屏幕上的以Z-order排序的所有窗。

4.2. addWindow()定义

    public int addWindow(Session session, IWindow client, int seq,      
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,      
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,      
            InputChannel outInputChannel)       
  • session:  即为Client端ViewRootImpl持有的mWindowSession。 这个mWindowSession为WindowManagerGlobal创建,针对当前App而不是单个activity的WMS的代理。
  • client: 为ViewRootImpl持有的mWindow, 也就是WMS向activity发送消息的aidl接口。同时在WMS端也起到索引窗口的作用。
  • attrs:为该窗口的layout属性设置。
  • viewVisibility:是否可见,对于刚加入的activity window, 一般都是invisible的。
  • displayId:显示在哪个屏幕上
  • outContentInsets:看名字,前面有个out, 代表是由WMS返回给activity的参数,说明窗口内容区域边衬大小。如图所示:
Android 窗口管理:如何添加窗口到WMS2. AppToken管理
  • outStableInsets: 算上status bar 或者navigation bar 的边衬区域。通常fullscreen的界面会用到这个值。
Android 窗口管理:如何添加窗口到WMS2. AppToken管理
  • outOutsets: chin跟content 区域的offset, 所谓的chin, 比如,某个区域并不实际显示在屏幕上,但是希望它像显示在屏幕上一样。一般情况下,只都为0。可以在系统的resource中设置该区域的值。Resource为config_windowOutsetBottom。 
Android 窗口管理:如何添加窗口到WMS2. AppToken管理
  • outInputChannel:在WMS中建立一对inputChannel,  这两个inputChannel,监听管道的消息,但是方向相反,一个用于input manager service 转送消息给activity, 并接受来自activity的消息。 另一个用于activity发送消息给inputManagerservice, 并监听input manager service发送的消息,这个会传送回activity。给个简单的示意图:
    Android 窗口管理:如何添加窗口到WMS2. AppToken管理

4.3. addWindow的大概的业务流程

        下面来看一下WMS中addWindow的大概的业务流程,为了避免看soucecode,更清晰直观的理解addWindow的流程,我使用下面的的流程图来说明,基本上从函数名字上就可以理解在做什么的,我都用了函数名, 或者理解不了的在旁边做了说明,在阅读代码是可以容易的对应上。有兴趣 可以参照soucecode来看。这个流程中只考虑了添加一个activity窗口的流程,并不包括一些系统窗口,比如wallpaper或者inputmethod窗口等, 了解了activity窗口流程,那么其他的也很容易看懂。如果感兴趣可以自行参阅addWindow()代码中的相关部分。

Android 窗口管理:如何添加窗口到WMS2. AppToken管理

  需要注意的是:

• Add Startingwindow and base applicationi window, don’t change focus.

• Win.attach(): create mSurfaceSession for app. 用于跟surface flinger通信。

  好了,到此添加一个窗口到WMS的过程就算讲完了,这里只是讲了怎么“添加”窗口到WMS, 至于添加窗口中涉及的细节,如WMS的Z-Order, configuration change, 以及后续的确定窗口大小,显示窗口到surface flinger等,会在后续文章中介绍。

继续阅读