📅  最后修改于: 2023-12-03 15:13:20.116000             🧑  作者: Mango
在开发 Android 应用时,我们经常需要使用 DialogFragment 来呈现一些弹窗。使用 DialogFragment 有一个好处是管理生命周期相对容易,同时也可以避免一些内存泄漏问题。
然而,有些情况下我们需要自定义 DialogFragment 的样式,例如让它全屏展示。这时候如果我们使用了错误的方法,在经过多次打开和关闭 DialogFragment 之后,就会导致堆栈溢出错误。这篇文章会详细介绍该问题的原因,并提供正确的解决方案。
当我们在 DialogFragment 中设置了全屏样式,例如:
@Override
public void onStart() {
super.onStart();
getDialog().getWindow().setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
}
我们会发现,每次打开和关闭 DialogFragment ,都会多往堆栈中添加一个 Fragment 实例。这样如果我们不断地打开和关闭 DialogFragment ,就会导致堆栈溢出错误,如下所示:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.dialogfragment, PID: 12345
java.lang.IllegalStateException: Fragment already added: MyDialogFragment{a4b346} (983c09e0-0874-4f1d-83c6-50e4a834bb6d) id=0x7f08003d
at androidx.fragment.app.FragmentStateManager.addMaxLifecycleFragment(FragmentStateManager.java:234)
at androidx.fragment.app.FragmentStateManager.createViewState(FragmentStateManager.java:132)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1566)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3127)
at androidx.fragment.app.FragmentManager.dispatchViewStateChange(FragmentManager.java:3135)
at androidx.fragment.app.FragmentManager.dispatchViewFragmentLifecycleChanged(FragmentManager.java:3111)
at androidx.fragment.app.Fragment.performViewLifecycleChanged(Fragment.java:3015)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:245)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1657)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3129)
at androidx.fragment.app.FragmentManager.dispatchBackStackChanged(FragmentManager.java:3269)
at androidx.fragment.app.FragmentManager.addBackStackState(FragmentManager.java:3302)
at androidx.fragment.app.FragmentManager$BackStackRecord.run(FragmentManager.java:914)
at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:2356)
at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:648)
at com.example.dialogfragment.MainActivity.openFragment(MainActivity.java:20)
at com.example.dialogfragment.MainActivity.access$000(MainActivity.java:10)
at com.example.dialogfragment.MainActivity$1.onClick(MainActivity.java:29)
at android.view.View.performClick(View.java:6597)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7078)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
问题的根本原因在于上述代码每次都会重新设置 Layout Params,导致每次打开都会再次创建一个新的 Fragment 实例,并添加到堆栈中。然而,堆栈的深度是有限制的,当超过系统允许的最大深度时,就会发生堆栈溢出错误。
为了解决该问题,我们需要在 DialogFragment 中使用自定义的样式来达到全屏效果,而不是在 onStart() 方法中设置 Layout Params。
首先,我们先定义一个全屏样式,例如:
<style name="FullScreenTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowFullscreen">true</item>
<item name="android:windowBackground">@color/colorPrimaryDark</item>
</style>
接着,在 DialogFragment 子类的 onCreateView() 方法中设置该样式,例如:
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Dialog dialog = getDialog();
if (dialog != null) {
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
dialog.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE);
dialog.getWindow().setStatusBarColor(Color.TRANSPARENT);
dialog.getWindow().setNavigationBarColor(Color.TRANSPARENT);
setStyle(DialogFragment.STYLE_NORMAL, R.style.FullScreenTheme);
}
return super.onCreateView(inflater, container, savedInstanceState);
}
注意事项:
在使用 DialogFragment 时,如果需要自定义全屏样式,务必使用上述方法,而不是在 onStart() 方法中设置 Layout Params。只有这样才能避免因堆栈溢出导致的应用崩溃。