【问题标题】:How can you handle dismissing a DialogFragment (compatibility lib) upon completion of an AsyncTask在完成 AsyncTask 后如何处理关闭 DialogFragment(兼容性库)
【发布时间】:2012-05-25 20:11:57
【问题描述】:

有很多关于如何在 AsyncTask 期间处理配置更改的帖子,但我没有找到一个关于 AsyncTask 完成并尝试关闭 DialogFragment(兼容性图书馆)。

这就是问题所在,如果我正在运行的 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();  
    }

}

Activity 看起来像这样:

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 版本不是有这样的吗?

【问题讨论】:

  • 看看我的blog post关于这个主题......也许它会有所帮助。

标签: java android android-fragments


【解决方案1】:

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

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

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

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

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

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

有关更多信息,请参阅this blog post

【讨论】:

  • Alex,您能否澄清一下如何实现这样的解决方案来在后台跟踪异步任务的状态以避免“commitAllowingStateLoss()”?对于 UI 影响和不连续 UI 体验的可能性,您绝对是正确的。
  • @Alex,真的已经阅读了很多你的东西有一段时间了。我遇到了几乎相同的问题,但还无法实施您的任何解决方案。请帮我看看this,谢谢。
  • 旋转后按home键也遇到同样的问题,有什么想法可以独立实现,不跟踪activity状态吗?
【解决方案2】:

要解决非法状态异常问题并从本质上实现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 已正确更新。服务也可能值得研究。

【讨论】:

  • 很高兴它有帮助。我最近遇到了同样的问题,并付出了巨大的努力来寻找解决方案。唯一找到的另外一个基本就是改兼容库源码,或者自己扩展类实现方法。
  • 这应该使用 getSupportFragmentManager() 而不是 getFragmentManager()
  • 也许这是一个旧线程,但我的问题是其中的一部分并且仍然是实际的。当我们旋转然后按主页按钮时如何处理?没有例外,对话框根本不会消失。主页按钮后无需旋转关闭即可。
【解决方案3】:

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

例如。在你的 onPause() 中:

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

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

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

【讨论】:

  • 我觉得 Android 操作系统正在让我把它变成意大利面条,其中 Activity 必须跟踪其状态更改时任务是否仍在运行,并且任务必须知道 Activity 是否可见以决定接下来会发生什么。不好。我真的很想避免这种情况。当然,有一种更直接的方法来解决这个问题。当然,我不是第一个遇到这个问题的开发人员。当 Activity 进入后台时,有人必须有一个干净的习惯来处理任务运行的场景。
  • 详细说明上面的评论,很明显,即使在后台,框架也可以正确处理 Activity.dismissDialog() 。 DialogFragment.dismissAllowingStateLoss() 即使在后台也能正常工作。但是,在 ACP 中没有 DialogFragment.dismissAllowingStateLoss()。不过,Android 人一定以前遇到过这种情况,并且必须有建议的方法来处理它。
  • 只需扩展 DialogFragment 并创建dismissAllowStateLoss(),或者覆盖我所做的dismiss。使用 getFragmentTransaction(),然后是 remove(this),然后是 commitAllowStateLoss()。工作正常。
【解决方案4】:

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

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

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

【讨论】:

  • 我有一个 onStop() 和 onStart() 的工作方法。问题出在 onPause() 上,即。我的活动不再可见。在暂停的情况下,对话框保持打开状态并且 AsyncTask 继续执行。任务完成后,它会尝试使用 Activity.dismissDialog() 关闭 ProgressDialog,即使在暂停时也能正常工作。然后它尝试通过调用 DialogFragment.dismiss() 来关闭 DialogFragment(ACP 版本)。这会导致 InvalidStateException。
  • 恐怕您可能需要重新考虑您的设计。做你想做的最简单的工作可能是关闭 onPause() 中的对话框。您也可以尝试在 onPause 中取消 AsyncTask,但这可能取决于 AsyncTask 正在做什么。 AsyncTask 类确实有一个取消方法,但这实际上并没有杀死生成的线程,它只是设置一个标志,并且 AsyncTask 中的代码必须以某种方式处理它。某些 IO 任务(例如网络流量)可能特别难以取消或中止。在这种情况下,尝试取消 AsyncTask 可能会出现问题。
  • 我还建议您考虑一项服务。然后,该服务可以将结果广播到您的活动。使用此模型,不会在您的活动之外尝试访问对话框。老实说,我有时希望他们永远不会因为这样的原因制作 AsyncTask,这实际上使处理边缘情况变得更加困难。
  • Glaucus 也许你应该将你的 cmets 移动到你的答案中。 @ChuckKrutsinger - 也许一旦 Glaucus 做到了,并且您认为他的建议是正确的,那么您应该将他的答案标记为已接受?
  • 我仍在评估答案,尚未确定任何答案。现在我在想我的问题是错误的问题。换句话说,问题不应该是我如何在不可见的情况下处理解雇,而应该让我的 AsyncTask 在 Activity 暂停时继续,我开始明白答案是否定的。我正在重新设计以在活动暂停时暂停甚至取消 AsyncTask,我可能会在 onSaveInstanceState() 中处理。
【解决方案5】:

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

总之,我要做的是:

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

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

【讨论】:

  • 使用锁阻止 Activity 的生命周期方法执行确实不是一个好的解决方案。
  • 同意。我从帖子中删除了它。指示的正确答案是正确的方法。
【解决方案6】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-16
    • 1970-01-01
    相关资源
    最近更新 更多