Android 对话框的最佳实践 - 如何避免应用程序因关闭对话框而崩溃

问题描述 投票:0回答:5

我在我的应用程序中使用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();
    }

但是 google play 后端显示有崩溃日志可供忽略。例外的是:

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的原因是,我想阻止用户直到网络操作完成。我发现当进度条显示时用户仍然可以按下按钮。

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

如有任何建议,我们将不胜感激。谢谢!

android android-alertdialog progressdialog android-progressbar crashlytics-android
5个回答
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);

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


0
投票

对我来说,似乎有两个问题,您正在从后台线程调用关闭对话框。您应该始终从 UI 线程进行 UI 调用(

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


0
投票

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


0
投票

当用户在网络操作期间离开(或轮换)您的应用程序并且您的

Activity
(或
Fragment
)被破坏(或重新创建)时,可能会发生异常。然后就不再有对话框可以关闭,并且抛出异常,请参阅这个答案

虽然上述答案解决了您的问题,但它错误地在后台任务中保留对

Activity
的引用。我过去所做的就是让我的
Activity
实现回调接口,并将此回调的
WeakReference
传递给后台任务。然后后台任务必须检查回调(
Activity
)是否仍然存在。

上述解决方案仍然存在问题,您必须为轮换(或任何其他配置更改)创建的每个新

Activity
重新启动任务。现代的方法是让
Activity
(或
Fragment
)观察
LiveData
中的
ViewModel
。请参阅官方文档

还建议使用

DialogFragment
,因为它会在重新创建时恢复。当您旋转屏幕(或在其被破坏后切换到您的应用程序)时,正常的
AlertDialog
会消失。


0
投票

在我的例子中,当 firebase 数据成功获取时,我显示 onCreate 对话框并关闭,但是当我获取数据并运行dialog.dismiss命令时,应用程序崩溃。我按照各种方法来解决这个问题,但应用程序仍然崩溃。我通过 int 值更改逻辑修复了它。我向 int 值添加了一个观察者,观察该值的变化。最初这个值是 0,从 firebase 获取数据后它会改变。当此值更改时,dialog.dismiss() 命令起作用。这是我的模型课 -

IntOserverModel.class

@Override
protected void onResume() {
    super.onResume();

    Dialog dialog = new Dialog(this);
    dialog.setContentView(R.layout.dialog_processing);
    ProgressBar progressBar = dialog.findViewById(R.id.progress_bar);
    dialog.setCancelable(false);
    dialog.show();

    viewModel = new ViewModelProvider(this).get(IntOserverModel.class);
    viewModel.getIntValue().observe(this, newValue -> dialog.dismiss());

    reference.child(UID).addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            Map map = (Map) snapshot.getValue();
            viewModel.setIntValue(Integer.parseInt(map.get("Coins").toString()));
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {
        }
    });

}

这是我的对话类 -

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">

<ProgressBar
    android:id="@+id/progress_bar"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:max="100"
    android:progressDrawable="@drawable/circular_progress" />

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:fontFamily="@font/average_sans"
    android:paddingHorizontal="10dp"
    android:text="Please wait..."
    android:textAlignment="center"
    android:textColor="@color/white"
    android:textSize="20sp"
    android:textStyle="bold" />

如果你喜欢这个。请喜欢我的评论

© www.soinside.com 2019 - 2024. All rights reserved.