后台任务,进度对话框,方向更改-是否有100%可行的解决方案?


235

我在后台线程中从Internet下载了一些数据(我使用AsyncTask),并在下载时显示了进度对话框。方向更改,Activity重新启动,然后我的AsyncTask完成-我想关闭进度对话框并启动新的Activity。但是,调用dismissDialog有时会引发异常(可能是因为Activity已被破坏并且尚未启动新的Activity)。

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


4
我有关此主题的博客文章可能会有所帮助。这是关于在配置更改中保留长期运行的任务。
Alex Lockwood 2013年

1
这个问题也是相关的。
亚历克斯·洛克伍德

只是FTR,这里还有一个相关的奥秘.. stackoverflow.com/q/23742412/294884
Fattie 2014年

Answers:


336

步骤#1:让您AsyncTaskstatic嵌套类或完全独立的类,而不是内部(非静态嵌套)类。

步骤#2:具有AsyncTask保持到Activity通过数据构件,通过构造和一个setter设置。

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

步骤#4:在中onRetainNonConfigurationInstance()AsyncTask从中分离出原始活动之后返回。

步骤#5:在中onCreate()(如果getLastNonConfigurationInstance()不是)null,将其AsyncTask强制转换为您的班级,然后调用setter将新活动与任务相关联。

步骤#6:不要引用中的活动数据成员doInBackground()

如果您遵循上述食谱,则可以正常使用。onProgressUpdate()并在后续onPostExecute()的开始onRetainNonConfigurationInstance()和结束之间暂停onCreate()

这是演示该技术的示例项目

另一种方法是抛弃AsyncTask并将您的工作移至IntentService。如果要完成的工作可能很长,并且无论用户在活动方面做了什么(例如,下载大文件),都应该继续进行,那么这特别有用。您可以使用有序广播Intent来使活动对正在完成的工作做出响应(如果活动仍在前台),或者提出一个Notification以通知用户是否已完成工作。这是一篇有关此模式的博客文章


8
非常感谢您对这个常见问题的出色回答!为了更全面,您可能需要在步骤4中添加我们必须分离(设置为null)AsyncTask中的活动。但是,在示例项目中对此进行了很好的说明。
凯文·高丁

3
但是,如果我需要访问Activity的成员怎么办?
尤金(Eugene)

3
@Andrew:创建一个静态内部类或包含多个对象的东西,然后将其返回。
CommonsWare

11
onRetainNonConfigurationInstance()不推荐使用,建议的替代方法是使用setRetainInstance(),但它不返回任何对象。是否可以使用来处理asyncTask配置更改setRetainInstance()
IndrekKõue2012年

10
@SYLARRR:好的。有Fragment保留的AsyncTask。有Fragment来电setRetainInstance(true)时本身。与AsyncTask唯一的谈话Fragment。现在,在配置更改中,Fragment不会销毁并重新创建(即使活动处于活动状态),因此在AsyncTask整个配置更改中都会保留。
CommonsWare 2012年

13

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

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

  1. 旋转有效,对话框继续存在。
  2. 您可以通过按“后退”按钮来取消任务和对话框(如果需要此行为)。
  3. 它使用片段。
  4. 当设备旋转时,活动下方片段的布局会正确更改。

可接受的答案是关于静态类(不是成员)的。为了避免AsyncTask具有指向外部类实例的(隐藏)指针,这些指针在破坏活动时会导致内存泄漏,这是必需的。
Bananeweizen 2013年

是的,不确定我为什么要提到静态成员,因为我实际上也使用过它们……很奇怪。编辑答案。
2013年

您能否更新您的链接?我真的需要这个
Romain Pellerin 2014年

抱歉,还没有恢复我的网站-我会尽快处理!但在平均时间也基本相同,在这个答案的代码:stackoverflow.com/questions/8417885/...
Timmmm

1
链接是虚假的;只会导致无用的索引,而没有指示代码在哪里。
FractalBob 2015年

9

我花了一个星期的时间来寻找解决这个难题的方法,而不必借助清单文件。该解决方案的假设是:

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

实作

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

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

  2. 在中onCreate()super.onCreate()应在初始化需要由ASyncTasks 访问的任何成员之后调用。另外,重写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;
    }
}

4

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

是。

解决方案更多是一个应用程序体系结构建议,而不是一些代码

他们提出了3种设计模式,无论应用程序处于什么状态,应用程序都可以与服务器同步工作(即使用户完成应用程序,用户更改屏幕,终止应用程序,其他可能的状态,应用程序也可以正常工作)可能会中断后台数据操作,这涵盖了它)

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

它的基础是将网络操作抽象ServiceActivity与应用程序中的任何一个独立工作的。如果您使用的是数据库,则当您使用所获取的远程数据更新本地数据库时,使用ContentResolverCursor将为您提供开箱即用的Observer模式,该模式可方便地更新UI而无需任何附加逻辑。其他任何后续操作代码都将通过传递给的回调Service(我ResultReceiver为此使用子类)来运行。

无论如何,我的解释实际上很模糊,您应该明确地观看演讲。


2

马克(CommonsWare)的回答确实确实可以解决方向变化,但如果直接销毁“活动”(例如在打电话的情况下),它将失败。

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

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

瑞恩(Ryan)完全弄清楚了这一点,这归功于他。


1

四年后,Google解决了该问题,仅在Activity onCreate中调用setRetainInstance(true)。它将在设备轮换期间保留您的活动实例。对于较旧的Android,我也有一个简单的解决方案。


1
人们之所以会看到这个问题,是因为Android在旋转,键盘扩展和其他事件时破坏了活动类,但是异步任务仍然为被破坏的实例保留引用,并尝试将其用于UI更新。您可以指示Android不要破坏清单或实用的活动。在这种情况下,异步任务引用仍然有效,没有发现问题。由于轮换可能需要一些其他工作来重新加载视图等,因此Google不建议保留活动。所以你决定。
Singagirl

谢谢,我知道这种情况,但不知道setRetainInstance()。我不明白您是否声称Google以此来解决问题中提出的问题。您可以链接信息源吗?谢谢。
jj_


onRetainNonConfigurationInstance()此函数纯粹是作为优化调用的,您不能依赖于其被调用。<从相同的源:developer.android.com/reference/android/app/...
Dhananjay中号

0

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


0

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

基本上,这些步骤是:

  1. onSaveInstanceState如果任务仍在处理中,我会使用它来保存。
  2. onCreate我得到的任务,如果它被保存。
  3. onPause我丢弃ProgressDialog如果显示。
  4. 在中onResume,显示ProgressDialog任务是否仍在处理中。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.