我在后台线程中下载了一些来自互联网的数据(我使用AsyncTask
)并在下载时显示进度对话框。方向更改,Activity重新启动,然后我的AsyncTask完成 - 我想关闭progess对话框并启动一个新的Activity。但是调用dismissDialog有时会抛出一个异常(可能是因为Activity被破坏而且还没有启动新的Activity)。
处理此类问题的最佳方法是什么(即使用户更改方向,也可以从后台线程更新UI)?谷歌有人提供了一些“官方解决方案”吗?
答案 0 :(得分:335)
步骤#1:使AsyncTask
成为static
嵌套类或完全独立的类,而不是内部(非静态嵌套)类。
步骤2:让AsyncTask
通过数据成员保持Activity
,通过构造函数和设置器设置。
步骤3:创建AsyncTask
时,将当前Activity
提供给构造函数。
步骤4:在onRetainNonConfigurationInstance()
中,在将AsyncTask
从原始的,即将开始的活动中分离后,返回onCreate()
。
步骤5:在getLastNonConfigurationInstance()
中,如果null
不是AsyncTask
,请将其投放到doInBackground()
班级并致电您的设置者,将新活动与任务相关联
步骤6:不要引用onProgressUpdate()
中的活动数据成员。
如果你按照上面的方法,它将全部工作。 onPostExecute()
和onRetainNonConfigurationInstance()
在onCreate()
的开头和后续AsyncTask
的结尾之间暂停。
Here is a sample project展示了这项技术。
另一种方法是放弃IntentService
并将您的工作转移到Intent
。如果要完成的工作可能很长并且应该继续进行而不管用户在活动方面做了什么(例如,下载大文件),则这尤其有用。您可以使用有序广播Notification
让活动响应正在进行的工作(如果它仍在前台)或者引发{{1}}以让用户知道工作是否已完成。 Here is a blog post对此模式有更多了解。
答案 1 :(得分:13)
接受的答案非常有用,但它没有进度对话框。
幸运的是,读者,我创建了一个extremely comprehensive and working example of an AsyncTask with a progress dialog!
答案 2 :(得分:8)
我已经辛苦工作了一个星期,无需编辑清单文件即可找到解决这一难题的方法。该解决方案的假设是:
<强>实施强>
您需要将此帖子底部的两个文件复制到工作区中。请确保:
您的所有Activity
都应该延伸BaseActivity
在onCreate()
中,在您初始化需要super.onCreate()
访问的所有成员后,应调用ASyncTask
。另外,覆盖getContentViewId()
以提供表单布局ID。
覆盖onCreateDialog()
like usual以创建活动管理的对话框。
请参阅下面的代码,了解制作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;
}
}
答案 3 :(得分:4)
谷歌有人提供了一些“官方解决方案”吗?
是强>
解决方案更多的是应用程序架构提案,而不是只是一些代码。
他们提出 3设计模式 ,允许应用程序与服务器同步工作,无论应用程序状态如何(即使用户完成应用程序,用户更改屏幕,应用程序终止,其他可能的状态,后台数据操作可能会被干扰,这涵盖了它)
该提案在Virgil Dobjanschi的 Google I / O 2010 期间的Android REST client applications演讲中进行了解释。它长达1小时,但非常值得观看。
它的基础是将网络操作抽象为Service
,它独立于应用程序中的任何Activity
。如果您正在使用数据库,使用ContentResolver
和Cursor
会为您提供开箱即用的观察者模式,这样便于更新UI而无需任何数据库一旦使用获取的远程数据更新本地数据库,aditional逻辑。任何其他的操作后代码都将通过传递给Service
的回调运行(为此使用ResultReceiver
子类)。
无论如何,我的解释实际上很模糊,你应该明确地观看演讲。
答案 4 :(得分:2)
虽然Mark的(CommonsWare)答案确实适用于方向更改,但如果直接销毁活动(例如打电话),则会失败。
您可以使用Application对象引用ASyncTask来处理方向更改和罕见的已销毁Activity事件。
对问题和解决方案here有一个很好的解释:
完全归功于Ryan将这一点搞清楚了。
答案 5 :(得分:1)
4年后,Google解决了在Activity onCreate中调用setRetainInstance(true)的问题。它将在设备轮换期间保留您的活动实例。对于较旧的Android,我也有一个简单的解决方案。
答案 6 :(得分:0)
您应该使用活动处理程序调用所有活动操作。所以如果你在某个线程中,你应该创建一个Runnable并使用Activitie的Handler发布。否则,您的应用程序有时会因致命异常而崩溃。
答案 7 :(得分:0)
这是我的解决方案:https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog
基本上步骤是:
onSaveInstanceState
来保存任务
处理onCreate
中,如果已保存,我会收到任务。onPause
中,如果显示ProgressDialog
,我会弃置。{/ li>
onResume
中,如果任务仍然存在,我会显示ProgressDialog
处理