zoukankan      html  css  js  c++  java
  • Android窗口系统第二篇---Window的添加过程

    以前写过客户端Window的创建过程,大概是这样子的。我们一开始从Thread中的handleLaunchActivity方法开始分析,首先加载Activity的字节码文件,利用反射的方式创建一个Activity对象,调用Activity对象的attach方法,在attach方法中,创建系统需要的Window并为设置回调,这个回调定义在Window之中,由Activity实现,当Window的状态发生变化的时候,就会回调Activity实现的这些回调方法。调用attach方法之后,Window被创建完成,这时候需要关联我们的视图,在handleLaunchActivity中的attach执行之后就要执行handleLaunchActivity中的callActivityOnCreate,在onCreate中我们会调用setContentView方法。通过setContentView,创建了Activity的顶级View---DecorView,DecorView的内容栏(mContentParent)用来显示我们的布局,这只是添加的过程,还要有一个显示的过程,显示的过程就要调用ActivityThead中handleLaunchActivity中的handleResumeActivity方法了,最后会调用makeVisible方法,把这个DecorView显示出来。

    那么今天所讨论的是话题主要有,创建后的Activity的窗口是怎么添加的,WindowManagerService是如何感知Activity窗口添加的?系统中有很多应用,每个应用有多个Activity,一个Activity对应一个Window,WindowManagerService是怎么管理的?本文基于Android7.0源码。

    一、从ActivityThread#handleResumeActivity方法说起

    final void handleResumeActivity(IBinder token,
               boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
           ActivityClientRecord r = mActivities.get(token);
           ....
           if (r != null) {
               final Activity a = r.activity;
              ....
               //Activity的Window没有被添加过并且Activity没有finish和需要设置成可见
               if (r.window == null && !a.mFinished && willBeVisible) {
                  //对Actiivty成员变量window赋值
                   r.window = r.activity.getWindow();
                   //获取Window的DecorView
                   View decor = r.window.getDecorView();
                   //将DecorView设置成可见
                   decor.setVisibility(View.INVISIBLE);
                   ViewManager wm = a.getWindowManager();
                   WindowManager.LayoutParams l = r.window.getAttributes();
                   a.mDecor = decor;
                   l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                  ...
                   if (a.mVisibleFromClient && !a.mWindowAdded) {
                       a.mWindowAdded = true;
                       //调用ViewManager的方法添加decor
                       wm.addView(decor, l);
                   }
             ...
       }
    
    

    ViewManager定义 了操作View的三大方法,addView,updateViewLayout,removeView;比如ViewGroup就实现了这个接口。

    public interface ViewManager{
       /**
        * Assign the passed LayoutParams to the passed View and add the view to the window.
        * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
        * errors, such as adding a second view to a window without removing the first view.
        * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
        * secondary {@link Display} and the specified display can't be found
        * (see {@link android.app.Presentation}).
        * @param view The view to be added to this window.
        * @param params The LayoutParams to assign to view.
        */
       public void addView(View view, ViewGroup.LayoutParams params);
       public void updateViewLayout(View view, ViewGroup.LayoutParams params);
       public void removeView(View view);
    }
    

    但是这个地方,我们要看的ViewManager的实现类是WindowManagerImpl,所以调用的是WindowManagerImpl的addView方法,

    
       private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    
       @Override
       public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
           applyDefaultToken(params);
           mGlobal.addView(view, params, mDisplay, mParentWindow);
       }
    

    WindowManagerImpl紧接就把这个活交给了WindowManagerGlobal,所以要去看WindowManagerGlobal的addView方法。WindowManagerGlobal是用来管理Window的全局类,它里面维护了几个全局的列表。

    //存储所有Window所对应的View
    private final ArrayList<View> mViews = new ArrayList<View>();
    
    //存储所有Window对应的ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    
    //存储所有Window对应的布局参数
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
    
    //存储所有将要被删除的View,即Window
    private final ArraySet<View> mDyingViews = new ArraySet<View>();
    
    

    下面是addView方法,很容易懂,主要做了如下几个事情

    public void addView(View view, ViewGroup.LayoutParams params,
               Display display, Window parentWindow) {
    
         //第一件事情:检查参数是否合法,如果是子Window,还要调整布局参数
           if (view == null) {
               throw new IllegalArgumentException("view must not be null");
           }
           if (display == null) {
               throw new IllegalArgumentException("display must not be null");
           }
           if (!(params instanceof WindowManager.LayoutParams)) {
               throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
           }
    
           final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
           if (parentWindow != null) {
               parentWindow.adjustLayoutParamsForSubWindow(wparams);
           } else {
               // If there's no parent and we're running on L or above (or in the
               // system context), assume we want hardware acceleration.
               final Context context = view.getContext();
               if (context != null
                       && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                   wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
               }
           }
    
           ViewRootImpl root;
           View panelParentView = null;
    
           synchronized (mLock) {
             .....
    
            //第二件事情   创建ViewRootImpl,将View添加到列表中,这里每次都会new一个对象,所以说调用一次addView,就会有一个ViewRootImpl。
               root = new ViewRootImpl(view.getContext(), display);
    
               view.setLayoutParams(wparams);
    
               mViews.add(view);
               mRoots.add(root);
               mParams.add(wparams);
           }
    
           // do this last because it fires off messages to start doing things
           try {
    
           //第三件事情 :使用ViewRootImpl对象,调用setView
               root.setView(view, wparams, panelParentView);
           } catch (RuntimeException e) {
               // BadTokenException or InvalidDisplayException, clean up.
               synchronized (mLock) {
                   final int index = findViewLocked(view, false);
                   if (index >= 0) {
                       removeViewLocked(index, true);
                   }
               }
               throw e;
           }
       }
    
    

    ViewRootImpl是是个什么呢?

    • 简单来说,ViewRoot相当于是MVC模型中的Controller,它有以下职责:1. 负责为应用程序窗口视图创建Surface。 2. 配合WindowManagerService来管理系统的应用程序窗口。 3. 负责管理、布局和渲染应用程序窗口视图的UI。*我们在看ViewRootImpl的setView 方法。
     */
       public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
           synchronized (this) {
               if (mView == null) {
                   mView = view;
    
                   ......
                   mAdded = true;
                   int res; /* = WindowManagerImpl.ADD_OKAY; */
    
                   //1、调用requestLayout方法进行绘制
                   requestLayout();
                   ....
                   try {
                       mOrigWindowType = mWindowAttributes.type;
                       mAttachInfo.mRecomputeGlobalAttributes = true;
                       collectViewAttributes();
                       //2、调用mWindowSession添加View
                       res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                               getHostVisibility(), mDisplay.getDisplayId(),
                               mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
                   } catch (RemoteException e) {
                       mAdded = false;
                       mView = null;
                       mAttachInfo.mRootView = null;
                       mInputChannel = null;
                       mFallbackEventHandler.setView(null);
                       unscheduleTraversals();
                       setAccessibilityFocus(null, null);
                       throw new RuntimeException("Adding window failed", e);
                   } finally {
                       if (restore) {
                           attrs.restore();
                       }
                   }
                  ......
           }
       }
    
    

    看看requestLayout方法,首先检验是不是主线程在修改UI,然后 调用scheduleTraversals,在scheduleTraversals开始执行了,才会有我们熟悉的onDraw,onLayout,onMeasure。

      @Override
       public void requestLayout() {
           if (!mHandlingLayoutInLayoutRequest) {
               checkThread();
               mLayoutRequested = true;
               scheduleTraversals();http://www.cnblogs.com/GMCisMarkdownCraftsman/p/6117129.html
           }
       }
    
    
      void checkThread() {
           if (mThread != Thread.currentThread()) {
               throw new CalledFromWrongThreadException(
                       "Only the original thread that created a view hierarchy can touch its views.");
           }
       }
    

    二、APP端与WMS的IPC过程

    继续回到setView方法,重点理解这行代码的意思。

          res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                               getHostVisibility(), mDisplay.getDisplayId(),
                               mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
    

    这行代码大部分人都知道这个是与WMS做IPC通信的,但是具体是个什么过程呢?我们可以将这个过程和AMS与应用进程IPC的过程做对比。

    AMS与应用进程IPC

    ActivityManager类内部调用ActivityManagerNative的getDefault函数得到一个ActivityManagerProxy对象,AMS继承ActivityManagerNative,通过它可与AMS通信。所以说,ActivityManagerProxy(是AMS在客户端进程的一个代理,通过AMP里面的方法请求AMS。那么客户端进程在AMS的代理呢?这个代理就是ApplicationThreadProxy,如果AMS要通知Activity,那么就是使用ApplicationThreadProxy。现在看APP端和WMS的IPC。

    App与WMS的连接

    App与WMS的连接,首先会建立一个Session到WMS,之后就会通过IWindowSession接口与WMS中的Session直接通信,IWindowSession类是什么?它指向了一个实现了IWindowSession接口的Session代理对象。当应用程序进程启动第一个Activity组件的时候,它就会请求WMS服务发送一个建立连接的Binder进程间通信请求。WMS服务接收到这个请求之后,就会在内部创建一个类型为Session的Binder本地对象,并且将这个Binder本地对象返回给应用程序进程,App就会得到一个Session代理对象,并且保存在ViewRootImpl类的成员变量mWindowSession中。大概代码如下。

    public ViewRootImpl(Context context, Display display) {
         ....
           //创建了WindowSession对象
           mWindowSession = WindowManagerGlobal.getWindowSession();
          //也创建了W的对象mWindow
           mWindow = new W(this)
          ....
    }
    
    
    private static IWindowSession sWindowSession;
    
    public static IWindowSession getWindowSession() {
           synchronized (WindowManagerGlobal.class) {
               if (sWindowSession == null) {
                   try {
                       InputMethodManager imm = InputMethodManager.getInstance();
                       IWindowManager windowManager = getWindowManagerService();
               //调用WMS的openSession创建一个Session对象
                       sWindowSession = windowManager.openSession(
                               new IWindowSessionCallback.Stub() {
                                   @Override
                                   public void onAnimatorScaleChanged(float scale) {
                                       ValueAnimator.setDurationScale(scale);
                                   }
                               },
                               imm.getClient(), imm.getInputContext());
                   } catch (RemoteException e) {
                       throw e.rethrowFromSystemServer();
                   }
               }
               return sWindowSession;
           }
       }
    
     com.android.server.wm.WindowManagerService.java
    
     @Override
       public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client, IInputContext inputContext) {
           if (client == null) throw new IllegalArgumentException("null client");
           if (inputContext == null) throw new IllegalArgumentException("null inputContext");
           Session session = new Session(this, callback, client, inputContext);
           return session;
      }
    
    

    当App有了这个远端Session代理对象mWindowSession之后,所有向WMS的请求都通过mWindowSession来进行。举例来说:ViewRootImpl要添加窗口,就使用mWindowSession代理对象的addToDisplay方法调用到远端Session对象的addToDisplay方法。

    mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                               getHostVisibility(), mDisplay.getDisplayId(),
                               mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                               mAttachInfo.mOutsets, mInputChannel);
    

    远端Session对象收到这个请求后,转接给WMS。

    final class Session extends IWindowSession.Stub  implements IBinder.DeathRecipient {
      final WindowManagerService mService; 
      .....
      @Override
        public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
               int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
               Rect outOutsets, InputChannel outInputChannel) {
             return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                      outContentInsets, outStableInsets, outOutsets, outInputChannel);
        }
    }
    

    注意到addToDisplay中的参数mWindow是一个W对象,W对象是什么?ViewRootImpl::W:用于向WMS提供接口,让WMS控制App端的窗口。它可看作是个代理,很多时候会调用ViewRootImpl中的功能。

    W的声明中有两个成员变量:mViewAncestor和mWindowSession,它一头连着App端的ViewRootImpl,一头连着WMS中的Session,且实现了IWindow的接口。意味着它是App和WMS的桥梁,是WMS用来回调App端,让ViewRootImpl做事用的。

    举例来说,dispatchAppVisibility()的流程就是经过它来完成的:WMS ->ViewRootImpl::W->ViewRootHandler->handleAppVisibility()->scheduleTraversals()。

    类似这种话结构的还有,PhoneWindow::DecorView,这种设计使得系统满足最小隔离原则,Client端该用到哪些接口就暴露哪些接口。因为这种类的函数都是跑在Binder线程中的,所以其中不能调用非线程安全的函数,也不能直接操作UI控件,所以一般都是往主线程消息队列里丢一个消息让其异步执行。

    三、addWindow方法解读

    addWindow方法比较长,粗略的分成下面9点来解释。

    public int addWindow(Session session, IWindow client, int seq,
               WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
               Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
               InputChannel outInputChannel) {
    1、窗口添加权限校验
    2、检查特殊窗口attr.token和attr.type的一致性
    3、创建窗口对象
    4、调用adjustWindowParamsLw对窗口参数进行调整
    5、创建pipe,用于输入消息的传递
    6、调用窗口的attach,初始化Surface相关的变量,将窗口win放到mWindowMap中
    7、如果type == TYPE_APPLICATION_STARTING ,说明这个是启动窗口,把win赋值给token.appWindowToken.startingWindow 
    8、添加窗口到Windows列表,确定窗口的位置
    9、窗口已经添加了,调用assignLayersLocked调整一下层值
    }
    

    1、窗口添加权限校验

       int[] appOp = new int[1];
           int res = mPolicy.checkAddPermission(attrs, appOp);
           if (res != WindowManagerGlobal.ADD_OKAY) {
               return res;
           }
    

    从参数attrs中可以取出窗口的type,对type类型除了Toast窗口、屏保窗口、输入法窗口、墙纸窗口、语音交互窗口等少数几个类型窗口不需要进行权限判断外,其余的窗口都需要检查是否有android.Manifest.permission.SYSTEM_ALERT_WINDOW权限或者 android.Manifest.permission.INTERNAL_SYSTEM_WINDOW权限。

    2、检查特殊窗口attr.token和attr.type的一致性

    synchronized(mWindowMap) {
               //屏幕没有准备好,不给添加窗口
               if (!mDisplayReady) {
                   throw new IllegalStateException("Display has not been initialialized");
               }
               //获取当前窗口需要添加在哪一个屏幕上
               final DisplayContent displayContent = getDisplayContentLocked(displayId);
               if (displayContent == null) {
                   Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
                           + displayId + ".  Aborting.");
                   return WindowManagerGlobal.ADD_INVALID_DISPLAY;
               }
               //该窗口有没有权限在这个屏幕上添加
               if (!displayContent.hasAccess(session.mUid)) {
                   Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
                           + "does not have access: " + displayId + ".  Aborting.");
                   return WindowManagerGlobal.ADD_INVALID_DISPLAY;
               }
               //该窗口是否已经添加过,WMS会把添加的窗口Token保存在mWindowMap中
               if (mWindowMap.containsKey(client.asBinder())) {
                   Slog.w(TAG_WM, "Window " + client + " is already added");
                   return WindowManagerGlobal.ADD_DUPLICATE_ADD;
               }
              //如果窗口类型是子窗口
               if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
             //把这个子窗口的父窗口取出来
                   attachedWindow = windowForClientLocked(null, attrs.token, false);
                   //父窗口如果为空,就会return,因为子窗口需要依赖一个父窗口存在
                   if (attachedWindow == null) {
                       Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                             + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                   }
                   //父窗口不为null,但是父窗口的类型也是一个子窗口,也会return
                   if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
                           && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                       Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                               + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                   }
               }
               //这个type表示该窗口是要添加到虚拟设备上的,但是该设备却不是虚拟的,也要返回
               if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
                   Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display.  Aborting.");
                   return WindowManagerGlobal.ADD_PERMISSION_DENIED;
               }
             
               boolean addToken = false;
               //从mTokenMap取出WindowToken,WindowToken是窗口分组的标志,因为addWindow方法调用之前,AMS等服务会提前向WMS注册一个token到mTokenMap,所以这里一般是可以取到的,也有些情况是取不到的,下面分析。
               WindowToken token = mTokenMap.get(attrs.token);
               AppWindowToken atoken = null;
               boolean addToastWindowRequiresToken = false;
    
               if (token == null) {
                   //如果这个窗口是应用程序窗口,上面取出的token为null,需要返回
                   if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                       Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                             + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
                    //如果这个窗口是输出法窗口,上面取出的token为null,需要返回
                   if (type == TYPE_INPUT_METHOD) {
                       Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                             + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
                    //如果这个窗口是语音交互窗口,上面取出的token为null,需要返回
                   if (type == TYPE_VOICE_INTERACTION) {
                       Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                             + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
              //如果这个窗口是墙纸窗口,上面取出的token为null,需要返回
                   if (type == TYPE_WALLPAPER) {
                       Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                             + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
             //如果这个窗口是屏保窗口,上面取出的token为null,需要返回
                   if (type == TYPE_DREAM) {
                       Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                             + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
             //如果这个窗口QS_DIALOG,上面取出的token为null,需要返回
                   if (type == TYPE_QS_DIALOG) {
                       Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                             + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
             //如果这个窗口TYPE_ACCESSIBILITY_OVERLAY类型窗口,上面取出的token为null,需要返回
                   if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                       Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                               + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
                   if (type == TYPE_TOAST) {
                       // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                       if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                               attachedWindow)) {
                           Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                   + attrs.token + ".  Aborting.");
                           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                       }
                   }
                   //如果不是上面这些类型窗口,并且从mWindowMap中又不能取出对应的token的话,
              这里会隐士的创建一个第四个参数等于false,代表隐士创建,隐士创建的token在窗口销毁的时候,是不需要移除的
                   token = new WindowToken(this, attrs.token, -1, false);
                   addToken = true;
                 //上面取出的窗口不为null,并且type是应用类型的窗口
               } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
             //取出appWindowToken,appWindowToken是WindowToken的子类,一个appWindowToken一般表示这个窗口是一个Activity窗口
                   atoken = token.appWindowToken;
                   //取出atoken为null的话,不能添加,窗口的添加是必须有一个token的
                   if (atoken == null) {
                       Slog.w(TAG_WM, "Attempted to add window with non-application token "
                             + token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                   } else if (atoken.removed) {
                       Slog.w(TAG_WM, "Attempted to add window with exiting application token "
                             + token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_APP_EXITING;
                   }
                     //如果这个窗口的类型是一个启动窗口的话,atoken的firstWindowDrawn等于true,不需要添加
                   if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
                       // No need for this guy!
                       if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v(
                               TAG_WM, "**** NO NEED TO START: " + attrs.getTitle());
                       return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
                   }
               } else if (type == TYPE_INPUT_METHOD) {
                     //窗口的类型是输入法窗口,但是它的token不是输入法类型的token,也要返回,不给添加,下面几个是类似的
                   if (token.windowType != TYPE_INPUT_METHOD) {
                       Slog.w(TAG_WM, "Attempted to add input method window with bad token "
                               + attrs.token + ".  Aborting.");
                         return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
               } else if (type == TYPE_VOICE_INTERACTION) {
                   if (token.windowType != TYPE_VOICE_INTERACTION) {
                       Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "
                               + attrs.token + ".  Aborting.");
                         return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
               } else if (type == TYPE_WALLPAPER) {
                   if (token.windowType != TYPE_WALLPAPER) {
                       Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "
                               + attrs.token + ".  Aborting.");
                         return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
               } else if (type == TYPE_DREAM) {
                   if (token.windowType != TYPE_DREAM) {
                       Slog.w(TAG_WM, "Attempted to add Dream window with bad token "
                               + attrs.token + ".  Aborting.");
                         return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
               } else if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                   if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                       Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "
                               + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
               } else if (type == TYPE_TOAST) {
                   // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                   addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
                           callingUid, attachedWindow);
                   if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
                       Slog.w(TAG_WM, "Attempted to add a toast window with bad token "
                               + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
               } else if (type == TYPE_QS_DIALOG) {
                   if (token.windowType != TYPE_QS_DIALOG) {
                       Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
                               + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
               } else if (token.appWindowToken != null) {
                   Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type);
                   // It is not valid to use an app token with other system types; we will
                   // instead make a new token for it (as if null had been passed in for the token).
                   attrs.token = null;
                   token = new WindowToken(this, null, -1, false);
                   addToken = true;
               }
       .......
    }
    
    

    3、创建窗口对象

    WindowState win = new WindowState(this, session, client, token,
                       attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
    

    WindowState是WMS中真正的窗口对象

    4、调用PhoneWindowManager的adjustWindowParamsLw调整布局参数

    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
           switch (attrs.type) {
               case TYPE_SYSTEM_OVERLAY:
               case TYPE_SECURE_SYSTEM_OVERLAY:            
                   attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                           | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                   attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
                   break;
               case TYPE_STATUS_BAR:
                   if (mKeyguardHidden) {
                       attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
                       attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
                   }
                   break;
    
               case TYPE_SCREENSHOT:
                   attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
                   break;
    
               case TYPE_TOAST:
                   if (attrs.hideTimeoutMilliseconds < 0
                           || attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) {
                       attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT;
                   }
                   attrs.windowAnimations = com.android.internal.R.style.Animation_Toast;
                   break;
           }
    
           if (attrs.type != TYPE_STATUS_BAR) {
               // The status bar is the only window allowed to exhibit keyguard behavior.
               attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
           }
    
           if (ActivityManager.isHighEndGfx()) {
               if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
                   attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
               }
               final boolean forceWindowDrawsStatusBarBackground =
                       (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND)
                               != 0;
               if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
                       || forceWindowDrawsStatusBarBackground
                               && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
                   attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
               }
           }
       }
    

    这段代码主要调整状态栏等几个特殊窗口的FLAG,比如不让它获取用户焦点,不让它响应触摸消息等。

    5、创建pipe,用于输入消息的传递

          final boolean openInputChannels = (outInputChannel != null
                       && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
               if  (openInputChannels) {
                   win.openInputChannel(outInputChannel);
               }
    

    窗口需要接收事件,所以需要向InputManagerService注册InputChannel。

    6、调用窗口的attach,初始化Surface相关的变量,将窗口win放到mWindowMap中

     if (addToken) {
          mTokenMap.put(attrs.token, token);
     }
      win.attach();
      mWindowMap.put(client.asBinder(), win);
    
     void attach() {
           if (WindowManagerService.localLOGV) Slog.v(
               TAG, "Attaching " + this + " token=" + mToken
               + ", list=" + mToken.windows);
           mSession.windowAddedLocked();
       }
    
     void windowAddedLocked() {
           if (mSurfaceSession == null) {
               if (WindowManagerService.localLOGV) Slog.v(
                   TAG_WM, "First window added to " + this + ", creating SurfaceSession");
               //创建窗口所对应的SurfaceSession
               mSurfaceSession = new SurfaceSession();
               if (SHOW_TRANSACTIONS) Slog.i(
                       TAG_WM, "  NEW SURFACE SESSION " + mSurfaceSession);
               mService.mSessions.add(this);
               if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
                   mService.dispatchNewAnimatorScaleLocked(this);
               }
           }
           mNumWindow++;
       }
    
    7、如果type == TYPE_APPLICATION_STARTING ,说明这个是启动窗口,把win赋值给token.appWindowToken.startingWindow
      if (type == TYPE_APPLICATION_STARTING && token.appWindowToken != null) {
          token.appWindowToken.startingWindow = win;
           if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + token.appWindowToken
                           + " startingWindow=" + win);
      }
    
    8、添加窗口到Windows列表,确定窗口的位置
         if (type == TYPE_INPUT_METHOD) {
                   win.mGivenInsetsPending = true;
                   mInputMethodWindow = win;
                   addInputMethodWindowToListLocked(win);
                   imMayMove = false;
               } else if (type == TYPE_INPUT_METHOD_DIALOG) {
                   mInputMethodDialogs.add(win);
                   addWindowToListInOrderLocked(win, true);
                   moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));
                   imMayMove = false;
               } else {
                   addWindowToListInOrderLocked(win, true);
                   if (type == TYPE_WALLPAPER) {
                       mWallpaperControllerLocked.clearLastWallpaperTimeoutTime();
                       displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                   } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                       displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                   } else if (mWallpaperControllerLocked.isBelowWallpaperTarget(win)) {
                       // If there is currently a wallpaper being shown, and
                       // the base layer of the new window is below the current
                       // layer of the target window, then adjust the wallpaper.
                       // This is to avoid a new window being placed between the
                       // wallpaper and its target.
                       displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                   }
               }
    

    确定窗口位置,分成几种情况,如果是输入法类型的窗口,调用addInputMethodWindowToListLocked插入窗口列表,如果是输入法对话框,调用addWindowToListInOrderLocked插入窗口列表,如果是其他类型的窗口,比如应用类型的窗口,调用addWindowToListInOrderLocked插入窗口列表,现在重点关注addWindowToListInOrderLocked方法。

       private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) {
           if (DEBUG_FOCUS) Slog.d(TAG_WM, "addWindowToListInOrderLocked: win=" + win +
                   " Callers=" + Debug.getCallers(4));
           if (win.mAttachedWindow == null) {
               final WindowToken token = win.mToken;
              //tokenWindowsPos表示该WindowState对象在所属同一WindowToken的所有WindowState中的位置
               int tokenWindowsPos = 0;
               if (token.appWindowToken != null) {
                   tokenWindowsPos = addAppWindowToListLocked(win);
               } else {
                   addFreeWindowToListLocked(win);
               }
               if (addToToken) {
                   if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + token);
                    // token.windows就是描述所属该token下的所有WindowState对象
                 // 比如一个activity弹出了一个AlertDialog窗口,这两个窗口的AppWindowToken是一个
                   token.windows.add(tokenWindowsPos, win);
               }
           } else {
               addAttachedWindowToListLocked(win, addToToken);
           }
    
           final AppWindowToken appToken = win.mAppToken;
           if (appToken != null) {
               if (addToToken) {
                   appToken.addWindow(win);
               }
           }
       }
    
    

    先看窗口的父窗口mAttachedWindow是不是存在,如果不存在,说明这个窗口应用类型的窗口,反之就是一个子窗口,走else分之,使用addAttachedWindowToListLocked方法将子窗口插入窗口列表。假设不是一个子窗口,那么需要取出这个窗口的token,如果token里面的appWindowToken不为null的话,就表明这是一个Activity窗口,需要调用addAppWindowToListLocked将Activity窗口插入窗口堆栈,反之使用addFreeWindowToListLocked方法将非Activity窗口插入窗口堆栈。

     private int addAppWindowToListLocked(final WindowState win) {
       //获取WindowState要插入的屏幕对象displayContent
           final DisplayContent displayContent = win.getDisplayContent();
           if (displayContent == null) {
               // It doesn't matter this display is going away.
               return 0;
           }
           final IWindow client = win.mClient;
           final WindowToken token = win.mToken;
       //获取当前屏幕上所有窗口列表对象windows
           final WindowList windows = displayContent.getWindowList();
       //获取属于displayContent这个屏幕上,属于token所描述的WindowList,WindowList存放了token所描述的WindowState的列表
           WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent);
           int tokenWindowsPos = 0;
       //tokenWindowList不为null,说明WMS中已经有了和待插入窗口win一样的token,那么就会使用addAppWindowToTokenListLocked来插入
       //比如当一个Activity窗口上弹出一个Dialog窗口,
         //那么这个待插入的Dialog窗口的token和Activity窗口的token是一样的,都是AppWindowToken,就会走这个逻辑
           if (!tokenWindowList.isEmpty()) {
               return addAppWindowToTokenListLocked(win, token, windows, tokenWindowList);
           }
    
           // No windows from this token on this display
           if (localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window " + client.asBinder()
                   + " (token=" + token + ")");
           // Figure out where the window should go, based on the
           // order of applications.
       //pos记录源码插入的WindowState
           WindowState pos = null;
       //遍历这个屏幕中的Task
           final ArrayList<Task> tasks = displayContent.getTasks();
           int taskNdx;
           int tokenNdx = -1;
       //自顶向下遍历Task,每一个Task中取出它的tokens列表
           for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
               AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
         //自顶向下遍历tokens列表,就可以取出AppWindowToken
               for (tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
                   final AppWindowToken t = tokens.get(tokenNdx);
           //一般来说,第一个就是要匹配的AppWindowToken,因为都是自顶向下遍历的,这个时候,跳出循环
                   if (t == token) {
             //tokenNdx记录着AppWindowToken在tokens中的位置
                       --tokenNdx;
             //如果tokenNdx小于0,那么是无效的,需要取下一个Task,把它插入下一个Task的顶部 
                       if (tokenNdx < 0) {
                           --taskNdx;
                           if (taskNdx >= 0) {
                               tokenNdx = tasks.get(taskNdx).mAppTokens.size() - 1;
                           }
                       }
                       break;
                   }
    
                   // We haven't reached the token yet; if this token
                   // is not going to the bottom and has windows on this display, we can
                   // use it as an anchor for when we do reach the token.
           //如果当前tasks中第一个AppWindowToken不等于t,那么执行getTokenWindowsOnDisplay,
             //获取属于displayContent这个屏幕上,属于token所描述的WindowList
                   tokenWindowList = getTokenWindowsOnDisplay(t, displayContent);
                   if (!t.sendingToBottom && tokenWindowList.size() > 0) {
              //pos就指向当前遍历到AppWindowToken所属的最下面一个的WindowState
                       pos = tokenWindowList.get(0);
                   }
               }
               if (tokenNdx >= 0) {
                   // early exit
                   break;
               }
           }
    
           // We now know the index into the apps.  If we found
           // an app window above, that gives us the position; else
           // we need to look some more.
       //如果pos不为null,说明pos是上面指向的当前遍历到AppWindowToken所属的最下面一个的WindowState
           if (pos != null) {
               // Move behind any windows attached to this one.
               WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
               if (atoken != null) {
                   tokenWindowList =
                           getTokenWindowsOnDisplay(atoken, displayContent);
                   final int NC = tokenWindowList.size();
                   if (NC > 0) {
                       WindowState bottom = tokenWindowList.get(0);
                       if (bottom.mSubLayer < 0) {
               //判断pos的token所描述的窗口列表中最下面的窗口对象bottom的子序是否小于0,
               //如果小于0,pos需要指向这个窗口, 确保pos指向的是上面AppWindowToken的最后一个WindowState
                           pos = bottom;
                       }
                   }
               }
         //插入最下面的一个WindowState的后面
               placeWindowBefore(pos, win);
               return tokenWindowsPos;
           }
    
           // Continue looking down until we find the first
           // token that has windows on this display.
       //走到这里,说明第一个就是要匹配的AppWindowToken,taskNdx记录了是在哪一个taskNdx中,,再次自定向下遍历
           for (; taskNdx >= 0; --taskNdx) {
               AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
               for (; tokenNdx >= 0; --tokenNdx) {
           //获取顶部最顶部Task中最顶部的AppWindowToken
                   final AppWindowToken t = tokens.get(tokenNdx);
                   tokenWindowList = getTokenWindowsOnDisplay(t, displayContent);
                   final int NW = tokenWindowList.size();
                   if (NW > 0) {
             //pos尽可能的指向最顶部TASK中最顶部的AppWindowToken所描述的WindowState
                       pos = tokenWindowList.get(NW - 1);
                       break;
                   }
               }
               if (tokenNdx >= 0) {
                   // found
                   break;
               }
           }
       //pos!= null,说明找到了,检查pos描述的窗口上面有没有子窗口,如果有,需要把pos指向最顶部的子窗口
           if (pos != null) {
               // Move in front of any windows attached to this
               // one.
               WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
               if (atoken != null) {
                   final int NC = atoken.windows.size();
                   if (NC > 0) {
                       WindowState top = atoken.windows.get(NC - 1);
                       if (top.mSubLayer >= 0) {
                           pos = top;
                       }
                   }
               }
         //把win插入到pos的后面
               placeWindowAfter(pos, win);
               return tokenWindowsPos;
           }
    
           // Just search for the start of this layer.
       // 走到这里,说明该WindowState是新启动的一个activity的第一个窗口(新task的第一个WindowState),
       // 因为新启动的窗口是没有可参考的activity窗口,所以需要通过mBaseLayer去插入
           final int myLayer = win.mBaseLayer;
           int i;
           for (i = windows.size() - 1; i >= 0; --i) {
               WindowState w = windows.get(i);
               // Dock divider shares the base layer with application windows, but we want to always
               // keep it above the application windows. The sharing of the base layer is intended
               // for window animations, which need to be above the dock divider for the duration
               // of the animation.
               if (w.mBaseLayer <= myLayer && w.mAttrs.type != TYPE_DOCK_DIVIDER) {
                   break;
               }
           }
           if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
                   "Based on layer: Adding window " + win + " at " + (i + 1) + " of "
                           + windows.size());
           windows.add(i + 1, win);
           mWindowsChanged = true;
           return tokenWindowsPos;
       }
    

    上面有检查tokenWindowList,tokenWindowList不为null,说明WMS中已经有了和待插入窗口win一样的token,那么就会使用addAppWindowToTokenListLocked来插入,比如当一个Activity窗口上弹出一个Dialog窗口,那么这个待插入的Dialog窗口的token和Activity窗口的token是一样的,都是AppWindowToken,就会走addAppWindowToTokenListLocked这个逻辑。

    private int addAppWindowToTokenListLocked(WindowState win, WindowToken token,
               WindowList windows, WindowList tokenWindowList) {
           int tokenWindowsPos;
           // If this application has existing windows, we
           // simply place the new window on top of them... but
           // keep the starting window on top.
       //如果是TYPE_BASE_APPLICATION窗口,则需要插入在该AppWindowToken所有窗口的最底部
           if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
               // Base windows go behind everything else.
               WindowState lowestWindow = tokenWindowList.get(0);
               placeWindowBefore(lowestWindow, win);
               tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows);
           } else {
               AppWindowToken atoken = win.mAppToken;
               final int windowListPos = tokenWindowList.size();
               WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
           // 如果是starting window,则插入到starting window的下面
               if (atoken != null && lastWindow == atoken.startingWindow) {
                   placeWindowBefore(lastWindow, win);
                   tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows);
               } else {
                   int newIdx = findIdxBasedOnAppTokens(win);
                   //there is a window above this one associated with the same
                   //apptoken note that the window could be a floating window
                   //that was created later or a window at the top of the list of
                   //windows associated with this token.
                   if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
                           "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of "
                                   + windows.size());
                   windows.add(newIdx + 1, win);
                   if (newIdx < 0) {
                       // No window from token found on win's display.
                       tokenWindowsPos = 0;
                   } else {
                       tokenWindowsPos = indexOfWinInWindowList(
                               windows.get(newIdx), token.windows) + 1;
                   }
                   mWindowsChanged = true;
               }
           }
           return tokenWindowsPos;
       }
    
    

    窗口的插入还是比较复杂的,总结而言:
    1.非应用窗口依据mBaseLayer插入,越高越靠前,墙纸和输入法会有特殊处理,在下面还会进行调整。
    2.应用窗口参考activity的位置插入,通常应该被插入在其activity所在task的顶部或者该activity上面的activity的最后一个窗口的下面
    3.子窗口依据mSubLayer插入
    最终插入的地方有两个:DisplayContent所持有的记录该屏幕下所有窗口顺序的WindowList,以及新窗口的WindowToken所记录的所有属于它的WindowState中的WindowList列表。

    9、窗口已经添加了,调用assignLayersLocked调整一下层值
    //参数windows是窗口列表
    final void assignLayersLocked(WindowList windows) {
           if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows,
                   new RuntimeException("here").fillInStackTrace());
    
           clear();
           int curBaseLayer = 0;
           int curLayer = 0;
           boolean anyLayerChanged = false;
          //遍历窗口列表,上面通过Z序的计算公式计算出来的Z序值保存在WindowState的变量mBaseLayer
           中,这个循环的意思是,遇到同类型的窗口,后一个窗口在前一个窗口的基础上偏移5。
           for (int i = 0, windowCount = windows.size(); i < windowCount; i++) {
               final WindowState w = windows.get(i);
               boolean layerChanged = false;
    
               int oldLayer = w.mLayer;
               if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) {
                   curLayer += WINDOW_LAYER_MULTIPLIER;
               } else {
                   curBaseLayer = curLayer = w.mBaseLayer;
               }
              // 更新该窗口的mAnimLayer,也就是动画显示时,该窗口的层级
               assignAnimLayer(w, curLayer);
    
               // TODO: Preserved old behavior of code here but not sure comparing
               // oldLayer to mAnimLayer and mLayer makes sense...though the
               // worst case would be unintentionalp layer reassignment.
               if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
                   layerChanged = true;
                   anyLayerChanged = true;
               }
    
         // 将当前应用窗口的最高显示层级记录在mHighestApplicationLayer中
               if (w.mAppToken != null) {
                   mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
                           w.mWinAnimator.mAnimLayer);
               }
              //  对于分屏等相关的窗口,它们的显示层级需要再次处理
               collectSpecialWindows(w);
    
               if (layerChanged) {
                   w.scheduleAnimationIfDimming();
               }
           }
    
        // 调整特殊窗口的层级
           adjustSpecialWindows();
    
           //TODO (multidisplay): Magnification is supported only for the default display.
           if (mService.mAccessibilityController != null && anyLayerChanged
                   && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) {
               mService.mAccessibilityController.onWindowLayersChangedLocked();
           }
    
           if (DEBUG_LAYERS) logDebugLayers(windows);
       }
    
    

    addWindow流程大致上分成以上九点,基本可以了解一个窗口是怎么添加到WMS中的。



    作者:LooperJing
    链接:https://www.jianshu.com/p/ba53cf8694f1
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
  • 相关阅读:
    使用ADO.NET2.0提升数据交互性能 DataSet 数据表
    AD域控制器所有使用的端口明细列表
    链接数据库 远程事务的处理方式
    根据权限动态生成菜单栏和工具栏
    FTP服务器配置(cmd中ftp命令)
    该操作未能执行,因为 OLE DB 提供程序SQLOLEDB无法启动分布式事务
    ChartLet GDI+中发生一般性错误
    SQL SERVER 2000用户sa 登录失败的解决办法
    .net实例:网站发布后,在IIS中浏览提示:无法找到该页...404错误。
    Winform 关闭应用程序
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/9400798.html
Copyright © 2011-2022 走看看