首页 > 代码库 > 我所理解的PhoneWindow的一个作用

我所理解的PhoneWindow的一个作用

<p>转载自<a href="http://www.mamicode.com/blog.csdn.net/u013356254/article/details/55116259" target="_blank">blog.csdn.net/u013356254/article/details/55116259</a></p>

<p>android交流:364595326</p>

<ul>

<li>android中我们常见的Activity,Diaog等内部都封装了PhoneWindow对象。</li>

<li>

我们今天要探讨的是两个问题

<ul>

<li><strong>为什么系统在创建Acivity或者Dialog的时候封装了PhoneWindow对象,而我们自己写悬浮窗口的时候并没有使用PhoneWindow对象?</strong></li>

<li><strong>为什么Diaog封装了PhoneWindow对象,而PopupWindow却直接将contentView封装成PopupDecorView(FrameLayout子类),直接调用WM来添加view?</strong> </li>

</ul>

</li>

<li>

<ul>

<li>

<p>我们从Dialog的setContentView()方法说起。源码</p>

<pre><code>  public void setContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) {

    // 调用的是window的方法

    mWindow.setContentView(view, params);

    }

</code></pre>


</li>

<li>

<p>下面是PhoneWindow的setContentView()方法。</p>

<pre><code>@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);


    }


}

</code></pre>


</li>

<li>

<p>PhoneWindow.setContentView()方法的核心是,生成DecorView和mContentParent对象,之后将布局文件添加到mContentParent上面去 </p>

</li>

<li>

<p>接下来我们分析installDecor()方法</p>

<pre><code>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);

    }

}

</code></pre>


</li>

<li>

<p>我们可以看到installDecor()方法主要是创建了DecorView,和mContentParent对象。</p>

</li>

<li>

<p>下面是generateDecor(-1)源码</p>

<pre><code> protected DecorView generateDecor(int featureId) {

// 创建DecorView(FrameLayout)对象,ViewTree的根节点

return new DecorView(context, featureId, this, getAttributes());

 }

</code></pre>


</li>

<li>

<p>下面是创建mContentParent的代码</p>

<pre><code>protected ViewGroup generateLayout(DecorView decor) {

// Apply data from current theme.

// 获得window的样式

TypedArray a = getWindowStyle();

/*省略掉一些设置样式的代码/

// 下面的代码是给decorView填充孩子的

// 主要功能是根据不同的配置给decorView添加不同的布局文件(即给decorView添加不同的孩子节点)

  int layoutResource;

int features = getLocalFeatures();

if ((features &amp; (1 &lt;&lt; FEATURE_SWIPE_TO_DISMISS)) != 0) {

    layoutResource = R.layout.screen_swipe_dismiss;

} else if ((features &amp; ((1 &lt;&lt; FEATURE_LEFT_ICON) | (1 &lt;&lt; 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 &amp; ((1 &lt;&lt; FEATURE_PROGRESS) | (1 &lt;&lt; FEATURE_INDETERMINATE_PROGRESS))) != 0

        &amp;&amp; (features &amp; (1 &lt;&lt; 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 &amp; (1 &lt;&lt; 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 &amp; (1 &lt;&lt; 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 &amp; (1 &lt;&lt; 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 &amp; (1 &lt;&lt; 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;



}

</code></pre>


</li>

</ul>

</li>

<li>generateLayout(DecorView decor) 主要完成了两件事,1通过不同的配置给decorView添加不同layoutResource布局文件, 2找到id为ID<em>ANDROID</em>CONTENT的view。</li>

</ul>

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

<ul>

<li>

<p>接下来我们分析Diaog的show()方法</p>

<pre><code>public void show() {

  // 拿到PhoneWindow中的decorView对象

mDecor = mWindow.getDecorView();

// 产生布局参数

WindowManager.LayoutParams l = mWindow.getAttributes();

if ((l.softInputMode

        &amp; 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);

</code></pre>


<p>}</p>

<ul>

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

</ul>

</li>

</ul>

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

<h3>结论</h3>

<ul>

<li>PhoneWindow的一个作用是给view包裹上一层DecorView。而DecorView中的布局结构,会根据requestWindowFeature()的不同而不同(requestWindowFeature()方法,会影响DecorView的孩子节点(layoutResource布局文件))</li>

<li>我们的Activity和Dialog的布局都比较复杂,比如都可能有appbar(toolbar/actionbar)等。因此通过PhoneWindow来封装下可以更好的解耦代码</li>

<li>PopupWindow或者Toast的布局比较简单。因此没有必要包裹一层PhoneWindow。在源码中也没有发现有PhoneWindow的痕迹。</li></ul>


我所理解的PhoneWindow的一个作用