后台任务,进度对话框,方向更改 – 是否有任何100%的工作解决scheme?
我从后台线程(我使用AsyncTask
)从互联网上下载一些数据,并下载时显示进度对话框。 方向更改,活动重新启动,然后我的AsyncTask完成 – 我想closuresprogess对话框,并开始一个新的活动。 但是调用dismissDialog有时会抛出一个exception(可能是因为Activity已经被销毁,新的Activity还没有被启动)。
什么是处理这种问题的最好方法(即使用户改变方向,从后台线程更新UI)? Google有人提供了一些“官方解决scheme”吗?
步骤1:使你的AsyncTask
成为一个static
嵌套类,或者一个完全独立的类,而不是一个内部(非静态嵌套)类。
第二步:让AsyncTask
通过一个数据成员保存到Activity
,通过构造函数和setter来设置。
第3步:创buildAsyncTask
,将当前的Activity
给构造函数。
步骤#4:在onRetainNonConfigurationInstance()
,将AsyncTask
从原始的,即将离开的活动中分离出来后返回。
第5步:在onCreate()
,如果getLastNonConfigurationInstance()
不为null
,则将其转换为AsyncTask
类,并调用setter将新活动与任务关联。
步骤#6:不要参考doInBackground()
的活动数据成员。
如果你按照上面的配方,它会一切正常。 onProgressUpdate()
和onPostExecute()
在onPostExecute()
的开始和后续onCreate()
的结束之间挂起。
这是一个示范项目展示技术。
另一种方法是抛弃AsyncTask
并将您的工作转移到IntentService
。 如果要完成的工作可能很长,并且应该继续进行,而不pipe用户在活动方面做了什么(例如,下载大文件),这是特别有用的。 您可以使用有序的广播Intent
来让活动响应正在完成的工作(如果它仍然在前台)或者提出一个Notification
让用户知道工作是否已经完成。 这里有一个关于这种模式更多的博客文章 。
接受的答案是非常有用的,但它没有进展对话框。
幸运的是,对于读者来说,我已经创build了一个非常全面的AsyncTask实例,并带有一个进度对话框 !
- 旋转工作,并对话生存。
- 您可以通过按下后退button来取消任务和对话框(如果需要这种行为)。
- 它使用碎片。
- 设备旋转时,活动下面的碎片布局会正确更改。
我已经辛苦了一个星期,find解决这个困境,而不诉诸编辑清单文件。 这个解决scheme的假设是:
- 您始终需要使用进度对话框
- 一次只执行一个任务
- 当手机旋转并且进度对话框被自动解除时,您需要保持该任务。
履行
您将需要将这篇文章底部的两个文件复制到工作区中。 只要确保:
-
你所有的
Activity
都应该扩展BaseActivity
-
在
onCreate()
,在初始化任何需要被ASyncTask
访问的成员之后,应该调用super.onCreate()
。 此外,重写getContentViewId()
以提供表单布局ID。 -
像平常一样重写
onCreateDialog()
来创build由活动pipe理的对话框。 -
请参阅下面的代码来获取示例静态内部类来制作您的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();
而已! 我希望这个强大的解决scheme将帮助某人。
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; } }
有人从Google提供了一些“官方解决scheme”?
是。
解决scheme更多的是应用程序体系结构build议,而不仅仅是一些代码 。
他们提出了3种devise模式 ,允许应用程序与服务器同步工作,而不pipe应用程序状态如何(即使用户完成应用程序,用户更改屏幕,应用程序终止,每隔一个可能的状态一个后台数据操作可能会被中断,这就覆盖了它)
该build议在Virgil Dobjanschi的Google I / O 2010期间的Android REST客户端应用程序演讲中进行了解释。 这是1小时,但是非常值得一看。
其基础是将networking操作抽象为独立于应用程序中的任何Activity
的Service
。 如果您使用的是数据库,一旦使用获取的远程数据更新了本地数据库,使用ContentResolver
和Cursor
将为您提供一个开箱即用的Observer模式 ,便于更新UI,而无需任何辅助逻辑。 任何其他操作后的代码将通过传递给Service
的callback(我使用ResultReceiver
子类来运行)来运行。
无论如何,我的解释其实很含糊,你应该有把握地观看演讲。
虽然马克(CommonsWare)的答案确实适用于方向变化,但如果活动被直接销毁,就会失败(就像打电话一样)。
您可以通过使用Application对象来引用您的ASyncTask来处理方向更改和罕见的被破坏的Activity事件。
这里有一个很好的解决问题和解决scheme:
信贷完全由瑞安来找出这一个。
您应该使用活动处理程序调用所有活动操作。 所以如果你在某个线程中,你应该创build一个Runnable并使用Activitie的Handler进行发布。 否则,您的应用程序有时会崩溃致命的exception。
谷歌解决了4年后,在Activity onCreate中调用了setRetainInstance(true)的问题。 它将在设备旋转期间保留您的活动实例。 我也有一个较老的Android的简单解决scheme。
这是我的解决scheme: https : //github.com/Gotchamoh/Android-AsyncTask-ProgressDialog
基本上这些步骤是:
- 如果任务仍在处理,我使用
onSaveInstanceState
来保存任务。 - 在
onCreate
我得到的任务,如果它被保存。 - 在
onPause
,如果显示ProgressDialog
我会放弃它。 - 在
onResume
,如果任务仍在处理,则显示ProgressDialog
。