Android 即使活动已销毁,AsyncTask 也不会停止
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2531336/
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
AsyncTask won't stop even when the activity has destroyed
提问by Raja
I have an AsyncTask object which starts executing when the activity is created and does stuff in the background (downloads up to 100 images). Everything works fine but there is this peculiar behavior which i'm not able to understand.
我有一个 AsyncTask 对象,它在创建活动时开始执行并在后台执行操作(最多下载 100 个图像)。一切正常,但有一种我无法理解的特殊行为。
For eg: when the android screen's orientation changes then the activity is destroyed and created again. So I override the onRetainNonConfigurationInstance() method and save all the downloaded data executed in the AsyncTask. My purpose of doing this is to not have AsyncTask run each time activity is destroyed-created during orientation changes, but as i can see in my logs the previous AsyncTask is still executing. (The data is saved correctly though)
例如:当 android 屏幕的方向发生变化时,活动将被销毁并再次创建。所以我覆盖了 onRetainNonConfigurationInstance() 方法并保存了在 AsyncTask 中执行的所有下载的数据。我这样做的目的是不让 AsyncTask 每次在方向更改期间销毁创建活动时都运行,但正如我在日志中看到的那样,之前的 AsyncTask 仍在执行。(虽然数据保存正确)
I even tried to cancel the AsyncTask in the onDestroy() method of the activity but the logs still show AsyncTask as running.
我什至试图在活动的 onDestroy() 方法中取消 AsyncTask,但日志仍然显示 AsyncTask 正在运行。
This is really strange behavior and would really be thankful if someone can tell me the correct procedure to stop/cancel the AsyncTask.
这真的是很奇怪的行为,如果有人能告诉我停止/取消 AsyncTask 的正确程序,我将不胜感激。
Thanks
谢谢
回答by Snicolas
The answer given by @Romain Guy is correct. Nevertheless, I would like to add a complement of information and give a pointer to a library or 2 that can be used for long running AsyncTask and even more for network oriented asynctasks.
@Romain Guy 给出的答案是正确的。不过,我想补充一些信息,并给出一个或 2 个库的指针,该库可用于长时间运行的 AsyncTask,甚至更多用于面向网络的异步任务。
AsyncTasks have been designed for doing stuff in background. And yes, you can stop it using the cancel
method. As you download stuff from the Internet, I strongly suggest you take care of your thread when it is the IO blocking state. You should organize your download as follow :
AsyncTasks 被设计为在后台执行任务。是的,您可以使用该cancel
方法停止它。当您从 Internet 下载东西时,我强烈建议您在 IO 阻塞状态时照顾好您的线程。您应该按如下方式组织下载:
public void download() {
//get the InputStream from HttpUrlConnection or any other
//network related stuff
while( inputStream.read(buffer) != -1 && !Thread.interrupted() ) {
//copy data to your destination, a file for instance
}
//close the stream and other resources
}
Using the Thread.interrupted
flag will help your thread to quit properly a blocking io state. Your thread will be more responsive to an invocation of the cancel
method.
使用该Thread.interrupted
标志将帮助您的线程正确退出阻塞 io 状态。您的线程将对cancel
方法的调用更敏感。
AsyncTask design flaw
AsyncTask 设计缺陷
But if your AsyncTask lasts for too long, then you will face 2 different issues :
但是如果您的 AsyncTask 持续时间过长,那么您将面临 2 个不同的问题:
- Activities are poorly tied to the activity life cycle and you won't get the result of your AsyncTask if your activity dies. Indeed, yes, you can but it will be the rough way.
- AsyncTask are not very well documented. A naive, though intuitive, implementation and use of an asynctask can quickly lead to memory leaks.
- 活动与活动生命周期的联系很差,如果您的活动终止,您将无法获得 AsyncTask 的结果。确实,是的,您可以,但这将是艰难的方式。
- AsyncTask 没有很好的文档记录。asynctask 的天真但直观的实现和使用可能会迅速导致内存泄漏。
RoboSpice, the library I would like to introduce, uses a background service to execute this kind of requests. It has been designed for network requests. It provides additional features such as automatic caching of requests' results.
我想介绍的库RoboSpice使用后台服务来执行此类请求。它是为网络请求而设计的。它提供了附加功能,例如请求结果的自动缓存。
Here is the reason why AsyncTasks are bad for long running tasks. The following reasonning is an adaptation from exerpts of RoboSpice motivations: the app that explains why using RoboSpice is filling a need on the Android platform.
这就是 AsyncTasks 对长时间运行的任务不利的原因。以下推理改编自 RoboSpice动机的摘录:解释为什么使用 RoboSpice 的应用程序正在满足 Android 平台上的需求。
The AsyncTask and Activity life cycle
AsyncTask 和 Activity 生命周期
AsyncTasks don't follow Activity instances' life cycle. If you start an AsyncTask inside an Activity and you rotate the device, the Activity will be destroyed and a new instance will be created. But the AsyncTask will not die. It will go on living until it completes.
AsyncTask 不遵循 Activity 实例的生命周期。如果您在 Activity 中启动 AsyncTask 并旋转设备,则 Activity 将被销毁并创建一个新实例。但是 AsyncTask 不会死。它将继续存在,直到完成。
And when it completes, the AsyncTask won't update the UI of the new Activity. Indeed it updates the former instance of the activity that is not displayed anymore. This can lead to an Exception of the type java.lang.IllegalArgumentException: View not attached to window manager if you use, for instance, findViewById to retrieve a view inside the Activity.
当它完成时,AsyncTask 不会更新新 Activity 的 UI。事实上,它更新了不再显示的活动的前一个实例。这可能会导致 java.lang.IllegalArgumentException: View not attach to window manager 类型的异常,例如,如果您使用 findViewById 来检索 Activity 内的视图。
Memory leak issue
内存泄漏问题
It is very convenient to create AsyncTasks as inner classes of your Activities. As the AsyncTask will need to manipulate the views of the Activity when the task is complete or in progress, using an inner class of the Activity seems convenient : inner classes can access directly any field of the outer class.
创建 AsyncTasks 作为活动的内部类非常方便。由于 AsyncTask 在任务完成或正在进行时需要操作 Activity 的视图,因此使用 Activity 的内部类似乎很方便:内部类可以直接访问外部类的任何字段。
Nevertheless, it means the inner class will hold an invisible reference on its outer class instance : the Activity.
尽管如此,这意味着内部类将在其外部类实例上持有一个不可见的引用:Activity。
On the long run, this produces a memory leak : if the AsyncTask lasts for long, it keeps the activity "alive" whereas Android would like to get rid of it as it can no longer be displayed. The activity can't be garbage collected and that's a central mechanism for Android to preserve resources on the device.
从长远来看,这会导致内存泄漏:如果 AsyncTask 持续很长时间,它会保持活动“活着”,而 Android 则希望摆脱它,因为它无法再显示。活动不能被垃圾收集,这是 Android 保留设备资源的中心机制。
Progress of your task will be lost
你的任务进度将会丢失
You can use some workarounds to create a long running asynctask and manage its life cycle accordingly to the life cycle of the activity. You can either cancel the AsyncTask in the onStop method of your activityor you can let your async task finish, and not loose its progress and relink it to the next instance of your activity.
您可以使用一些变通方法来创建长时间运行的异步任务并根据活动的生命周期管理其生命周期。您可以在您的 Activity 的 onStop 方法中取消 AsyncTask ,也可以让您的异步任务完成,并且不会丢失其进度并将其重新链接到您的 Activity 的下一个实例。
This is possible and we show how in RobopSpice motivations, but it becomes complicated and the code is not really generic. Moreover, you will still loose the progress of your task if the user leaves the activity and comes back. This same issue appears with Loaders, although it would be a simpler equivalent to the AsyncTask with relinking workaround mentionned above.
这是可能的,我们在 RobopSpice 中展示了它的动机,但它变得复杂并且代码不是真正通用的。此外,如果用户离开活动并回来,您仍然会失去任务的进度。加载器也会出现同样的问题,尽管它与上面提到的带有重新链接解决方法的 AsyncTask 更简单。
Using an Android service
使用 Android 服务
The best option is to use a service to execute your long running background tasks. And that is exactly the solution proposed by RoboSpice. Again, it is designed for networking but could be extended to non-network related stuff. This library has a large number of features.
最好的选择是使用服务来执行长时间运行的后台任务。而这正是 RoboSpice 提出的解决方案。同样,它是为网络设计的,但可以扩展到非网络相关的东西。这个库有很多功能。
You can even get an idea of it in less than 30 seconds thanks to an infographics.
借助信息图表,您甚至可以在 30 秒内了解它。
It is really a very very bad idea to use AsyncTasks for long running operations. Nevertheless, they are fine for short living ones such as updating a View after 1 or 2 seconds.
将 AsyncTasks 用于长时间运行的操作确实是一个非常非常糟糕的主意。然而,它们适用于短暂的生命周期,例如在 1 或 2 秒后更新视图。
I encourage you to download the RoboSpice Motivations app, it really explains this in-depth and provides samples and demonstrations of the different ways to do some network related stuff.
我鼓励您下载RoboSpice Motivations 应用程序,它确实深入解释了这一点,并提供了执行某些网络相关内容的不同方法的示例和演示。
If you are looking for an alternative to RoboSpice for non network related tasks (for instance without caching), you could also have a look at Tape.
如果您正在为非网络相关任务(例如没有缓存)寻找 RoboSpice 的替代方案,您还可以查看Tape。
回答by Cagatay Kalan
Romain Guy is right. In fact, an async task is responsible for finishing its own job in any case. Interrupt is not the best way, so you should continuously check if somebody wants you to cancel or stop your task.
罗曼盖是对的。事实上,在任何情况下,异步任务都负责完成自己的工作。中断不是最好的方法,因此您应该不断检查是否有人希望您取消或停止您的任务。
Let's say your AsyncTask
does something in a loop many times. Then you should check isCancelled()
in every loop.
假设您AsyncTask
在循环中多次执行某些操作。然后你应该检查isCancelled()
每个循环。
while ( true ) {
if ( isCancelled())
break;
doTheTask();
}
doTheTask()
is your real job and before you do it in every loop, you check if your task should be cancelled.
doTheTask()
是你真正的工作,在你做每一个循环之前,你检查你的任务是否应该被取消。
Generally you should set a flag in your AsyncTask
class or return an appropriate result from your doInBackground()
so that, in your onPostExecute()
, you can check if you could finish what you want or if your work was cancelled in the middle.
通常,您应该在您的AsyncTask
班级中设置一个标志或从您的班级中返回一个适当的结果,doInBackground()
以便在您onPostExecute()
的 .
回答by Frane Poljak
The following doesn't solve your problem, but prevents it: In the app manifest do this:
以下内容不能解决您的问题,但可以防止它:在应用清单中执行以下操作:
<activity
android:name=".(your activity name)"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden|screenSize" > //this line here
</activity>
When you add this, your activity doesn't reload on configuration change, and if you want to make some changes when orientation changes you just override the following method of the activity:
当您添加此内容时,您的活动不会在配置更改时重新加载,如果您想在方向更改时进行一些更改,您只需覆盖活动的以下方法:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//your code here
}
回答by ralphgabb
the activity is recreated on orientation change, yes thats true. but you can continue you asynctask whenever this event happen.
活动是在方向改变时重新创建的,是的,这是真的。但是无论何时发生此事件,您都可以继续执行异步任务。
you check it on
你检查一下
@Override
protected void onCreate(Bundle savedInstanceState) {
if ( savedInstanceState == null ) {
startAsyncTask()
} else {
// ** Do Nothing async task will just continue.
}
}
-cheers
-干杯
回答by 18446744073709551615
From the MVCviewpoint, Activity is the Controller; it is wrong for the Controllerto perform operations that outlive the View(derived from android.view.View, usually you just reuse the existing classes). Therefore, it should be the Model's responsibility to start AsyncTasks.
从MVC的角度来看,Activity就是Controller;Controller执行比View寿命更长的操作是错误的(从 android.view.View 派生,通常你只是重用现有的类)。因此,启动 AsyncTasks应该是Model的责任。
回答by OzgurGundogan
If asynctask is not in thread pool (parallel processing) you can not stop executing asynctask since it is already being executed by CPU and your stop or cancel command will be executed after CPU is free (cpu is done with asynctask). However it is in thread pool, the tasks will be queued and will be executed one by one. So if your cancel command queued while executing async task , it can stop your async task.
如果 asynctask 不在线程池中(并行处理),您将无法停止执行 asynctask,因为它已被 CPU 执行,并且您的停止或取消命令将在 CPU 空闲后执行(cpu 由 asynctask 完成)。然而它在线程池中,任务将被排队并被一一执行。因此,如果您的取消命令在执行 async task 时排队,它可以停止您的异步任务。
回答by 18446744073709551615
You can use class MagicAppRestart
from this postto kill the processalong with all AsyncTasks; Android will restore the activity stack (the user will not mention anything). It is important to note that the only notification before a process restart is calling onPause()
; according to the Android app lifecycle logic, your application must be ready to such termination anyway.
您可以使用class MagicAppRestart
从这篇文章来杀死进程与所有AsyncTasks一起; Android 将恢复活动堆栈(用户不会提及任何内容)。重要的是要注意,进程重新启动之前的唯一通知是调用onPause()
; 根据Android 应用程序生命周期逻辑,您的应用程序无论如何都必须准备好终止此类应用程序。
I have tried it, and it seems to work. Nevertheless, at the the moment I plan to use "more civilized" methods like weak references from the Application class (my AsyncTasks are rather short-living and hopefully not so much memory-consuming).
我试过了,它似乎有效。尽管如此,目前我计划使用“更文明”的方法,例如来自 Application 类的弱引用(我的 AsyncTasks 相当短暂,希望不会消耗太多内存)。
Here is some code you can play with:
这是您可以使用的一些代码:
MagicAppRestart.java
MagicAppRestart.java
package com.xyz;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
/** This activity shows nothing; instead, it restarts the android process */
public class MagicAppRestart extends Activity {
// Do not forget to add it to AndroidManifest.xml
// <activity android:name="your.package.name.MagicAppRestart"/>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.exit(0);
}
public static void doRestart(Activity anyActivity) {
anyActivity.startActivity(new Intent(anyActivity.getApplicationContext(), MagicAppRestart.class));
}
}
The rest is what Eclipse created for a new Android project for com.xyz.AsyncTaskTestActivity:
其余的是 Eclipse 为com.xyz.AsyncTaskTestActivity的新 Android 项目创建的内容:
AsyncTaskTestActivity.java
异步任务测试活动.java
package com.xyz;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class AsyncTaskTestActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d("~~~~","~~~onCreate ~~~ "+this);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void onStartButton(View view) {
Log.d("~~~~","~~~onStartButton {");
class MyTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
Log.d("~~~~","~~~doInBackground started");
try {
for (int i=0; i<10; i++) {
Log.d("~~~~","~~~sleep#"+i);
Thread.sleep(200);
}
Log.d("~~~~","~~~sleeping over");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.d("~~~~","~~~doInBackground ended");
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
taskDone();
}
}
MyTask task = new MyTask();
task.execute(null);
Log.d("~~~~","~~~onStartButton }");
}
private void taskDone() {
Log.d("~~~~","\n\n~~~taskDone ~~~ "+this+"\n\n");
}
public void onStopButton(View view) {
Log.d("~~~~","~~~onStopButton {");
MagicAppRestart.doRestart(this);
Log.d("~~~~","~~~onStopButton }");
}
public void onPause() { Log.d("~~~~","~~~onPause ~~~ "+this); super.onPause(); }
public void onStop() { Log.d("~~~~","~~~onStop ~~~ "+this); super.onPause(); }
public void onDestroy() { Log.d("~~~~","~~~onDestroy ~~~ "+this); super.onDestroy(); }
}
main.xml
主文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button android:text="Start" android:onClick="onStartButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<Button android:text="Stop" android:onClick="onStopButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
</LinearLayout>
AndroidManifest.xml
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xyz"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".AsyncTaskTestActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MagicAppRestart"/>
</application>
</manifest>
and a relevant part of the logs (note that only onPause
is called):
以及日志的相关部分(请注意,仅onPause
称为):
D/~~~~ (13667): ~~~onStartButton {
D/~~~~ (13667): ~~~onStartButton }
D/~~~~ (13667): ~~~doInBackground started
D/~~~~ (13667): ~~~sleep#0
D/~~~~ (13667): ~~~sleep#1
D/~~~~ (13667): ~~~sleep#2
D/~~~~ (13667): ~~~sleep#3
D/~~~~ (13667): ~~~sleep#4
D/~~~~ (13667): ~~~sleep#5
D/~~~~ (13667): ~~~sleep#6
D/~~~~ (13667): ~~~sleep#7
D/~~~~ (13667): ~~~sleep#8
D/~~~~ (13667): ~~~sleep#9
D/~~~~ (13667): ~~~sleeping over
D/~~~~ (13667): ~~~doInBackground ended
D/~~~~ (13667):
D/~~~~ (13667):
D/~~~~ (13667): ~~~taskDone ~~~ com.xyz.AsyncTaskTestActivity@40516988
D/~~~~ (13667):
D/~~~~ (13667): ~~~onStartButton {
D/~~~~ (13667): ~~~onStartButton }
D/~~~~ (13667): ~~~doInBackground started
D/~~~~ (13667): ~~~sleep#0
D/~~~~ (13667): ~~~sleep#1
D/~~~~ (13667): ~~~sleep#2
D/~~~~ (13667): ~~~sleep#3
D/~~~~ (13667): ~~~sleep#4
D/~~~~ (13667): ~~~sleep#5
D/~~~~ (13667): ~~~onStopButton {
I/ActivityManager( 81): Starting: Intent { cmp=com.xyz/.MagicAppRestart } from pid 13667
D/~~~~ (13667): ~~~onStopButton }
D/~~~~ (13667): ~~~onPause ~~~ com.xyz.AsyncTaskTestActivity@40516988
I/ActivityManager( 81): Process com.xyz (pid 13667) has died.
I/WindowManager( 81): WIN DEATH: Window{4073ceb8 com.xyz/com.xyz.AsyncTaskTestActivity paused=false}
I/ActivityManager( 81): Start proc com.xyz for activity com.xyz/.AsyncTaskTestActivity: pid=13698 uid=10101 gids={}
I/ActivityManager( 81): Displayed com.xyz/.AsyncTaskTestActivity: +44ms (total +65ms)
D/~~~~ (13698): ~~~onCreate ~~~ com.xyz.AsyncTaskTestActivity@40517238