13

有很多关于如何在 AsyncTask 期间处理配置更改的帖子,但是我没有找到一个关于 AsyncTask 完成并尝试关闭 DialogFragment(兼容性库)时处于后台的应用程序(onPause())的明确解决方案。

这是问题所在,如果我有一个正在运行的 AsyncTask 应该在 onPostExecute() 中关闭 DialogFragment,如果应用程序在后台尝试关闭 DialogFragment 时,我会收到 IllegalStateException。

private static class SomeTask extends AsyncTask<Void, Void, Boolean> {

    public SomeTask(SomeActivity tActivity)
    {
        mActivity = tActivity;
    }

    private SomeActivity mActivity;

    /** Set the view during/after config change */
    public void setView(Activity tActivity) {
        mActivity tActivity;
    }

    @Override
    protected Boolean doInBackground(Void... tParams) {
        try {
          //simulate some time consuming process
          TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException ignore) {}
        return true;
    }

    @Override
    protected void onPostExecute(Boolean tRouteFound) {
        mActivity.dismissSomeDialog();  
    }

}

活动如下所示:

import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;

public class SomeActivity extends FragmentActivity {

    public void someMethod() {
        ...
        displaySomeDialog();
        new SomeTask(this).execute();
        ...
    }

    public void displaySomeDialog() {
        DialogFragment someDialog = new SomeDialogFragment();
        someDialog.show(getFragmentManager(), "dialog");
    }

    public void dismissSomeDialog() {
        SomeDialogFragment someDialog = (SomeDialogFragment) getFragmentManager().findFragmentByTag("dialog");
        someDialog.dismiss();
    }

    ....

}

工作正常,除非应用程序在 SomeTask 仍在运行时切换到后台。在这种情况下,当 SomeTask 尝试关闭SomeDialog() 时,我会收到 IllegalStateException。

05-25 16:36:02.237: E/AndroidRuntime(965): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

我看到的所有帖子似乎都指向了一些笨拙的方向,并提供了精心设计的解决方法。是不是有一些android的方式来处理这个?如果它是 Dialog 而不是 DialogFragment,那么 Activity 的 dismissDialog() 将正确处理它。如果它是一个真正的 DialogFragment 而不是来自 ACP 的一个,那么 dismissAllowingStateLoss() 将处理它。DialogFragment 的 ACP 版本不是有这样的东西吗?

4

6 回答 6

22

片段被保存为每个活动状态的一部分,因此在onSaveInstanceState()技术上调用之后执行事务是没有意义的。

在这种情况下,您绝对不想使用commitAllowingStateLoss()来避免异常。以这种情况为例:

  1. Activity 执行一个AsyncTask. 显示AsyncTask并开始DialogFragmentonPreExecute()后台线程上执行其任务。
  2. 用户单击“主页”,然后Activity停止并强制进入后台。系统认为该设备的内存非常低,因此它决定它也应该销毁该设备Activity
  3. AsyncTask完成并被onPostExecute()调用。在里面onPostExecute()你解雇了DialogFragmentusingcommitAllowingStateLoss()以避免异常。
  4. 用户导航回Activity. 将根据的保存状态FragmentManager恢复其片段的状态。Activity已保存的状态在调用后不知道任何内容,因此即使已完成,也不会记住onSaveInstanceState()关闭的请求,并且将恢复。DialogFragmentDialogFragmentAsyncTask

由于偶尔会发生此类奇怪的错误,因此commitAllowingStateLoss()避免此异常通常不是一个好主意。因为AsyncTask回调方法(响应后台线程完成其工作而调用)与Activity生命周期方法(由系统服务器进程调用以响应系统范围的外部事件,例如设备掉落睡着了,或者内存不足),处理这些情况需要你做一些额外的工作。当然,这些错误极为罕见,保护您的应用免受这些错误的影响通常不会是 Play 商店中 1 星评级和 5 星评级之间的区别......但仍然需要注意这一点。

希望这至少有一些意义。另外,请注意Dialogs 也作为Activitys 状态的一部分存在,因此尽管使用普通 oldDialog可能会避免异常,但您基本上会遇到相同的问题(即,当' 状态稍后恢复Dialog时,将不会记住取消s ) Activity.

坦率地说,最好的解决方案是避免在AsyncTask. 一个更加用户友好的解决方案是在ActionBar(例如 G+ 和 Gmail 应用程序)中显示一个不确定的进度微调器。响应异步回调而导致用户界面发生重大变化对用户体验不利,因为它是意料之外的,并且会突然将用户从他们正在做的事情中拉出来。

有关更多信息,请参阅此主题的博客文章

于 2013-08-21T15:15:51.507 回答
17

要解决非法状态异常问题并从本质上实现dismissAllowingStateLoss(),可以使用以下方法完成。

getFragmentManager().beginTransaction().remove(someDialog).commitAllowingStateLoss();

这应该可以在没有 hacky 代码的情况下解决问题。如果您有线程通过处理程序与使用 dialog.show() 的 UI 线程进行通信,则同样可以应用于 show;这也可能导致非法状态异常

getFragmentManager().beginTransaction().add(someDialog).commitAllowingStateLoss();


鉴于海报的问题,@joneswah 是正确的。如果您使用的是支持库,请替换

getFragmentManager()

getSupportFragmentManager()


对于未来的 Google 员工:@Alex Lockwood 对此解决方案提出了良好且有效的担忧。该解决方案确实解决了错误并且在大多数情况下都可以工作,但从用户体验的角度来看,暗示原始问题中的方法存在问题。

Activity 应该假设异步任务可能没有完成并且它不会执行 onPostExecute()。无论 UI 操作(即微调器,理想情况下不是对话框)启动以通知用户异步操作,都应提供在超时或跟踪状态并检查 onRestore/onResume 类型生命周期事件时自动停止以确保UI 已正确更新。服务也可能值得研究。

于 2012-11-26T23:46:01.990 回答
3

如果 onPostExecute() 要更新 UI,您应该在 onPause() 中取消 AsyncTask。当您的活动已暂停时,您不应尝试更新 UI。

例如。在您的 onPause() 中:

if (task != null) task.cancel(true);

如果您希望任务中的更改持续到下一次,则将数据/更改存储在 doInBackground() 中,然后在您的活动/片段/对话框恢复时更新 UI。

如果您不希望任务中的更改持续存在,则在 onPostExecute() 之前不要存储更改

于 2012-05-30T17:30:04.927 回答
0

当 Android 因为用户点击返回或主页按钮而停止您的应用程序时,您的对话框会为您关闭。通常诀窍是保留 onStop()/onStart() 之间的对话。因此,除非您需要做的不仅仅是关闭对话框,否则我会说不要担心。

编辑:在托管对话框的活动中,如果对话框仍然在 onStop() 中打开,您可能仍希望关闭对话框。这有助于防止内存泄漏。但这不需要从 AsyncTask 触发。

就像我上面说的,问题是当你再次点击 onStart() 并且你的 AsyncTask 还没有完成时会发生什么。您需要找到一种方法来确定并在需要时重新打开该对话框。

于 2012-05-25T20:49:08.180 回答
0

经过多次重新设计后,我最终确定了一种似乎可行的方法。但是,在 Android 开发的讨论中,我无法在其他地方找到我的设计的某些方面的文档。所以我会对任何反馈感兴趣。

总之,我必须做的是:

  • onSaveInstanceState() - 如果 SomeTask 正在运行,我使用一个简单的锁来阻止 SomeTask 在暂停期间退出 doInBackground()。
  • onResume() - 我必须放入一个 switch 语句来处理不同的恢复情况。如果启动应用程序我什么都不做,因为什么都没有暂停,如果在隐藏或配置更改后重新启动,我会释放锁定,以便保留的 SomeTask 实例可以从它停止的地方恢复,等等。
  • onDestroy() - 如果 SomeTask 正在运行,我会取消它。

我将把这个解决方案的代码片段放在我原来的帖子中。

于 2012-07-19T19:56:00.923 回答
0

getActivity().finish();DialogFragment为我工作。

于 2017-05-09T15:57:37.763 回答