素心若雪-146号消零专题

android遇坑-requestFeature() must

2021-07-13  本文已影响0人  return_toLife

问题现象:

在调试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中处理
上一篇下一篇

猜你喜欢

热点阅读