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);
...
...
}

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

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

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

而在最新的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出现了崩溃
总结
- 查看源码的时候一定要注意多个版本的问题
- fragment中尽量不要使用
requestFeature
去对window
做处理,应该使用style
或者放到activity
中处理