requestFeature() must be called before adding content问题的解析

    过完年,又重新拾起之前编写的android的项目,希望美化一下界面,于是自定义了一些AlertDialog,然后就遇到了requestFeature() must be called before adding content这个AndroidRuntimeException。上网百度一下,发现大多数文章只是记录一下错误和改正方法,并没有给出内部的原理,于是自己总结一个相关的内部原理。

    首先,列举一下会导致这个异常的情况

  1. 自定义AlertDialog时,AlertDialog.show()和AlertDialog.setContentView方法调用顺序有误会导致异常,例子如下
            final AlertDialog dialog = new AlertDialog.Builder(SettingActivity.this).create();
            dialog.setContentView(R.layout.dialog2);
            dialog.show();//错误代码
            final AlertDialog dialog = new AlertDialog.Builder(SettingActivity.this).create();
            dialog.show();
            dialog..setContentView(R.layout.dialog2);//正确


  2. 在Activity的onCreate方法中调用requestWindowFeature方法的位置不当可能会导致异常,例子如下(引自点击打开链接)
     protected void onCreate(Bundle savedInstanceState) {
             this.setContentView(R.layout.setup_info_page);
             this.requestWindowFeature(Window.FEATURE_NO_TITLE);//错误代码
         super.onCreate(savedInstanceState);
         }

     protected void onCreate(Bundle savedInstanceState) {
             this.requestWindowFeature(Window.FEATURE_NO_TITLE);//正确
             this.setContentView(R.layout.setup_info_page);
    
         super.onCreate(savedInstanceState);
         }
    

    其实上述的错误都是方法调用顺序的问题,但是为什么会出现这种情况呢?我们下面主要来看第一种情况内部原理。

    先按照错误的代码的方法调用顺序来看,setContentView(),我们来看看这个方法内部的原理。

  1.     AlertDialog是Dialog的子类,继承了Dialog的setContentView方法,而Dialog类中的方法如下
        public void setContentView(int layoutResID) {
            mWindow.setContentView(layoutResID);
        }

  2. mWindow是类型是Window这个抽象类,其真正的类型为PhoneWindow,而PhoneWindow的setContentView方法如下
        public void setContentView(int layoutResID) {
            if (mContentParent == null) { 
                installDecor();    //mContentParent为根View,当其为null既第一次调用setContentView时,会执行这个方法
            } else {
                mContentParent.removeAllViews();   //not the fist times,so remove last views
            }
            mLayoutInflater.inflate(layoutResID, mContentParent); //create the view from layout.xml
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
        }
    关于WInodw,PhoneWindow的具体介绍请参考点击打开链接
  3. installDecor也是PhoneWindow的成员函数
    private void installDecor() {
            if (mDecor == null) {
               //省略
            }
            //根view的生成
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor); //当mContentParent为null的时候才会调用了generateLayout方法,这一点很关键
              //下方代码省略
             }
      }
             
  4. 而在protected ViewGroup generateLayout(DecorView decor)中便会调用requestFeature这个方法,但是此时并不会出现异常,而是生成了mContentView。然后顺利执行完整个setContentView的方法

    但是当执行Dialog.show()方法的时候便会出现异常,让我们一步一步的来分析源码

  1. AlertDialog.show()方法继承于Dialog这个父类,show方法代码如下
    if (mShowing) {
           //上方代码省略
            mCanceled = false;        
            if (!mCreated) {  //mCreate在初始化设置为false
                dispatchOnCreate(null); //执行此方法
            }
         //后方代码省略
    }
  2. 而在dispatchOnCreate()方法中调用了AlertDialog的onCreate方法,代码如下
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mAlert.installContent();
        }
  3. 其中mAlert的类型为AlertController,其installContent方法会直接调用requestFeature方法,mWindow依旧是PhoneWIndow的实例
    public void installContent() {
            /* We use a custom title so never request a window title */
            //TODO: 错误之源
            mWindow.requestFeature(Window.FEATURE_NO_TITLE);
            
            if (mView == null || !canTextInput(mView)) {
                mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                        WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
            }
            mWindow.setContentView(mAlertDialogLayout);
            setupView();
        }
  4. requestFeature()方法的内部有一个关于mContentParent是否为null的布尔判断,如果不为null,则引发异常,如果为null,则顺利执行。

    看完两个成员方法的执行过程,我们再来分析一下二者调用先后顺序的不同,为什么会导致不同的结果

  1. 当先调用show方法,在调用setContentView方法时,虽然show的方法执行过程中mContentParent和mDecor都会被初始化,但是请注意setContentView的第三步时会对mContentView是否为null进行判断,如果不为null,便不会再执行到第四步,就不会调用requestFeature。所以按照这个顺序调用方法不会引发异常
  2. 当先调用setContentView方法,在调用show方法时,show方法执行之前mContentParent已经初始化了,在其第三步中直接调用了requestFeature,从而引发了异常。

    还有一个问题大家可能会问,show方法和setContentView方法执行过程中涉及的是不同类型的对象啊,为什么mContentParent会共享,其实关键在于mWindow这个对象,这个对象是共享的,mContentView和mDecor都是mWindow的成员变量。

    至于出现异常的第二种场景,其原理应该相似,这里就不多说啦。

   

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值