我下载了一些数据从互联网在后台线程(我使用AsyncTask)和显示进度对话框,而下载。方向改变,活动重新启动,然后我的AsyncTask完成-我想解散进程对话框,并启动一个新的活动。但是调用disdisdialog有时会抛出一个异常(可能是因为Activity已经被销毁并且新的Activity还没有启动)。
处理这类问题的最佳方法是什么(从后台线程更新UI,即使用户改变方向也能工作)?谷歌是否有人提供了一些“官方解决方案”?
我下载了一些数据从互联网在后台线程(我使用AsyncTask)和显示进度对话框,而下载。方向改变,活动重新启动,然后我的AsyncTask完成-我想解散进程对话框,并启动一个新的活动。但是调用disdisdialog有时会抛出一个异常(可能是因为Activity已经被销毁并且新的Activity还没有启动)。
处理这类问题的最佳方法是什么(从后台线程更新UI,即使用户改变方向也能工作)?谷歌是否有人提供了一些“官方解决方案”?
当前回答
步骤#1:使AsyncTask成为一个静态嵌套类,或者一个完全独立的类,只是不是一个内部(非静态嵌套)类。
步骤#2:让AsyncTask通过一个数据成员保存活动,通过构造函数和setter设置。
步骤#3:当创建AsyncTask时,将当前Activity提供给构造函数。
步骤#4:在onRetainNonConfigurationInstance()中,返回AsyncTask,在将它从原始的,现在正在消失的活动中分离出来之后。
步骤#5:在onCreate()中,如果getLastNonConfigurationInstance()不为空,将其转换为AsyncTask类,并调用setter将新活动与任务关联起来。
步骤#6:不要引用doInBackground()中的活动数据成员。
如果你按照上面的食谱做,一切都会成功的。onProgressUpdate()和onPostExecute()被挂起在onRetainNonConfigurationInstance()的开始和后续onCreate()的结束之间。
下面是演示该技术的示例项目。
Another approach is to ditch the AsyncTask and move your work into an IntentService. This is particularly useful if the work to be done may be long and should go on regardless of what the user does in terms of activities (e.g., downloading a large file). You can use an ordered broadcast Intent to either have the activity respond to the work being done (if it is still in the foreground) or raise a Notification to let the user know if the work has been done. Here is a blog post with more on this pattern.
其他回答
谷歌是否有人提供了一些“官方解决方案”?
Yes.
解决方案更像是一个应用程序体系结构建议,而不仅仅是一些代码。
他们提出了3种设计模式,允许应用程序与服务器同步工作,无论应用程序状态如何(即使用户完成应用程序,用户更换屏幕,应用程序被终止,任何其他可能的状态,后台数据操作可能被中断,它也会工作,这包括在内)
Virgil Dobjanschi在谷歌I/O 2010期间的Android REST客户端应用程序演讲中解释了这个提议。它只有1小时长,但非常值得一看。
它的基础是将网络操作抽象为独立于应用程序中的任何活动的服务。如果您正在使用数据库,使用ContentResolver和Cursor将为您提供一个开箱即用的Observer模式,一旦您使用获取的远程数据更新了本地数据库,就可以方便地更新UI,而无需任何额外的逻辑。任何其他后续操作代码都将通过传递给服务的回调运行(为此我使用ResultReceiver子类)。
不管怎样,我的解释其实很模糊,你一定要看这个演讲。
已接受的答案非常有用,但它没有进度对话框。
读者,幸运的是,我已经创建了一个非常全面和工作的AsyncTask示例,其中包含一个进度对话框!
旋转工作,对话框保存下来。 您可以通过按后退按钮取消任务和对话框(如果您想要这种行为)。 它使用片段。 当设备旋转时,活动下面的片段的布局会适当地改变。
虽然Mark (CommonsWare)的答案确实适用于方向更改,但如果Activity被直接销毁(比如在电话的情况下),它就会失败。
你可以通过使用一个应用对象来引用ASyncTask来处理方向变化和很少被破坏的Activity事件。
对于这个问题和解决方案,这里有一个很好的解释:
这一点完全要归功于瑞恩。
4年后,谷歌解决了问题,只是在onCreate活动中调用setRetainInstance(true)。它将在设备旋转期间保存您的活动实例。对于旧版本的Android,我也有一个简单的解决方案。
为了找到解决这个困境的方法,我已经辛苦了一个星期,而不是求助于编辑清单文件。这个解决方案的假设是:
您总是需要使用进度对话框 一次只执行一个任务 您需要在电话旋转时保持任务,并且进度对话框将自动退出。
实现
你需要将这篇文章底部的两个文件复制到你的工作区中。只要确保:
你所有的activity都应该扩展BaseActivity 在onCreate()中,super.onCreate()应该在初始化ASyncTasks需要访问的任何成员后调用。同样,重写getContentViewId()来提供表单布局id。 像往常一样重写onCreateDialog()来创建由活动管理的对话框。 请参阅下面的示例静态内部类来创建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();
就是这样!我希望这个强大的解决方案能帮助到一些人。
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;
}
}