android遇坑-requestFeature() must be called before adding conten

问题现象:

在调试app的时候点击弹窗DialogFragment突然崩溃,而且诡异的是线上又没有出现,代码也没改动

  android.util.AndroidRuntimeException: requestFeature() must be called before adding content

问题分析:

崩溃日志指向下面的requestFeature

   @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        Window window = getDialog().getWindow();
        WindowManager.LayoutParams windowParams = window.getAttributes();
        window.requestFeature(Window.FEATURE_NO_TITLE);
        window.setWindowAnimations(R.style.dialog_theme_intro_style);
        super.onActivityCreated(savedInstanceState);
        }
    }

通过源码看到异常抛出原因如下(api 28) :

    //PhoneWindow.class
    @Override
    public boolean requestFeature(int featureId) {
        if (mContentParentExplicitlySet) {
            throw new AndroidRuntimeException("requestFeature() must be called before adding content");
        }
     ...
     ...
     ...
   }

而将mContentParentExplicitlySet设置为true的地方有两处

    //PhoneWindow.class
   @Override
    public void setContentView(int layoutResID) {
         ...
         ...
        mContentParentExplicitlySet = true;
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        ...
        ...
        mContentParentExplicitlySet = true;
    }

那么问题很明显了,就是在调用requestFeature之前,setContentView先调用了就会发生崩溃


那么回到前面的问题出现的位置,super.onActivityCreated内部会调用setContentView,那我已经在他前面了啊,为什么还会出错,说明还有其他地方调用了,那搜索一下DialogFragment中调用的setContentView地方,发现只有一个地方调用,就是onActivityCreated

    //DialogFragment

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        ...
        ...
        mDialog.setContentView(view);
        ...
        ...
    }
image.png

问题解决:

通过AndroidStudio中的调试工具profiler,发现setContentView的调用链如下:

image.png

然后我搜索了DialogFragment中的onChange方法,发现并没有这个方法

image.png

然后通过搜索DialogFragment中的成员变量dialog引用发现,项目存在多个fragment的版本


image.png

而在最新的1.3.0-rc01版本中发现了如下代码:

 private Observer<LifecycleOwner> mObserver = new Observer<LifecycleOwner>() {
        @SuppressLint("SyntheticAccessor")
        @Override
        public void onChanged(LifecycleOwner lifecycleOwner) {
            if (lifecycleOwner != null && mShowsDialog) {
                View view = requireView();
                if (view.getParent() != null) {
                    throw new IllegalStateException(
                            "DialogFragment can not be attached to a container view");
                }
                if (mDialog != null) {
                    if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                        Log.d(TAG, "DialogFragment " + this + " setting the content view on "
                                + mDialog);
                    }
                    mDialog.setContentView(view);
                }
            }
        }
    };

  @MainThread
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        getViewLifecycleOwnerLiveData().observeForever(mObserver);
        if (!mShownByMe) {
            // If not explicitly shown through our API, take this as an
            // indication that the dialog is no longer dismissed.
            mDismissed = false;
        }
    }

最后通过调试验证证实,就是这里提前触发调用了setContentView,导致后面调用requestFeature发生崩溃,而因为存在多个版本的fragment,线上使用的和debug使用的版本不一样,所以线上没出现崩溃,debug出现了崩溃

总结

  1. 查看源码的时候一定要注意多个版本的问题
  2. fragment中尽量不要使用requestFeature去对window做处理,应该使用style或者放到activity中处理
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 第14章 使用Kotlin 进行 Android 开发(1) 根据Realm Report (2017-Q4,ht...
    光剑书架上的书阅读 1,163评论 4 14
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,859评论 2 59
  • 目录介绍 1.0.0.1 说下Activity的生命周期?屏幕旋转时生命周期?异常条件会调用什么方法? 1.0.0...
    杨充211阅读 620评论 0 2
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,588评论 28 53
  • 信任包括信任自己和信任他人 很多时候,很多事情,失败、遗憾、错过,源于不自信,不信任他人 觉得自己做不成,别人做不...
    吴氵晃阅读 6,220评论 4 8