Window的工作原理

  • Window的概念:

    • 官方的解释:

      Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.

      The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.

      大概的意思是,Window是关于顶级窗体的外观、行为的抽象基类,它被作为顶层的View被添加到WindowManager当中。它还规定了手机界面的标准规范,比如:背景、标题栏等等。

      它只有一个实现类PhoneWindow,当你需要一个Window的时候就需要实例化PhoneWindow对象。

    • 关于官方解释的困惑:

      看了上述的解释,如果会感觉的一些困惑!可能是因为平常我们很少接触到Window,并且理解的top-level-view(顶层视图)就是setContentView()中的contentView或者是DecorView。

      有上述感觉原因是没有了解Window的整个工作机制,所以对Window的认识不是很全面;下面我们首先会讲一些关于Window的结论,然后再一步步探索它的工作流程。

      总之,暂时就把Window理解成是View的根容器。

  • Window的分类:

    Window是分层的,每个Window都有对应的z-ordered,层级范围数大的会覆盖在层级范围小的Window上面。

    • 应用类Window

      应用类的Window一般指的是Activity对应的Window,它的层级范围数为:(1~99)。

    • 子Window

      子Window必须要依附在特定的父Window之上,它的层级范围为:(1000~1999)。

    • 系统Window

      系统Window一般需要声明权限才能创建,它的层级范围为:(2000~2999)。比如常见的系统Window有Toast、系统状态栏。

  • 通过WindowManger添加Window:

    分析Window的工作机制之前,还是先要了解下Window是如何被添加的?需要添加Window首先需要使用到WindowManager,下面是具体的使用代码。

      // 将一个Button添加到窗体上面屏幕坐标为(100,300)的位置上。
      WindowManager manager = getWindowManager();
      Button button = new Button(this);
      button.setText("button");
      WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
              WindowManager.LayoutParams.WRAP_CONTENT,
              WindowManager.LayoutParams.WRAP_CONTENT,
              0, 0, PixelFormat.TRANSPARENT);
      layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
              | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
              | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
      layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
      layoutParams.x = 100;
      layoutParams.y = 300;
      manager.addView(button, layoutParams);
    

    WindowManger顾名思义,它就是Window的管理器(负责Window的添加View,更新View,删除View等操作)同时也是外界(可以理解为用户进程)访问Window的入口,由于Window的具体实现过程位于WindowManagerService(下面简称WMS)这个系统远程服务当中,为了与WMS进行IPC通信,Android给我们提供了WindowManager去获取WMS的Binder引用,方便用户进程与WMS的交互。

    (ps:如果不懂Binder进程通信机制的同学,请务必把Binder机制的工作流程弄清楚。)

    • WindowManager.LayoutParams.flags属性的常见参数:

      • FLAG_NOT_FOCUSABLE:

        表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启动FLAG_NOT_TOUCH_MODAL,最终事件会传递给下层具有焦点的Window处理。

      • FLAG_NOT_TOUCH_MODAL:

        该模式下,系统会将Window以外的单击事件传递给下层的Window,Window区域范围内的事件会自己处理。一般来说都需要开启这个标记,否则其他的Window将无法收到单击事件。

      • FLAG_SHOW_WHEN_LOCKED:

        开启此模式可以让Window显示在锁屏界面上面。

    • WindowManager.LayoutParams.type属性的常见参数:

      前面说的Window的分类,就是通过type属性来控制的。

      • TYPE_SYSTEM_ERROR

        设置Window的类型为系统层级,注意:使用此参数时需要声明权限:android.permission.SYSTEM_ALERT_WINDOWS。

  • Window的内部工作机制:

《Window的工作原理》 Window的内部工作机制.png

Window其实是一个抽象的概念,每一个Window都对应一个View和一个ViewRootImpl,Window和View的联系是通过ViewRootImpl来完成的。为什么这么说呢,因为这是从WindowManager定义的三个方法(addView()、updateView()、removeView())都是针对View看出来的,所以接下会从这三个方法进行分析。

《Window的工作原理》 WindowManager的源码分析流程.png

  • Window的添加过程–WindowManager.addView()

    通过分析源码Window.setWindowManager()我们发现最后执行添加View的是WindowManagerGlobal。

    • 检测参数是否合法,如果是子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);
            }
      
    • 创建ViewRootImpl对象并且View添加到列表中。

            root = new ViewRootImpl(view.getContext(), display);
      
            view.setLayoutParams(wparams);
      
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
      
    • 通过ViewRootImpl的setView()方法来更新界面并完成Window的添加。

            requestLayout();
      
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, 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);
            }
      
  • Window的删除过程–WindowManager.removeView():

    删除过程与添加过程一样都是WindowManagerGlobal来完成的。

    • 查找待删除的View的索引

            final int index = mViews.indexOf(view);
            if (required && index < 0) {
                throw new IllegalArgumentException("View=" + view + " not attached to window manager");
            }
            return index;
      
    • 删除View会有两种方式(同步删除、异步删除),同步删除将会立即执行删除动作,并且执行dispatchDetachedFromWindow()

              if (mAdded) {
                    dispatchDetachedFromWindow();
                }
      
                if (mAdded && !mFirst) {
                    destroyHardwareRenderer();
      
                    if (mView != null) {
                        int viewVisibility = mView.getVisibility();
                        boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                        if (mWindowAttributesChanged || viewVisibilityChanged) {
                            // If layout params have been changed, first give them
                            // to the window manager to make sure it has the correct
                            // animation info.
                            try {
                                if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                        & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                    mWindowSession.finishDrawing(mWindow);
                                }
                            } catch (RemoteException e) {
                            }
                        }
      
                        mSurface.release();
                    }
                }
      
                mAdded = false;
          
            WindowManagerGlobal.getInstance().doRemoveView(this);
      
    • 同步删除只是发送了一个请求删除的消息后就返回了,此时View并没有完成删除操作,会添加到mDyingViews当中。

            if (immediate && !mIsInTraversal) {
                doDie();
                return false;
            }
      
            if (!mIsDrawing) {
                destroyHardwareRenderer();
            } else {
                Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
                        "  window=" + this + ", title=" + mWindowAttributes.getTitle());
            }
            mHandler.sendEmptyMessage(MSG_DIE);
      
  • Window的更新过程–WindowManager.updateViewLayout():

              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;
      
              view.setLayoutParams(wparams);
      
              synchronized (mLock) {
                  int index = findViewLocked(view, true);
                  ViewRootImpl root = mRoots.get(index);
                  mParams.remove(index);
                  mParams.add(index, wparams);
                  root.setLayoutParams(wparams, false);
              }
    

    view重新设置布局参数setLayoutParams(),删除旧的布局参数,添加新的布局参数,最后通过IWindowSession进行远程删除。

    原文作者:allenjones_23
    原文地址: https://www.jianshu.com/p/258eccdcb1e1
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞