Android窗口系统第一篇---Window的抽象概念和WMS相关数据结构理解

2,937 阅读15分钟
原文链接: www.jianshu.com

Android的窗口系统是比较难啃的一块内容了,这是UI架构很重要的一部分,数据结构比较多,细节比较多,面试的时候,面试官问我一个问题,r.token与app.token有什么区别,在一个月以前我是不能回答这个问题的,作为一个应用开发工程师转行到系统做Framework,最大的感觉就是Android系统博大精深,很多人可以单独搞定一个app,但是系统就不行了,以前看framework代码侧重流程,疏忽了不少细节。因为整个系统很大,学习起来要有一套方式方法,现在主要采取各个模块(比如WMS,每个模块仍然要细分)逐个击破,先高度抽象,总结架构,然后梳理流程,把握主线,重点函数,重点分析,深究细节,一边理解,一遍记录理解,画出类图和流程图,难于理解的地方,开启调试,细细打磨。本篇文章主要介绍窗口相关数据结构和抽象概念理解,关于 窗口部分的博客计划如下。

  • 窗口抽象概念理解和WMS相关数据结构
  • WMS的布局系统,计算与管理窗口的位置、层次
  • WMS的创建过程和启动过程
  • 窗口的创建和删除
  • 窗口大小的计算
  • 窗口的切换过程
  • 窗口的动画框架
  • 屏幕旋转

本篇文章主要讨论窗口坐标系统、Z序确定,Choreographer、WindowToken、WindowState、WindowManagerService中的关键成员等知识。

一、窗口布局

窗口的布局一般有两种,一种是平铺式的布局,一种是层叠式的布局

平铺窗口布局
平铺窗口布局

平铺式的布局设计简单,现在还有一些终端系统仍然是单窗口的平铺布局,手机上采用的是层叠式布局,层叠式布局是一个三维的空间,将手机的水平方向作为X轴,竖直方向作为Y轴,还有一根垂直与屏幕从里朝外方向的虚拟的Z轴,所有窗口 (WindowState) 按照顺序排列在Z轴上,如下图。

层叠式布局
层叠式布局

1.1、窗口主序的确定

WindowState是WMS中事实的窗口,而不是Window,包含了一个窗口的所有的属性,其中一个属性为mLayer,表示窗口在Z轴的位置,mLayer值越小,窗口越靠后,mLayer值越大,窗口越靠前,最前面的一个窗口就作为焦点窗口,可以接收触摸事件。因为窗口的切换,切换后的Z序(窗口的显示次序称为 Z 序)就可能不同,所以mLayer的值不是固定不变的。mLayer是通过WindowState的另一个成员变量mBaseLayer的值计算得到,mBaseLayer的值是固定不变的,只和窗口类型有关。mBaseLayer(称为主序)是WindowState的构造方法中赋值。

mBaseLayer = mPolicy.windowTypeToLayerLw(
                   attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER
                   + WindowManagerService.TYPE_LAYER_OFFSET;

windowTypeToLayerLw根据窗口的类型type,返回2开始的整数值

public int windowTypeToLayerLw(int type) {
       if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
           return 2;
       }
       switch (type) {
       case TYPE_WALLPAPER:
             return 2;
           // wallpaper is at the bottom, though the window manager may move it.
       case TYPE_PHONE:
           return 3;
      .....
    case TYPE_TOAST:
           // toasts and the plugged-in battery thing
           return 8;
        .....
       case TYPE_BOOT_PROGRESS:
           return 30;
       case TYPE_POINTER:
           // the (mouse) pointer layer
           return 31;
       }
       Log.e(TAG, "Unknown window type: " + type);
       return 2;

可以看到系统中的窗口种类比较多,case个数都到31了。比如应用程序的窗口是2,电话类型的窗口是3,Toast窗口是8...,因为系统中同类型的窗口比较多,所以对返回的整数值乘以10000(WindowManagerService.TYPE_LAYER_MULTIPLIER),在加上1000(WindowManagerService.TYPE_LAYER_OFFSET),相当把Z轴划分成了31个值域,不同类型的窗口的Z轴位置都是处于两个不相交的值域之中,互相不打扰。OK,通过mBaseLayer,我们知道了窗口是如何排序的,但是,假设系统中现在有5个应用程序的窗口,按照上面的规则,计算出来的主序应该相同,如何排序? 其实窗口的Z轴位置除了跟mBaseLayer有关与之外,还跟窗口在堆栈中的位置有关,后面会详细介绍窗口位置的计算。

1.2、窗口子序的确定

除了mBaseLayer,还有SubLayer(称为子序),SubLayer值是用来描述一个窗口是否属于另外一个窗口的子窗口,或者说SubLayer值是用来确定子窗口和父窗口之间的相对位置的。

SubLayer.png
SubLayer.png

一个Activity中有三个子窗口WindowState1、WindowState2、WindowState3,WindowState1WindowState2在窗口A的前面,WindowState3在A的后面,这几个兄弟窗口为什么可以这样排序呢,这就是mSubLayer的作用,子序越大,则相对其他兄弟窗口越靠前,反之,越靠后,如果为负数,就处在父窗口的后面,如窗口A中的WindowState3,子序是根据窗口类型调用subWindowTypeToLayerLw确定的。

  mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
 public int subWindowTypeToLayerLw(int type) {
       switch (type) {
       case TYPE_APPLICATION_PANEL:
       case TYPE_APPLICATION_ATTACHED_DIALOG:
           return APPLICATION_PANEL_SUBLAYER;//返回值是1
       case TYPE_APPLICATION_MEDIA:
           return APPLICATION_MEDIA_SUBLAYER;//返回值是-2  
       case TYPE_APPLICATION_MEDIA_OVERLAY:
           return APPLICATION_MEDIA_OVERLAY_SUBLAYER;//返回值是-1  
       case TYPE_APPLICATION_SUB_PANEL:
           return APPLICATION_SUB_PANEL_SUBLAYER;//返回值是2 
       case TYPE_APPLICATION_ABOVE_SUB_PANEL:
           return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;//返回值是3  
       }
       Log.e(TAG, "Unknown sub-window type: " + type);
       return 0;
   }

1.3、窗口的组成

窗口的组成
窗口的组成

这一部分很简单,Activity内部有PhoneWindow对象,PhoneWindow持有DecorView,DecorView继承Framelayout,具有标题部分和内容部分,我们写的各种View都在它的内容部分。

二、与窗口相关的数据结构

准确的说,这里先说一下与WMS相关的数据结构,因为与窗口相关的数据结构很多,动画的部分,绘图的部分都是相关的,先学习一小部分。


类的成员省去
类的成员省去

2.1、Choreographer的作用

WMS使用Choreographer负责所有的窗口动画和屏幕旋转动画,墙纸动画的渲染,用法跟Handler有点类似,他们都是在后期某个时机传入Runable对象,但是他们的回调时机不一样,Handler的处理时机取决与消息队列的处理情况,各个Message的时间都可能不同,而Choreograpoher的回调时机是下一次的VSYNC(垂直刷新同步),如果在当前时机没有处理完成就会造成失帧,造成造成失帧的原因往往是因为我们布局写的太过复杂,导致16ms内没有完成。 Android性能优化第(四)篇---Android渲染机制有介绍。

2.2、WindowToken的作用

你可以把WindowToken理解成是一个显示令牌,无论是系统窗口还是应用窗口,添加新的窗口的时候必须使用这个令牌向WMS表明自己的身份,添加窗口的时候会创建WindowToken,销毁窗口的时候移除WindowToken(removeWindowToken方法)。

WMS使用WindowToken将同一个应用组件(Activity,InputMethod,Wallpaper,Dream)的窗口组织在一起,换句话说,每一个窗口都会对应一个WindowToken,并且这个窗口中的所有子窗口将会对应同一个WindowToken,就如下面这个图的关系,假设窗口A是播放器中的一个窗口,除了主窗口外,还有三个子窗口,这些窗口的WindowToken都是一样的。

WindowToken和WindowState是1对多的关系.png
WindowToken和WindowState是1对多的关系.png

WindowToken的定义如下

class WindowToken {
   // The window manager!
   final WindowManagerService service;

   // The actual token.
   final IBinder token;

   // The type of window this token is for, as per WindowManager.LayoutParams.
   final int windowType;

   // Set if this token was explicitly added by a client, so should
   // not be removed when all windows are removed.
   final boolean explicit;

   // For printing.
   String stringName;

   // If this is an AppWindowToken, this is non-null.
   AppWindowToken appWindowToken;

   // All of the windows associated with this token.
   final WindowList windows = new WindowList();

   // Is key dispatching paused for this token?
   boolean paused = false;

   // Should this token's windows be hidden?
   boolean hidden;

   // Temporary for finding which tokens no longer have visible windows.
   boolean hasVisible;

   // Set to true when this token is in a pending transaction where it
   // will be shown.
   boolean waitingToShow;

   // Set to true when this token is in a pending transaction where its
   // windows will be put to the bottom of the list.
   boolean sendingToBottom;

   WindowToken(WindowManagerService _service, IBinder _token, int type, boolean _explicit) {
       service = _service;
       token = _token;
       windowType = type;
       explicit = _explicit;
   }

   void removeAllWindows() {
       for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
           WindowState win = windows.get(winNdx);
           if (DEBUG_WINDOW_MOVEMENT) Slog.w(TAG_WM, "removeAllWindows: removing win=" + win);
           win.mService.removeWindowLocked(win);
       }
       windows.clear();
   }
   ...
}

简单介绍一下各个成员的意思,实际的Token是WindowToken的Ibinder类型的token成员,windowType表示窗口的类型,explicit表示token创建是通过显式创建的还是通过隐式创建的,为true就是显式创建的。appWindowToken专门为Activity用的,windows这个成员,里面存放的是WindowState,表示WindowToken相同的所有窗口都在一起。hidden表示Activity组件是否是处于不可见状态。

如何向WMS声明一个令牌呢,其实任意一个Binder对象都可以作为令牌。声明令牌是通过WMS的addWindowToken函数,从而获取WMS帮忙添加窗口的许可。

WindowManagerService.java

   final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();

  @Override
   public void addWindowToken(IBinder token, int type) {
       if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
               "addWindowToken()")) {
           throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
       }

       synchronized(mWindowMap) {
           WindowToken wtoken = mTokenMap.get(token);
           if (wtoken != null) {
               Slog.w(TAG_WM, "Attempted to add existing input method token: " + token);
               return;
           }
           wtoken = new WindowToken(this, token, type, true);
           mTokenMap.put(token, wtoken);
           if (type == TYPE_WALLPAPER) {
               mWallpaperControllerLocked.addWallpaperToken(wtoken);
           }
       }
   }

声明WindowToken需要android.Manifest.permission.MANAGE_APP_TOKENS权限,然后进入同步代码块中,先尝试从mTokenMap中取WindowToken,如果没有,就创建WindowToken,并把它放到mTokenMap这个表中,对于墙纸类型的窗口,需要单独处理,调用mWallpaperControllerLocked.addWallpaperToken(wtoken),将WindowToken加到mWallpaperTokens表中。外部要添加窗口,首先调用WMS的addWindowToken方法申请令牌(WindowToken)。现在看下Activity中添加一个窗口(Dialog)令牌是怎么样的。 Activity的创建是由SystemServer进程的ActivityStackSupervisor的realStartActivityLocked方法调用scheduleLaunchActivity方法发起的。其中的参数r.appToken就是添加窗口所需要的令牌。

final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
           boolean andResume, boolean checkConfig) throws RemoteException {
   ...
   app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                   System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
                   new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
                   task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
                   newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
       ....
}

appToken是在ActivityRecord的构造函数中初始化的。

  appToken = new Token(this, service);

Token的定义

static class Token extends IApplicationToken.Stub {
       private final WeakReference<ActivityRecord> weakActivity;
       private final ActivityManagerService mService;

       Token(ActivityRecord activity, ActivityManagerService service) {
           weakActivity = new WeakReference<>(activity);
           mService = service;
       }
        //窗口绘制完成的时候调用
       @Override
       public void windowsDrawn() {
           synchronized (mService) {
               ActivityRecord r = tokenToActivityRecordLocked(this);
               if (r != null) {
                   r.windowsDrawnLocked();
               }
           }
       }

        //窗口可见的时候调用
       @Override
       public void windowsVisible() {
           synchronized (mService) {
               ActivityRecord r = tokenToActivityRecordLocked(this);
               if (r != null) {
                   r.windowsVisibleLocked();
               }
           }
       }
       //窗口不可见的时候调用
       @Override
       public void windowsGone() {
           synchronized (mService) {
               ActivityRecord r = tokenToActivityRecordLocked(this);
               if (r != null) {
                   if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "windowsGone(): " + r);
                   r.nowVisible = false;
                   return;
               }
           }
       }

       //取出一个Activity
       private static final ActivityRecord tokenToActivityRecordLocked(Token token) {
           if (token == null) {
               return null;
           }
           ActivityRecord r = token.weakActivity.get();
           if (r == null || r.task == null || r.task.stack == null) {
               return null;
           }
           return r;
       }
      ...
   }

当 app.thread.scheduleLaunchActivity执行之后,来到ActivityThread::ApplicationThread

 @Override
       public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
               ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
               CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
               int procState, Bundle state, PersistableBundle persistentState,
               List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
               boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

           updateProcessState(procState, false);

           ActivityClientRecord r = new ActivityClientRecord();

           r.token = token;
           ...
           sendMessage(H.LAUNCH_ACTIVITY, r);
       }

将token赋值给ActivityClientRecord,接下来,应用就会拿着这个token作为令牌去添加窗口(ActivityThread的handleResumeActivity中),细心的会看到,token和ActivityRecord在数量上是一对一的关系。之后这个Token是怎么传递给WMS的呢?

Token的传递上.png
Token的传递上.png

在Activity被创建出来之后,会调用Activity的attach方法,attach方法将token传给了Activity,Activity调用Window的setWindowManager方法,将token传给了Window,保存在Window的IBinder类型的成员变量mAppToken中。

![Uploading image_126547.png . . .]

Token的传递下.png
Token的传递下.png

之后ActivityThread发起addView,调用到了Window的adjustLayoutParamsForSubWindow方法,这个方法就将Window中mAppToken赋值给传进来的参数wp,之后WindowManagerGlobal调用ViewRootImpl的setView将参数wparams中的token传给WMS。

 public int addWindow(Session session, IWindow client, int seq,
           WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
           Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
           InputChannel outInputChannel) {
          WindowToken token = mTokenMap.get(attrs.token);
           AppWindowToken atoken = null;
           if (token == null) {
               ...
               token = new WindowToken(this, attrs.token, -1, false);
               addToken = true;
           }

}

总结:WindowToken对于客户端来说只是一个令牌,一个可以添加窗口的许可,WindowToken对于WMS来说,是一个WindowToken的实例对象,存储在mTokenMap中。WindowToken将同一个应用组件(Activity,InputMethod,Wallpaper,Dream)的窗口组织在一起。看完WindowToken的添加,你们可以分析一下AppWindowToken是怎么添加的。

2.3、WindowState的作用

WindowState用于窗口管理,这个是WMS中事实的窗口,包含了一个窗口的所有的属性,WindowState对象都存放在mWindowMap里面,,mWindowMap是所有窗口的一个全集,如果梳理WindowState的一些增加、移动和删除等操作,会更加理解这个类。前面也接触了一点,WindowState的构造函数会根据用户传进来的Window Type计算出窗口的mBaseLayer,mSubLayer和mLastLayer值,分别对应于主窗口,主窗口上弹出的子窗口(如输入法),以及动画时分别对应的ZOrder值。WindowState 构造函数中还会生成IWindowId.Stub 对象和DeathRecipient对象来分别监听Focus和窗口死亡的信息,生成一个WindowStateAnimator 负责整个Window的动画,并在内部将windowToken, appWindowToken等关联起来。WindowState的属性很多,看看它的构造函数。

WindowState.java

final class WindowState implements WindowManagerPolicy.WindowState {
   
  WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
          WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
          int viewVisibility, final DisplayContent displayContent) {
       mService = service;
       mSession = s;
       mClient = c;
       mAppOp = appOp;
       mToken = token;
       mOwnerUid = s.mUid;
       mWindowId = new IWindowId.Stub() {
         ....
       };
       mAttrs.copyFrom(a);
       mViewVisibility = viewVisibility;
       mDisplayContent = displayContent;
       mPolicy = mService.mPolicy;
       mContext = mService.mContext;
       DeathRecipient deathRecipient = new DeathRecipient();
       mSeq = seq;
       mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0;
       if (WindowManagerService.localLOGV) Slog.v(
           TAG, "Window " + this + " client=" + c.asBinder()
           + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
       try {
           c.asBinder().linkToDeath(deathRecipient, 0);
       } catch (RemoteException e) {
           mDeathRecipient = null;
           mAttachedWindow = null;
           mLayoutAttached = false;
           mIsImWindow = false;
           mIsWallpaper = false;
           mIsFloatingLayer = false;
           mBaseLayer = 0;
           mSubLayer = 0;
           mInputWindowHandle = null;
           mWinAnimator = null;
           return;
       }
       mDeathRecipient = deathRecipient;

       WindowState appWin = this;
       while (appWin.isChildWindow()) {
           appWin = appWin.mAttachedWindow;
       }
       WindowToken appToken = appWin.mToken;
       while (appToken.appWindowToken == null) {
           WindowToken parent = mService.mTokenMap.get(appToken.token);
           if (parent == null || appToken == parent) {
               break;
           }
           appToken = parent;
       }
       mRootToken = appToken;
       ....
       mWinAnimator = new WindowStateAnimator(this);
       mWinAnimator.mAlpha = a.alpha;

       mRequestedWidth = 0;
       mRequestedHeight = 0;
       mLastRequestedWidth = 0;
       mLastRequestedHeight = 0;
       mXOffset = 0;
       mYOffset = 0;
       mLayer = 0;
       mInputWindowHandle = new InputWindowHandle(
               mAppToken != null ? mAppToken.mInputApplicationHandle : null, this,
               displayContent.getDisplayId());
   }

}

解释一下各个变量的意思,老罗总结的详细,下面解释摘自老罗的博客

  • mSession:指向一个类型为Session的Binder本地对象,使用参数s来初始化,表示当前所创建的WindowState对象是属于哪一个应用程序进程的,Session是进程唯一的。

  • mClient:指向一个实现了IWindow接口的Binder代理对象,它引用了运行在应用程序进程这一侧的一个类型为W的Binder本地对象,使用参数c来初始化,通过它可以与运行在应用程序进程这一侧的Activity组件进行通信。

  • mToken:指向一个WindowToken对象,使用参数token来初始化,通过它就可以知道唯一地标识一个窗口。

  • mAttrs:指向一个WindowManager.LayoutParams对象,使用参数a来初始化,通过它就可以知道当前当前所创建的WindowState对象所描述的窗口的布局参数。

  • mViewVisibility:这是一个整型变量,使用参数viewVisibility来初始化,表示当前所创建的WindowState对象所描述的窗口视图的可见性。

  • mAlpha:这是一个浮点数,使用参数a所描述的一WindowManager.LayoutParams对象的成员变量alpha来初始化,表示当前所创建的WindowState对象所描述的窗口的Alpha通道。

  • mLayer,mBaseLayer和mSubLayer上面已经说过

  • mAttachedWindow:指向一个WindowState对象,用来描述一个子窗口的父窗口。

  • mLayoutAttached:这是一个布尔变量,用来描述一个子窗口的视图是否是嵌入在父窗口的视图里面的。对于非子窗口来说,这个值固定为false;对于子窗口来说,这个值只有子窗口的类型是非对话框时,它的值才会等于true,否则都等于false。

  • mIsImWindow:这是一个布尔变量,表示当前所创建的WindowState对象所描述的窗口是否是一个输入法窗口或者一个输入法对话框。

  • mIsWallpaper :这是一个布尔变量,表示当前所创建的WindowState对象所描述的窗口是否是一个壁纸窗口。

  • mSurface:指向一个mSurface对象,用来描述窗口的绘图表面。

  • mRequestedWidth:这是一个整型变量,用来描述应用程序进程所请求的窗口宽度。

  • mRequestedHeight:这是一个整型变量,用来描述应用程序进程所请求的窗口高度。

  • mLastRequestedWidth:这是一个整型变量,用来描述应用程序进程上一次所请求的窗口宽度。

  • mLastRequestedHeight:这是一个整型变量,用来描述应用程序进程上一次所请求的窗口高度。

  • mXOffset:这是一个整型变量,用来描述壁纸窗口相对屏幕在X轴上的偏移量,对其它类型的窗口为说,这个值等于0。

  • mYOffset:这是一个整型变量,用来描述壁纸窗口相对屏幕在Y轴上的偏移量,对其它类型的窗口为说,这个值等于0。

  • mAnimLayer:这是一个整型变量,用来描述窗口的Z轴位置值,它的值可能等于mLayer的值

  • mLastLayer:这是一个整型变量,用描述窗口上一次所使用的mAnimLayer的值

2.4、WMS中的集合解释

  • 保存所有的Session,上面说过 final ArraySet<Session> mSessions = new ArraySet<>();

  • 保存WindowState,给出一个键,能够快速找出对应的窗口 final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

  • 保存WindowToken final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();

  • 保存已经完成启动的应用。 final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();

  • 保存尺寸正在改变的窗口,当改变完成后,需要通知应用。 final ArrayList<WindowState> mResizingWindows = new ArrayList<>();

  • 保存动画结束的窗口 final ArrayList<WindowState> mPendingRemove = new ArrayList<>();

  • 保存需要释放Surface的窗口。 final ArrayList<WindowState> mDestroySurface = new ArrayList<>();

  • 保存需要强行关闭的窗口,以释放内存。 final ArrayList<WindowState> mForceRemoves = new ArrayList<>();

  • 保存失去焦点的窗口,等待获得焦点的窗口进行显示 ArrayList<WindowState> mLosingFocus = new ArrayList<>();

  • 保存等待绘制的窗口 ArrayList<WindowState> mWaitingForDrawn = new ArrayList<>();

好了,与WMS相关的难以理解的数据结构都写在这个,后面遇到重要的数据结构,比如PhoneWindowManager是窗口策略类,继承了WindowManagerPolicy,主要进行窗口大小计算,DisplayContent等,再在到这补充,下一篇写WMS的布局系统,计算与管理窗口的位置、层次。