【问题标题】:Best practise for Android dialog - how to avoid app crash for dismiss dialogAndroid 对话框的最佳实践 - 如何避免应用程序因关闭对话框而崩溃
【发布时间】:2020-11-01 03:41:56
【问题描述】:

我在我的应用程序中使用了一个 AlertDialog,当我需要进行一些网络操作时,该对话框会向用户显示一些文本并阻止用户与应用程序交互,直到网络操作完成。 当网络操作完成后,我会调用dismiss来关闭对话框,然后用户可以再次与应用程序交互。 我的代码是这样的:

//pseudo-code runs in UI/main thread
private void fun() {
    //business code here...
    
    mainExecutor.execute(new Runnable() {
        @Override
        public void run() {
            int timeLeftSec = 10;
            while (true)
            {
                final int timeLeft = timeLeftSec--;
                final String msg = "Time left ";
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        ShowDialog(msg + " " + timeLeft + " s");
                    }
                });
                
                if (CheckIfNetworkOpDone() || timeLeft < 0) {
                    mainHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            DismissProcessDialog();
                        }
                    });
                    break;
                } else {
                    try {
                        Thread.sleep(1000);
                    }catch (Exception e)
                    {
                        //
                    }
                }
            }
        }
    }
}

private void ShowDialog(String info)
{
    if (mainDialog == null || mainDialogTextView == null)
    {
        View dialogView = LayoutInflater.from(this).inflate(R.layout.processbar_dialog, null);

        if (mainDialog == null)
        {
            mainDialog = new AlertDialog.Builder(this).create();
            mainDialog.setView(dialogView);
            mainDialog.setCancelable(false);
            mainDialog.setCanceledOnTouchOutside(false);
        }
        if (mainDialogTextView == null)
        {
            mainDialogTextView = dialogView.findViewById(R.id.dialogTextView);
        }
    }

    mainDialogTextView.setText(info);
    mainDialog.show();
}

private void DismissProcessDialog()
{
    if (mainDialog != null)
    {
        try {
            Activity activity = (Activity) mainActivityContext;

            if (activity == null || activity.isDestroyed() || activity.isFinishing())
            {
                return;
            }

            Context context = ((ContextWrapper)(mainDialog.getContext())).getBaseContext();
            if (!(context instanceof Activity ))
            {
                return;
            }
            activity = (Activity) context;
            if (activity == null || activity.isDestroyed() || activity.isFinishing())
            {
                return;
            }

            if (mainDialog != null && mainDialog.isShowing())
            {
                mainDialog.dismiss();
            }
        }
        catch (Exception e)
        {
            //
        }
    }
}

    @Override
    protected void onDestroy() {
        if (mainDialog != null && mainDialog.isShowing())
        {
            mainDialog.dismiss();
        }
        mainDialog = null;

        super.onDestroy();
    }

但谷歌播放后端显示有一个崩溃日志以关闭。例外是:

java.lang.IllegalArgumentException: 
  at android.view.WindowManagerGlobal.findViewLocked (WindowManagerGlobal.java:517)
  at android.view.WindowManagerGlobal.removeView (WindowManagerGlobal.java:426)
  at android.view.WindowManagerImpl.removeViewImmediate (WindowManagerImpl.java:126)
  at android.app.Dialog.dismissDialog (Dialog.java:389)
  at android.app.-$$Lambda$oslF4K8Uk6v-6nTRoaEpCmfAptE.run (Unknown Source:2)
  at android.os.Handler.handleCallback (Handler.java:883)
  at android.os.Handler.dispatchMessage (Handler.java:100)
  at android.os.Looper.loop (Looper.java:214)
  at android.app.ActivityThread.main (ActivityThread.java:7356)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:492)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:930)

ProgressDialog 已弃用,我改用 AlertDialog。我不使用 ProgressBar 的原因是,我想在网络操作完成之前阻止用户。我发现当 ProgressBar 显示时用户仍然可以按下按钮。

关于如何修复这种崩溃或如何处理这种情况以正确显示对话框,是否有任何最佳实践?

任何建议将不胜感激。谢谢!

【问题讨论】:

  • stackoverflow.com/a/44188192/3192693 看看这个。您需要检查活动状态和对话框状态以避免此类错误。
  • @Monster Brain 感谢您的建议,但您可能不会阅读我的代码,因为我已经检查了代码中的活动和对话框状态。但它仍然崩溃。
  • 你试过dismissAllowingStateLoss()
  • 错误发生在 DismissProcessDialog 还是 onDestroy 中?
  • 我认为它应该出现在 DismissProcessDialog 中,但我无法从我没有显示任何代码的崩溃日志中获取它。我最近才添加 onDestroy,在此之前有一些崩溃日志。所以我想它应该来自 DismissProcessDialog。

标签: android android-alertdialog progressdialog android-progressbar crashlytics-android


【解决方案1】:

您是否考虑过管理用户与应用程序的交互?

有时我会使用我的 utils 类中定义的这两种方法。

public class Utils {
    public static void disableUserInteraction(Activity activity) {
        activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
    }

    public static void enableUserInteraction(Activity activity) {
        activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
    }
}

我这样称呼他们:

 Utils.disableUserInteraction(activity);

上述方法设置了适当的标志,以便在需要时忽略用户交互。

【讨论】:

    【解决方案2】:

    在我看来有两个问题,您正在从后台线程调用解除对话框。您应该始终从 UI 线程进行 UI 调用(runOnUi{...} 将对此有所帮助)。其次,您正在使用while (true) 运行循环。这是非常糟糕的做法,您应该在此处传递一个可以在循环内结束的条件。这将无限期地运行,因此目前第一遍进行网络调用。说它立即完成,第二遍试图关闭对话框。然后第三遍满足相同的条件,现在您在崩溃日志中找到了 findViewLocked,因为它试图从两个后台线程访问同一个对象。

    【讨论】:

    • 1) handler.post 将确保它在 UI 线程上运行,否则,它会立即崩溃... 2) 存在中断循环的条件,您可能在 if 中看不到条件检查。
    【解决方案3】:

    这里用progressbar代替alertdialog更合理。如果您想阻止用户在加载时与应用交互,您可以使屏幕中的所有组件(进度条除外)不可见,或者您可以保持它们可见但更改它们的可聚焦性或可点击性。

    【讨论】:

    • 我同意禁用其他组件的可点击,但是我没有找到一个好的方法来做到这一点,因为需要处理很多组件......
    • 在您的 XML 中,放置两个布局,其中一个只有一个进度条,另一个是您的主布局。当数据正在加载带有进度条的布局时,主布局可点击性设置为 false。 @托马斯
    【解决方案4】:

    当用户在您的网络操作期间离开(或旋转)您的应用程序并且您的Activity(或Fragment)被破坏(或重新创建)时,您的异常可能会发生。然后不再有对话框可以关闭并抛出异常,请参阅this answer

    虽然上述答案解决了您的问题,但在后台任务中持有对 Activity 的引用是错误的。我过去所做的是让我的Activity 实现一个回调接口,并将此回调的WeakReference 传递给后台任务。然后后台任务必须检查回调(Activity)是否还在。

    上述解决方案仍然存在问题,即您必须为轮换创建的每个新Activity(或任何其他配置更改)重新启动任务。现代方法是让Activity(或Fragment)在ViewModel 中观察LiveData。请参阅official documentation

    还建议使用DialogFragment,因为它会在重新创建时恢复。正常的AlertDialog 会在您旋转屏幕时消失(或在它被销毁后切换到您的应用)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多