【问题标题】:Background task, progress dialog, orientation change - is there any 100% working solution?后台任务、进度对话框、方向更改 - 是否有任何 100% 可行的解决方案?
【发布时间】:2011-04-18 19:07:45
【问题描述】:

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

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

【问题讨论】:

标签: android orientation android-orientation


【解决方案1】:

第 1 步:将您的 AsyncTask 设为 static 嵌套类,或完全独立的类,而不是内部(非静态嵌套)类。

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

第 3 步:创建 AsyncTask 时,将当前的 Activity 提供给构造函数。

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

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

第 6 步:不要引用来自 doInBackground() 的活动数据成员。

如果你按照上面的方法,一切都会奏效。 onProgressUpdate()onPostExecute()onRetainNonConfigurationInstance() 的开头和随后的 onCreate() 的结尾之间暂停。

Here is a sample project 演示该技术。

另一种方法是放弃AsyncTask 并将您的工作转移到IntentService。如果要完成的工作可能很长并且无论用户在活动方面做什么(例如,下载大文件)都应该继续进行,这将特别有用。您可以使用有序广播Intent 让活动响应正在完成的工作(如果它仍在前台)或引发Notification 让用户知道工作是否已经完成。 Here is a blog post 更多关于此模式的信息。

【讨论】:

  • 非常感谢您对这个常见问题的出色回答!为了彻底起见,您可能会在第 4 步中添加我们必须分离(设置为 null)AsyncTask 中的活动。不过,这在示例项目中得到了很好的说明。
  • 但是如果我需要访问 Activity 的成员怎么办?
  • @Andrew:创建一个静态内部类或包含多个对象的东西,然后返回它。
  • onRetainNonConfigurationInstance() 已弃用,建议的替代方法是使用 setRetainInstance(),但它不返回对象。是否可以使用setRetainInstance() 在配置更改时处理asyncTask
  • @SYLARRR:当然。让Fragment 持有AsyncTask。让Fragment 自己调用setRetainInstance(true)。让AsyncTask 只与Fragment 交谈。现在,在配置更改时,Fragment 不会被销毁和重新创建(即使活动是),因此 AsyncTask 在配置更改期间保留。
【解决方案2】:

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

幸运的是,读者,我创建了一个extremely comprehensive and working example of an AsyncTask with a progress dialog

  1. 旋转有效,对话框继续存在。
  2. 您可以通过按返回按钮取消任务和对话框(如果您想要这种行为)。
  3. 它使用片段。
  4. 当设备旋转时,Activity 下方的 Fragment 布局会正确更改。

【讨论】:

  • 接受的答案是关于静态类(不是成员)。这些是必要的以避免 AsyncTask 有一个(隐藏的)指向外部类实例的指针,这会在销毁活动时成为内存泄漏。
  • 是的,不知道为什么我把它放在静态成员身上,因为我实际上也使用了它们......很奇怪。编辑答案。
  • 能否更新您的链接?我真的需要这个。
  • 抱歉,我的网站还没有恢复——我会尽快恢复的!但同时它与此答案中的代码基本相同:stackoverflow.com/questions/8417885/…
  • 链接是假的;只会导致一个无用的索引,没有指示代码在哪里。
【解决方案3】:

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

  1. 您始终需要使用进度对话框
  2. 一次只执行一项任务
  3. 您需要在手机旋转时保持任务并且自动关闭进度对话框。

实施

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

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

  2. onCreate() 中,super.onCreate() 应在您初始化需要由您的ASyncTasks 访问的任何成员之后调用。此外,覆盖 getContentViewId() 以提供表单布局 ID。

  3. 覆盖 onCreateDialog() like usual 以创建由 Activity 管理的对话框。

  4. 请参阅下面的代码以获取用于创建 AsyncTask 的示例静态内部类。您可以将结果存储在 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;
    }
}

【讨论】:

    【解决方案4】:

    Google 有人提供了一些“官方解决方案”吗?

    是的。

    解决方案更像是一个应用程序架构提案,而不是只是一些代码

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

    Virgil Dobjanschi 在 Google I/O 2010 期间的 Android REST client applications 演讲中解释了该提案。时长 1 小时,但非常值得一看。

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

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

    【讨论】:

      【解决方案5】:

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

      您可以通过使用 Application 对象来引用您的 ASyncTask 来处理方向更改和罕见的销毁 Activity 事件。

      对问题和解决方案有很好的解释here

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 人们观察者的问题是因为 Android 在旋转、键盘扩展和其他事件时破坏了一个活动类,但异步任务仍然保留对被破坏实例的引用并尝试将其用于 UI 更新。您可以指示 Android 不要在清单中或务实地破坏活动。在这种情况下,异步任务引用仍然有效并且没有观察到问题。由于旋转可能需要一些额外的工作,例如重新加载视图等,因此 Google 不建议保留活动。所以你决定。
        • 谢谢,我知道情况,但不知道 setRetainInstance()。我不明白的是您声称 Google 使用它来解决问题中提出的问题。可以链接信息来源吗?谢谢。
        • onRetainNonConfigurationInstance() 这个函数被调用纯粹是为了优化,你不能依赖它被调用。 developer.android.com/reference/android/app/…
        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

          基本上步骤是:

          1. 如果任务还在,我使用onSaveInstanceState 保存任务 处理。
          2. onCreate 中,如果任务被保存,我会得到它。
          3. onPause 中,如果显示ProgressDialog,我会丢弃它。
          4. onResume 中,如果任务还在,我会显示ProgressDialog 处理。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2022-11-17
            • 1970-01-01
            • 2016-05-10
            • 1970-01-01
            • 2019-06-30
            • 1970-01-01
            • 2020-09-23
            相关资源
            最近更新 更多