236

我在后台线程(我使用AsyncTask)中从互联网下载了一些数据,并在下载时显示进度对话框。方向改变,Activity 重新启动,然后我的 AsyncTask 完成 - 我想关闭进程对话框并开始一个新的 Activity。但是调用dismissDialog有时会抛出异常(可能是因为Activity被销毁了,新的Activity还没有启动)。

处理此类问题的最佳方法是什么(从后台线程更新 UI,即使用户改变方向也可以工作)?谷歌的人是否提供了一些“官方解决方案”?

4

8 回答 8

337

第 1 步:AsyncTask创建一个static嵌套类或完全独立的类,而不是内部(非静态嵌套)类。

步骤#2:通过数据成员AsyncTask持有Activity,通过构造函数和设置器设置。

步骤#3:创建时AsyncTask,将电流Activity提供给构造函数。

第 4 步:在中,将 , 与原始的、现在离开的活动分离后onRetainNonConfigurationInstance()返回。AsyncTask

第 5 步:在onCreate()中,如果getLastNonConfigurationInstance()不是null,则将其转换为您的AsyncTask类并调用您的设置器以将您的新活动与任务相关联。

第 6 步:不要引用来自doInBackground().

如果你按照上面的食谱,一切都会奏效。onProgressUpdate()并在后续onPostExecute()的开始和结束之间暂停。onRetainNonConfigurationInstance()onCreate()

这是一个演示该技术的示例项目。

另一种方法是放弃AsyncTask并将您的工作移动到IntentService. 如果要完成的工作可能很长并且无论用户在活动方面做了什么(例如,下载一个大文件),这都特别有用。您可以使用有序广播Intent让活动响应正在完成的工作(如果它仍在前台)或引发 aNotification让用户知道工作是否已完成。这是一篇关于此模式的博客文章。

于 2010-09-29T13:23:27.757 回答
13

接受的答案非常有帮助,但它没有进度对话框。

幸运的是,读者,我创建了一个非常全面且有效的 AsyncTask 示例,带有进度对话框

  1. 旋转有效,对话继续存在。
  2. 您可以通过按后退按钮取消任务和对话框(如果您想要此行为)。
  3. 它使用片段。
  4. 当设备旋转时,活动下方的片段布局会正确更改。
于 2012-09-06T15:42:35.020 回答
9

我已经辛勤工作了一周来找到解决这个困境的方法,而没有求助于编辑清单文件。此解决方案的假设是:

  1. 您总是需要使用进度对话框
  2. 一次只执行一项任务
  3. 当手机旋转并且进度对话框自动关闭时,您需要保留任务。

执行

您需要将本文底部的两个文件复制到您的工作区中。只要确保:

  1. 你所有的Activitys 应该扩展BaseActivity

  2. in onCreate(),super.onCreate()应该在你初始化任何需要被你ASyncTask的 s 访问的成员之后调用。此外,覆盖getContentViewId()以提供表单布局 ID。

  3. onCreateDialog() 像往常一样覆盖以创建由活动管理的对话框。

  4. 请参阅下面的代码以获取用于创建 AsyncTasks 的示例静态内部类。您可以将结果存储在 mResult 中以供以后访问。


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

最后,启动您的新任务:

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

就是这样!我希望这个强大的解决方案对某人有所帮助。

BaseActivity.java(自己组织导入)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}
于 2011-07-08T03:26:43.383 回答
4

谷歌的人是否提供了一些“官方解决方案”?

是的。

该解决方案更多的是一个应用程序架构建议,而不仅仅是一些代码

他们提出了3 种设计模式,允许应用程序与服务器同步工作,而不管应用程序的状态如何(即使用户完成应用程序、用户更改屏幕、应用程序终止,以及其他所有可能的状态,它都会工作)后台数据操作可能会中断,这涵盖了它)

Virgil Dobjanschi在Google I/O 2010期间的Android REST 客户端应用程序演讲中解释了该提议。时长1小时,但非常值得一看。

它的基础是将网络操作抽象为Service独立Activity于应用程序中的任何操作的。如果您正在使用数据库,一旦您使用获取的远程数据更新了本地数据库,使用ContentResolverandCursor将为您提供一个开箱即用的观察者模式,该模式可以方便地更新 UI 而无需任何额外的逻辑。任何其他操作后代码都将通过传递给的回调运行Service(我ResultReceiver为此使用了一个子类)。

不管怎样,我的解释其实很模糊,你一定要看演讲。

于 2015-08-08T16:49:31.857 回答
2

虽然 Mark (CommonsWare) 的答案确实适用于方向更改,但如果 Activity 被直接销毁(例如在电话的情况下),它会失败。

您可以通过使用应用程序对象来引用您的 ASyncTask 来处理方向更改和罕见的销毁活动事件。

这里有一个很好的问题解释和解决方案:

完全归功于 Ryan 解决了这个问题。

于 2013-03-30T05:18:11.623 回答
1

4 年后,谷歌解决了在 Activity onCreate 中调用 setRetainInstance(true) 的问题。它将在设备旋转期间保留您的活动实例。我还有一个适用于旧版 Android 的简单解决方案。

于 2014-09-26T01:54:43.313 回答
0

您应该使用活动处理程序调用所有活动操作。因此,如果你在某个线程中,你应该创建一个 Runnable 并使用 Activiti 的处理程序发布。否则,您的应用程序有时会因致命异常而崩溃。

于 2011-07-01T00:14:08.177 回答
0

这是我的解决方案:https ://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

基本上步骤是:

  1. onSaveInstanceState如果任务仍在处理中,我会使用它来保存任务。
  2. 如果onCreate它被保存,我会得到任务。
  3. onPause我丢弃ProgressDialog它是否显示。
  4. onResume我显示ProgressDialog任务是否仍在处理中。
于 2015-03-05T21:57:38.970 回答