后台任务,进度对话框,方向改变-有任何100%工作的解决方案吗?

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

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

67040 次浏览

步骤#1:使你的AsyncTask成为一个static嵌套类,或者一个完全独立的类,只是不是一个内部(非静态嵌套)类。

步骤2:通过构造函数和setter设置的数据成员,让AsyncTask保留Activity

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

步骤#4:在onRetainNonConfigurationInstance()中,将AsyncTask从原始的、现在正在消失的活动中分离出来后返回AsyncTask

步骤#5:在onCreate()中,如果getLastNonConfigurationInstance()不是null,则将其转换为AsyncTask类,并调用setter将新活动与任务关联起来。

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

如果你按照上面的食谱做,一切都会成功的。onProgressUpdate()onPostExecute()被挂起在onRetainNonConfigurationInstance()的开始和随后的onCreate()的结束之间。

下面是一个示例项目演示技术。

另一种方法是丢弃AsyncTask并将工作移到IntentService中。如果要完成的工作可能很长,并且不管用户在活动方面做了什么(例如,下载一个大文件),都应该继续进行,那么这一点特别有用。你可以使用一个有序广播Intent来让活动响应正在完成的工作(如果它仍然在前台),或者引发一个Notification来让用户知道工作是否已经完成。这是一篇博客文章与更多关于此模式。

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

为了找到解决这个困境的方法,我已经辛苦了一个星期,而不是求助于编辑清单文件。这个解决方案的假设是:

  1. 您总是需要使用进度对话框
  2. 一次只执行一个任务
  3. 您需要在电话旋转时保持任务,并且进度对话框将自动退出。

实现

你需要将这篇文章底部的两个文件复制到你的工作区中。只要确保:

  1. 所有的__abc0都应该扩展BaseActivity

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

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

幸运的是,读者,我已经创建了非常全面和工作的例子,一个AsyncTask与进度对话框!

  1. 旋转工作,对话框保存下来。
  2. 您可以通过按后退按钮取消任务和对话框(如果您想要这种行为)。
  3. 它使用片段。
  4. 当设备旋转时,活动下面的片段的布局会适当地改变。

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

你可以通过使用一个应用对象来引用ASyncTask来处理方向变化和很少被破坏的Activity事件。

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

这一点完全要归功于瑞恩。

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

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

基本步骤如下:

    如果任务仍然存在,我使用onSaveInstanceState保存任务 李处理。< / >
  1. onCreate中,如果它被保存,我得到任务。
  2. onPause中,如果显示了ProgressDialog,则丢弃它。
  3. onResume中,如果任务仍然存在,则显示ProgressDialog 李处理。< / >

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

是的。

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

他们提出了3设计模式,允许应用程序与服务器同步工作,无论应用程序状态如何

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

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

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