📜  android dialogfragment 全屏堆栈溢出 - Java (1)

📅  最后修改于: 2023-12-03 15:13:20.116000             🧑  作者: Mango

Android DialogFragment 全屏堆栈溢出

在开发 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);
}

注意事项:

  1. 我们使用 DialogFragment.STYLE_NORMAL 来设置样式,这样 DialogFragment 就不会被系统自动添加到堆栈中;
  2. 我们在 onCreateView() 方法中设置样式,这样只会在创建 View 时设置样式,而不是每次打开 DialogFragment 时都重新设置样式。
结论

在使用 DialogFragment 时,如果需要自定义全屏样式,务必使用上述方法,而不是在 onStart() 方法中设置 Layout Params。只有这样才能避免因堆栈溢出导致的应用崩溃。