我所理解的 PhoneWindow 的一个作用

2,658 阅读4分钟
原文链接: blog.csdn.net
  • 我们今天要探讨的是两个问题
    • 为什么系统在创建Acivity或者Dialog的时候封装了PhoneWindow对象,而我们自己写悬浮窗口的时候并没有使用PhoneWindow对象?
    • 为什么Diaog封装了PhoneWindow对象,而PopupWindow却直接将contentView封装成PopupDecorView(FrameLayout子类),直接调用WM来添加view?
  • 我们从Dialog的setContentView()方法说起。源码

     public void setContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) {
            // 调用的是window的方法
            mWindow.setContentView(view, params);
            }
    
    • 下面是PhoneWindow的setContentView()方法。

      @Override
      public void setContentView(int layoutResID ) {
          // mContentParent是id为ID_ANDROID_CONTENT的FrameLayout
          // 我们经常写的setContentView,这个方法,其实就是给id为ID_ANDROID_CONTENT的view添加一个孩子
          if (mContentParent == null) {
              // 下面这个方法。完成了两件事情
              // 1 创建DecorView(FrameLayout),也就是我们经常说的window中有个DecorView对象。
              // 2 给mContentParent赋值
              installDecor();
          } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
              // 如果没有5.0转场动画,remove掉之前添加的所有view
              mContentParent.removeAllViews();
          }
      
          if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
              //  5.0专场动画
              view.setLayoutParams(params);
              final Scene newScene = new Scene(mContentParent, view);
              transitionTo(newScene);
          } else {
              // 给id为ID_ANDROID_CONTENT的view添加新的孩子
               // 将layoutResID添加到ContentParent上面
                mLayoutInflater.inflate(layoutResID, mContentParent);
      
          }
      
      }
      
    • PhoneWindow.setContentView()方法的核心是,生成DecorView和mContentParent对象,之后将布局文件添加到mContentParent上面去

    • 接下来我们分析installDecor()方法

      private void installDecor() {
          mForceDecorInstall = false;
          if (mDecor == null) {
              // 产生decorView 也就是ViewTree的根节点
              mDecor = generateDecor(-1);
          } else {
              // 将decorView和window关联起来
              mDecor.setWindow(this);
          }
          if (mContentParent == null) {
              // 根据decorview产生我们的ContentParent也就是id为content的viewGroup,
               mContentParent = generateLayout(mDecor);
          }
      }
      
    • 我们可以看到installDecor()方法主要是创建了DecorView,和mContentParent对象。

    • 下面是generateDecor(-1)源码

       protected DecorView generateDecor(int featureId) {
      // 创建DecorView(FrameLayout)对象,ViewTree的根节点
      return new DecorView(context, featureId, this, getAttributes());
       }
      
    • 下面是创建mContentParent的代码

      protected ViewGroup generateLayout(DecorView decor) {
      // Apply data from current theme.
      // 获得window的样式
      TypedArray a = getWindowStyle();
      /*省略掉一些设置样式的代码/
      // 下面的代码是给decorView填充孩子的
      // 主要功能是根据不同的配置给decorView添加不同的布局文件(即给decorView添加不同的孩子节点)
        int layoutResource;
      int features = getLocalFeatures();
      if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
          layoutResource = R.layout.screen_swipe_dismiss;
      } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
          if (mIsFloating) {
              TypedValue res = new TypedValue();
              getContext().getTheme().resolveAttribute(
                      R.attr.dialogTitleIconsDecorLayout, res, true);
              layoutResource = res.resourceId;
          } else {
              layoutResource = R.layout.screen_title_icons;
          }
           removeFeature(FEATURE_ACTION_BAR);
      } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
              && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
          // Special case for a window with only a progress bar (and title).
          // XXX Need to have a no-title version of embedded windows.
          layoutResource = R.layout.screen_progress;
          // System.out.println("Progress!");
      } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
          // Special case for a window with a custom title.
          // If the window is floating, we need a dialog layout
          if (mIsFloating) {
              TypedValue res = new TypedValue();
              getContext().getTheme().resolveAttribute(
                      R.attr.dialogCustomTitleDecorLayout, res, true);
              layoutResource = res.resourceId;
          } else {
              layoutResource = R.layout.screen_custom_title;
          }
          // XXX Remove this once action bar supports these features.
          removeFeature(FEATURE_ACTION_BAR);
              // 设置notitle的布局文件
      } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
          // Dialog样式的
           if (mIsFloating) {
              TypedValue res = new TypedValue();
              getContext().getTheme().resolveAttribute(
                      R.attr.dialogTitleDecorLayout, res, true);
              layoutResource = res.resourceId;
          } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
              layoutResource = a.getResourceId(
                      R.styleable.Window_windowActionBarFullscreenDecorLayout,
                      R.layout.screen_action_bar);
          } else {
              layoutResource = R.layout.screen_title;
          }
      } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
          layoutResource = R.layout.screen_simple_overlay_action_mode;
      } else {
          // Embedded, so no decoration is needed.
          layoutResource = R.layout.screen_simple;
          // System.out.println("Simple!");
      }
      
      mDecor.startChanging();
      // 下面的方法是将找到的不同的布局文件,添加给decorView.
      // 这里也说明了,我们经常写的requestWindowFeature(Window.FEATURE_NO_TITLE)代码为什么一定放在setContentView之前。
      // 因为系统会根据配置找不同的布局文件,而一旦添加了布局文件,就没有办法再移除title了。因此会抛出异常
      
      mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
      // 接下来是给赋值,这里直接调用的findViewById(),其实内部会调用decorView.findViewById();
       ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      return contentParent;
      
      
      }
      
  • generateLayout(DecorView decor) 主要完成了两件事,1通过不同的配置给decorView添加不同layoutResource布局文件, 2找到id为ID_ANDROID_CONTENT的view。

分析完setContentView代码,我们发现setContentView.其实是将view添加到PhoneWindow的成员变量DecorView中的id为ID_CONTENT_ANDROID的View节点上。还发现了DecorView的孩子节点会根据我们的requestWindowFeature()的不同,添加不同的layoutResource布局文件,而这些不同的layoutResource布局文件都是一个id为ID_ANDROID_CONTENT的孩子。

  • 接下来我们分析Diaog的show()方法

    public void show() {
      // 拿到PhoneWindow中的decorView对象
    mDecor = mWindow.getDecorView();
    // 产生布局参数
    WindowManager.LayoutParams l = mWindow.getAttributes();
    if ((l.softInputMode
            & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
        WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
        nl.copyFrom(l);
        nl.softInputMode |=
                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
        l = nl;
    }
    // wm添加decorView
    
    mWindowManager.addView(mDecor, l);
    

    }

    • 我们发现,写到最后show()方法其实就是将decorView添加到wm中

而我们写悬浮窗口的时候,直接用wm添加view。通过以上分析我们可以得出以下结论

结论

  • PhoneWindow的一个作用是给view包裹上一层DecorView。而DecorView中的布局结构,会根据requestWindowFeature()的不同而不同(requestWindowFeature()方法,会影响DecorView的孩子节点(layoutResource布局文件))
  • 我们的Activity和Dialog的布局都比较复杂,比如都可能有appbar(toolbar/actionbar)等。因此通过PhoneWindow来封装下可以更好的解耦代码
  • PopupWindow或者Toast的布局比较简单。因此没有必要包裹一层PhoneWindow。在源码中也没有发现有PhoneWindow的痕迹。