如何处理屏幕旋转期间的AsyncTask?

我读了很多关于如何保存我的实例状态,或如何处理我的活动在屏幕旋转期间遭到破坏。

似乎有很多的可能性,但我还没有找出哪一个最适合检索AsyncTask的结果。

我有一些AsyncTasks,只是再次启动,并调用该活动的isFinishing()方法,如果活动正在完成,他们不会更新任何东西。

问题是我有一个任务向Web服务发出请求,可能会失败或成功,重新启动任务将导致用户的经济损失。

你将如何解决这个问题? 可能的解决scheme有哪些优缺点?

我的第一个build议是确保你实际上需要你的活动在屏幕上旋转(默认行为)。 每次我遇到轮换问题时,我已经将这个属性添加到了AndroidManifest.xml中的<activity>标签中,并且很好。

 android:configChanges="keyboardHidden|orientation" 

它看起来很奇怪,但它做什么它交给你的onConfigurationChanged()方法,如果你不提供一个它只是没有什么比重新测量布局,这似乎是一个完美的处理旋转的最适当的方式的时间。

您可以查看我如何在code.google.com/p/shelves处理AsyncTask和方向更改。 有很多方法可以做到这一点,我在这个应用程序中select的是取消任何当前正在运行的任务,保存它的状态,并创build新的Activity时,启动一个新的与保存的状态。 这很容易做到,它运作良好,作为奖励,当用户离开应用程序时,需要处理停止任务。

您还可以使用onRetainNonConfigurationInstance()将您的AsyncTask传递给新的Activity (注意不要以这种方式泄漏前面的Activity )。

这是我见过的有关Android的最有趣的问题! 其实在过去的几个月里我一直在寻找解决scheme。 还没有解决。

小心,简单地覆盖

 android:configChanges="keyboardHidden|orientation" 

东西是不够的。

考虑一下用户在AsyncTask运行时接到电话的情况。 你的请求已经被服务器处理,所以AsyncTask正在等待响应。 在这一刻你的应用程序将在后台,因为电话应用程序刚刚进入前台。 因为它在后台,操作系统可能会杀死你的活动。

为什么不总是保持对Android提供的Singleton的当前AsyncTask的引用?

每当任务启动时,在PreExecute上或在构build器上,您可以定义:

((Application) getApplication()).setCurrentTask(asyncTask);

每当它完成,你把它设置为null。

这样,你总是有一个引用,允许你做一些像onCreate或onResume这样的特定逻辑:

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

如果它是空的,你知道目前没有运行!

🙂

最合适的方法是使用片段来保留asynchronous任务的实例,以防止旋转。

这里是一个非常简单的例子的链接,可以很容易地将这种技术集成到您的应用程序。

https://gist.github.com/daichan4649/2480065

Pro android 4 。 作者提出了一个很好的方法,你应该使用weak reference

弱参考说明

在我看来,最好通过onRetainNonConfigurationInstance存储asynctask, onRetainNonConfigurationInstance其与当前Activity对象解耦,并在方向更改后将其绑定到新的Activity对象。 在这里我find了一个很好的例子,说明如何使用AsyncTask和ProgressDialog。

Android:configuration更改的后台处理/ Async Opeartion

为了在后台进程中保持asynchronous操作的状态,你可以借助碎片。

请参阅以下步骤:

第1步:创build一个无标题片段让我们说背景任务,并在其中添加一个私人的asynchronous任务类。

步骤2(可选步骤):如果您想要将加载光标置于您的活动之上,请使用以下代码:

第3步:在您的主要Activity中实现在第1步中定义的BackgroundTaskCallbacks接口

 class BackgroundTask extends Fragment { public BackgroundTask() { } // Add a static interface static interface BackgroundTaskCallbacks { void onPreExecute(); void onCancelled(); void onPostExecute(); void doInBackground(); } private BackgroundTaskCallbacks callbacks; private PerformAsyncOpeation asyncOperation; private boolean isRunning; private final String TAG = BackgroundTask.class.getSimpleName(); /** * Start the async operation. */ public void start() { Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********"); if (!isRunning) { asyncOperation = new PerformAsyncOpeation(); asyncOperation.execute(); isRunning = true; } Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********"); } /** * Cancel the background task. */ public void cancel() { Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********"); if (isRunning) { asyncOperation.cancel(false); asyncOperation = null; isRunning = false; } Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********"); } /** * Returns the current state of the background task. */ public boolean isRunning() { return isRunning; } /** * Android passes us a reference to the newly created Activity by calling * this method after each configuration change. */ public void onAttach(Activity activity) { Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********"); super.onAttach(activity); if (!(activity instanceof BackgroundTaskCallbacks)) { throw new IllegalStateException( "Activity must implement the LoginCallbacks interface."); } // Hold a reference to the parent Activity so we can report back the // task's // current progress and results. callbacks = (BackgroundTaskCallbacks) activity; Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********"); } public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********"); super.onCreate(savedInstanceState); // Retain this fragment across configuration changes. setRetainInstance(true); Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********"); } public void onDetach() { super.onDetach(); callbacks = null; } private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> { protected void onPreExecute() { Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********"); if (callbacks != null) { callbacks.onPreExecute(); } isRunning = true; Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********"); } protected Void doInBackground(Void... params) { Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********"); if (callbacks != null) { callbacks.doInBackground(); } Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********"); return null; } protected void onCancelled() { Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********"); if (callbacks != null) { callbacks.onCancelled(); } isRunning = false; Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********"); } protected void onPostExecute(Void ignore) { Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********"); if (callbacks != null) { callbacks.onPostExecute(); } isRunning = false; Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********"); } } public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setRetainInstance(true); } public void onStart() { super.onStart(); } public void onResume() { super.onResume(); } public void onPause() { super.onPause(); } public void onStop() { super.onStop(); } 

 public class ProgressIndicator extends Dialog { public ProgressIndicator(Context context, int theme) { super(context, theme); } private ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.progress_indicator); this.setCancelable(false); progressBar = (ProgressBar) findViewById(R.id.progressBar); progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN); } @Override public void show() { super.show(); } @Override public void dismiss() { super.dismiss(); } @Override public void cancel() { super.cancel(); } 

 public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{ private static final String KEY_CURRENT_PROGRESS = "current_progress"; ProgressIndicator progressIndicator = null; private final static String TAG = MyActivity.class.getSimpleName(); private BackgroundTask task = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(//"set your layout here"); initialize your views and widget here ............. FragmentManager fm = getSupportFragmentManager(); task = (BackgroundTask) fm.findFragmentByTag("login"); // If the Fragment is non-null, then it is currently being // retained across a configuration change. if (task == null) { task = new BackgroundTask(); fm.beginTransaction().add(task, "login").commit(); } // Restore saved state if (savedInstanceState != null) { Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: " + task.isRunning()); if (task.isRunning()) { progressIndicator = new ProgressIndicator(this, R.style.TransparentDialog); if (progressIndicator != null) { progressIndicator.show(); } } } } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); } @Override protected void onSaveInstanceState(Bundle outState) { // save the current state of your operation here by saying this super.onSaveInstanceState(outState); Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: " + task.isRunning()); outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning()); if (progressIndicator != null) { progressIndicator.dismiss(); progressIndicator.cancel(); } progressIndicator = null; } private void performOperation() { if (!task.isRunning() && progressIndicator == null) { progressIndicator = new ProgressIndicator(this, R.style.TransparentDialog); progressIndicator.show(); } if (task.isRunning()) { task.cancel(); } else { task.start(); } } @Override protected void onDestroy() { super.onDestroy(); if (progressIndicator != null) { progressIndicator.dismiss(); progressIndicator.cancel(); } progressIndicator = null; } @Override public void onPreExecute() { Log.i(TAG, "CALLING ON PRE EXECUTE"); } @Override public void onCancelled() { Log.i(TAG, "CALLING ON CANCELLED"); if (progressIndicator != null) { progressIndicator.dismiss(); progressIndicator.cancel(); } public void onPostExecute() { Log.i(TAG, "CALLING ON POST EXECUTE"); if (progressIndicator != null) { progressIndicator.dismiss(); progressIndicator.cancel(); progressIndicator = null; } } @Override public void doInBackground() { // put your code here for background operation } 

}

需要考虑的一件事是,AsyncTask的结果是否仅适用于启动任务的活动。 如果是,那么罗曼盖伊的答案是最好的。 如果它应该可用于您的应用程序的其他活动,然后在onPostExecute您可以使用LocalBroadcastManager

 LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished")); 

在活动暂停时发送广播时,您还需要确保活动正确处理情况。

看看这个post 。 当一个示例应用程序中出现屏幕旋转时,此Post将涉及AsyncTask执行长时间运行操作和内存泄漏。 示例应用程序在源代码中可用

我的解决scheme

在我的情况下,我有一个具有相同的上下文的AsyncTasks链。 活动只能访问第一个。 要取消任何正在运行的任务,我做了以下事情

 public final class TaskLoader { private static AsyncTask task; private TaskLoader() { throw new UnsupportedOperationException(); } public static void setTask(AsyncTask task) { TaskLoader.task = task; } public static void cancel() { TaskLoader.task.cancel(true); } } 

任务doInBackground()

 protected Void doInBackground(Params... params) { TaskLoader.setTask(this); .... } 

Activity onStop()onPause()

 protected void onStop() { super.onStop(); TaskLoader.cancel(); } 
 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); final AddTask task = mAddTask; if (task != null && task.getStatus() != UserTask.Status.FINISHED) { final String bookId = task.getBookId(); task.cancel(true); if (bookId != null) { outState.putBoolean(STATE_ADD_IN_PROGRESS, true); outState.putString(STATE_ADD_BOOK, bookId); } mAddTask = null; } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) { final String id = savedInstanceState.getString(STATE_ADD_BOOK); if (!BooksManager.bookExists(getContentResolver(), id)) { mAddTask = (AddTask) new AddTask().execute(id); } } }