Android 如何在屏幕旋转期间处理 AsyncTask?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2620917/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to handle an AsyncTask during Screen Rotation?
提问by Janusz
I read a lot on how to save my instance state or how to deal with my activity getting destroyed during screen rotation.
我阅读了很多关于如何保存我的实例状态或如何处理我的活动在屏幕旋转期间被破坏的内容。
There seem to be a lot of possibilities but I haven't figured out which one works best for retrieving results of an AsyncTask.
似乎有很多可能性,但我还没有弄清楚哪一种最适合检索 AsyncTask 的结果。
I have some AsyncTasks that are simply started again and call the isFinishing()
method of the activity and if the activity is finishing they wont update anything.
我有一些 AsyncTasks 只是再次启动并调用isFinishing()
活动的方法,如果活动完成,它们将不会更新任何内容。
The problem is that I have one Task that does a request to a web service that can fail or succeed and restarting the task would result in a financial loss for the user.
问题是我有一个 Task 向 Web 服务发出请求,该请求可能会失败或成功,并且重新启动该任务会导致用户遭受经济损失。
How would you solve this? What are the advantages or disadvantages of the possible solutions?
你会如何解决这个问题?可能的解决方案的优点或缺点是什么?
采纳答案by Jim Blackler
My first suggestion would be to make sure you actually need your activity to be reset on a screen rotation (the default behavior). Every time I've had issues with rotation I've added this attribute to my <activity>
tag in the AndroidManifest.xml, and been just fine.
我的第一个建议是确保您确实需要在屏幕旋转时重置您的活动(默认行为)。每次我遇到轮换问题时,我都会将此属性添加到<activity>
AndroidManifest.xml 中的标记中,并且效果很好。
android:configChanges="keyboardHidden|orientation"
It looks weird, but what it does it hand off to your onConfigurationChanged()
method, if you don't supply one it just does nothing other than re-measure the layout, which seems to be a perfectly adequate way of handling the rotate most of the time.
它看起来很奇怪,但是它会传递给您的onConfigurationChanged()
方法,如果您不提供它,它只会重新测量布局,这似乎是大多数情况下处理旋转的完美方法.
回答by Romain Guy
You can check out how I handle AsyncTask
s and orientation changes at code.google.com/p/shelves. There are various ways to do it, the one I chose in this app is to cancel any currently running task, save its state and start a new one with the saved state when the new Activity
is created. It's easy to do, it works well and as a bonus it takes care of stopping your tasks when the user leaves the app.
您可以AsyncTask
在code.google.com/p/shelves 上查看我如何处理s 和方向更改。有多种方法可以做到这一点,我在此应用程序中选择的一种方法是取消任何当前正在运行的任务,保存其状态并在创建新任务时使用保存的状态启动新任务Activity
。这很容易做到,效果很好,而且作为奖励,它会在用户离开应用程序时停止您的任务。
You can also use onRetainNonConfigurationInstance()
to pass your AsyncTask
to the new Activity
(be careful about not leaking the previous Activity
this way though.)
您也可以使用onRetainNonConfigurationInstance()
将您AsyncTask
的传递给新的Activity
(但请注意不要以Activity
这种方式泄漏前一个。)
回答by Vit Khudenko
This is the most interesting question I've seen regarding to Android!!! Actually I've been already looking for the solution during the last months. Still haven't solved.
这是我见过的关于 Android 的最有趣的问题!!!实际上,在过去的几个月里,我一直在寻找解决方案。还是没有解决。
Be careful, simply overriding the
小心,只需覆盖
android:configChanges="keyboardHidden|orientation"
stuff is not enough.
东西还不够。
Consider the case when user receives a phone call while your AsyncTask is running. Your request is already being processed by server, so the AsyncTask is awaiting for response. In this moment your app goes in background, because the Phone app has just come in foreground. OS may kill your activity since it's in the background.
考虑用户在 AsyncTask 运行时接到电话的情况。您的请求已由服务器处理,因此 AsyncTask 正在等待响应。此时您的应用程序进入后台,因为电话应用程序刚刚进入前台。操作系统可能会杀死您的活动,因为它在后台。
回答by neteinstein
Why don't you always keep a reference to the current AsyncTask on the Singleton provided by Android?
为什么不总是在 Android 提供的 Singleton 上保留对当前 AsyncTask 的引用?
Whenever a task starts, on PreExecute or on the builder, you define:
每当任务开始时,在 PreExecute 或构建器上,您定义:
((Application) getApplication()).setCurrentTask(asyncTask);
((Application) getApplication()).setCurrentTask(asyncTask);
Whenever it finishes you set it to null.
每当它完成时,您将其设置为 null。
That way you always have a reference which allows you to do something like, onCreate or onResume as appropriated for your specific logic:
这样你总是有一个参考,它允许你根据你的特定逻辑执行类似 onCreate 或 onResume 的操作:
this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();
this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();
If it's null you know that currently there is none running!
如果它为空,您就知道当前没有运行!
:-)
:-)
回答by user2342491
The most proper way to this is to use a fragment to retain the instance of the async task, over rotations.
最正确的方法是使用片段来保留异步任务的实例,通过旋转。
Here is a link to very simple example making it easy to follow integrate this technique into your apps.
这是一个非常简单示例的链接,可以轻松地将这种技术集成到您的应用程序中。
回答by hqt
In Pro android 4
. author has suggest a nice way, that you should use weak reference
.
在Pro android 4
. 作者提出了一个很好的方法,你应该使用weak reference
.
回答by Yury
To my point of view, it's better to store asynctask via onRetainNonConfigurationInstance
decoupling it from the current Activity object and binding it to a new Activity object after the orientation change. HereI found a very nice example how to work with AsyncTask and ProgressDialog.
在我看来,最好通过将 asynctaskonRetainNonConfigurationInstance
与当前 Activity 对象解耦并在方向更改后将其绑定到新的 Activity 对象来存储它。在这里,我找到了一个非常好的示例,如何使用 AsyncTask 和 ProgressDialog。
回答by Piyush Gupta
Android : background processing/Async Opeartion with configuration change
Android:后台处理/配置更改的异步操作
To maintain the states of async opeartion during background process: you can take an help of fragments.
要在后台进程中维护异步操作的状态:您可以借助片段。
See the following steps :
请参阅以下步骤:
Step 1: Create a headerless fragment let say background task and add a private async task class with in it.
第 1 步:创建一个无头片段,比如说后台任务,并在其中添加一个私有的异步任务类。
Step 2 (Optional Step): if you want to put a loading cursor on top of your activity use below code:
第 2 步(可选步骤):如果您想将加载光标放在活动顶部,请使用以下代码:
Step 3: In your main Activity implement BackgroundTaskCallbacks interface defined in step 1
第 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
}
}
}
回答by Juozas Kontvainis
One thing to consider is whether the result of the AsyncTask should be available only to the activity that started the task. If yes, then Romain Guy's answeris best. If it should be available to other activities of your application, then in onPostExecute
you can use LocalBroadcastManager
.
需要考虑的一件事是 AsyncTask 的结果是否应该只对启动任务的活动可用。如果是,那么Romain Guy 的回答是最好的。如果它应该可用于您的应用程序的其他活动,那么onPostExecute
您可以使用LocalBroadcastManager
.
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));
You will also need to make sure that activity correctly handles situation when broadcast is sent while activity is paused.
您还需要确保活动在活动暂停时发送广播时正确处理情况。
回答by Vahid
Have a look at this post. This Post involves AsyncTask performing long running operation and memory leak when screen rotation happens both in one sample application. The sample app is available on the source forge
看看这个帖子。这篇文章涉及在一个示例应用程序中发生屏幕旋转时 AsyncTask 执行长时间运行的操作和内存泄漏。示例应用程序可在源伪造上获得