我在后台线程中使用AsyncTask
下载一些数据,并在下载时显示一个进度对话框。当屏幕方向改变、Activity被重新启动,此时我的AsyncTask
已完成 - 我想关闭进度对话框并启动一个新的Activity。但是调用dismissDialog有时会抛出异常(可能是因为Activity已经被销毁,而新的Activity还没有启动)。
如何处理这种问题的最佳方法(从后台线程更新UI,即使用户改变方向也能正常工作)?Google是否提供了某种“官方解决方案”?
我在后台线程中使用AsyncTask
下载一些数据,并在下载时显示一个进度对话框。当屏幕方向改变、Activity被重新启动,此时我的AsyncTask
已完成 - 我想关闭进度对话框并启动一个新的Activity。但是调用dismissDialog有时会抛出异常(可能是因为Activity已经被销毁,而新的Activity还没有启动)。
如何处理这种问题的最佳方法(从后台线程更新UI,即使用户改变方向也能正常工作)?Google是否提供了某种“官方解决方案”?
步骤#1:将您的AsyncTask
作为static
嵌套类或完全单独的类,而不是内部(非静态嵌套)类。
步骤#2:通过数据成员保留Activity
,并通过构造函数和setter设置。
步骤#3:在创建AsyncTask
时,将当前Activity
提供给构造函数。
步骤#4:在onRetainNonConfigurationInstance()
中,在将其从原始的将要消失的活动中分离后,返回AsyncTask
。
步骤#5:在onCreate()
中,如果getLastNonConfigurationInstance()
不为null
,则将其转换为您的AsyncTask
类,并调用setter将新的活动与任务关联起来。
步骤#6:不要从doInBackground()
引用活动数据成员。
如果按照上述步骤进行操作,就会顺利运行。在onRetainNonConfigurationInstance()
开始和随后的onCreate()
结束之间,onProgressUpdate()
和onPostExecute()
将被暂停。
AsyncTask
,并将工作移到 IntentService
中。如果要执行的工作可能很长,并且无论用户在活动方面做什么(例如下载大文件),都应该继续进行,那么这种方法特别有用。您可以使用有序广播 Intent
,以便活动响应正在进行的工作(如果它仍在前台),或者引发一个 Notification
,让用户知道工作是否已完成。有关此模式的更多信息,请参见此博客文章。onRetainNonConfigurationInstance()
方法已被弃用,建议使用setRetainInstance()
方法作为替代方案,但它不会返回对象。使用setRetainInstance()
方法能否在配置更改时处理asyncTask
? - Indrek KõuesetRetainInstance(true)
。让“AsyncTask”仅与“Fragment”通信。这样,在配置更改时,“Fragment”不会被销毁和重新创建(即使活动被销毁和重新创建),因此“AsyncTask”将跨越配置更改保留下来。 - CommonsWareAsyncTaskLoader
不是推荐的方法,因为它并不完全相同。AsyncTask
执行单个异步操作,而 AsyncTaskLoader
则为 Activity
和/或 Fragment
执行异步加载,跨配置更改保留已加载的数据,并在检测到数据源更改时自动执行新的加载。如果您需要执行单个、一次性、可能昂贵的操作,那么使用 AsyncTaskLoader
就没有意义了...请使用 AsyncTask
代替。 - Alex Lockwood这个被接受的答案非常有帮助,但是它没有一个进度对话框。
幸运的是,亲爱的读者,我创建了一个非常详细且可行的带有进度对话框的AsyncTask示例!
实现
您需要将本帖底部找到的两个文件复制到您的工作区。请确保:
所有Activity
都应该扩展BaseActivity
在onCreate()
中,在初始化任何需要由您的ASyncTask
访问的成员之后,应该调用super.onCreate()
。另外,重写getContentViewId()
以提供表单布局ID。
像往常一样覆盖onCreateDialog()
来创建由活动管理的对话框。
有关示例静态内部类的代码,请参见下面。您可以将结果存储在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;
}
}
有谷歌的人提供了“官方解决方案”吗?
是的。
这个解决方案更像是一个应用程序架构建议,而不仅仅是一些代码。
他们提出了3种设计模式,允许应用程序与服务器同步工作,无论应用程序的状态如何(即使用户完成应用程序、用户更改屏幕、应用程序被终止,以及其他可能导致后台数据操作中断的状态,都可以正常工作)。
该提案在Virgil Dobjanschi在Google I/O 2010期间的Android REST客户端应用程序演讲中详细说明。虽然演讲时长为1小时,但非常值得观看。
其基础是将网络操作抽象到一个Service
中,该服务独立于应用程序中的任何Activity
。如果您正在使用数据库,则使用ContentResolver
和Cursor
可以为您提供开箱即用的Observer pattern
,方便地更新UI,而无需任何其他逻辑,一旦您使用获取的远程数据更新了本地数据库。任何其他的后续操作代码都将通过传递给Service
的回调来运行(我使用一个ResultReceiver
子类来实现)。
无论如何,我的解释实际上相当模糊,您应该一定要观看演讲。
你应该使用Activity Handler调用所有的Activity操作。因此,如果你在某个线程中,你应该创建一个Runnable并使用Activity的Handler进行发布。否则,你的应用程序有时会崩溃并出现致命异常。
这是我的解决方案: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog
基本步骤如下:
onSaveInstanceState
来保存任务,如果它仍在处理中。onCreate
中,如果任务已被保存,我获取该任务。onPause
中,如果显示了ProgressDialog
,我将其丢弃。onResume
中,如果任务仍在处理中,我会显示ProgressDialog
。