即使活动已经被破坏,AsyncTask也不会停止

我有一个AsyncTask对象,它在创build活动时开始执行,并在后台执行任务(最多下载100个图像)。 一切正常,但有这种奇特的行为,我不明白。

例如:当android屏幕的方向改变时,活动被破坏并重新创build。 所以我重写了onRetainNonConfigurationInstance()方法,并保存在AsyncTask中执行的所有下载的数据。 我这样做的目的是在定向更改期间,每次活动被销毁时都不会运行AsyncTask,但正如我在日志中所看到的,以前的AsyncTask仍在执行中。 (虽然数据保存正确)

我甚至试图取消活动的onDestroy()方法中的AsyncTask,但是日志仍然显示AsyncTask正在运行。

这真是奇怪的行为,如果有人能告诉我停止/取消AsyncTask的正确过程,真的很感激。

谢谢

AFAIK,你不能停止一个AsyncTask 。 我不相信,如果任务已经在执行,那么cancel()方法就会工作 – 如果任务正在一个队列中等待一个线程释放,那么这个方法就行了。

@Romain Guy给出的答案是正确的。 不过,我想添加一个补充的信息,给一个库或2的指针,可以用于长期运行的AsyncTask,甚至更多的面向networking的asynctasks。

AsyncTasks被devise用于在后台执行任务。 是的,你可以使用cancel方法来停止它。 当你从互联网上下载东西时,我强烈build议你在线程处于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 } 

使用Thread.interrupted标志将有助于您的线程正确退出阻塞的io状态。 您的线程将更有效地调用cancel方法。

AsyncTaskdevise缺陷

但是,如果你的AsyncTask持续太久,那么你将面临两个不同的问题:

  1. 活动与活动生命周期关系不大,如果活动消失,您将无法获得AsyncTask的结果。 事实上,是的,你可以,但这将是粗糙的方式。
  2. AsyncTask没有很好的logging。 一个天真,虽然直观,实施和使用一个asynctask可以很快导致内存泄漏。

RoboSpice ,我想介绍的库,使用后台服务来执行这种请求。 它被devise用于networking请求。 它提供了附加function,例如自动caching请求结果。

这是AsyncTasks对于长时间运行任务不好的原因。 下面的理由是RoboSpice动机的改编:这个应用解释了为什么使用RoboSpice来满足Android平台的需求。

AsyncTask和Activity生命周期

AsyncTasks不遵循Activity实例的生命周期。 如果你在一个Activity里面启动了一个AsyncTask,并且你旋转了这个设备,这个Activity将被销毁,并且一个新的实例将被创build。 但是AsyncTask不会死。 它会继续活着直到完成。

完成后,AsyncTask将不会更新新Activity的UI。 事实上,它更新了以前不再显示的活动实例。 这可能会导致types为java.lang.IllegalArgumentException的exception:如果您使用findViewById来检索Activity中的视图,则视图不会附加到窗口pipe理器。

内存泄漏问题

创buildAsyncTasks作为活动的内部类是非常方便的。 由于AsyncTask需要在任务完成或正在进行时操作Activity的视图,因此使用Activity的内部类似乎很方便:内部类可以直接访问外部类的任何字段。

尽pipe如此,这意味着内部类将在其外部类实例上持有一个无形的参考:活动。

从长远来看,这会产生一个内存泄漏:如果AsyncTask持续了很长时间,它会使活动保持“活跃”,而Android则希望摆脱它,因为它不能再显示。 该活动不能被垃圾收集,这是Android保存设备资源的中心机制。

你的任务进度将会丢失

您可以使用一些解决方法来创build长时间运行的asynchronous应用程序,并根据活动的生命周期pipe理其生命周期。 您可以在活动的onStop方法中取消AsyncTask,也可以让asynchronous任务完成,而不是放松其进度,并将其重新链接到下一个活动实例 。

这是可能的,我们展示了如何RobopSpice动机,但它变得复杂,代码是不是真正的通用。 而且,如果用户离开活动并返回,您仍然会放松任务的进度。 同样的问题也出现在Loaders上,虽然它与上面提到的重新链接解决方法的AsyncTask相当简单。

使用Android服务

最好的select是使用服务来执行长时间运行的后台任务。 这正是RoboSpice提出的解决scheme。 同样,它是为networkingdevise的,但可以扩展到非networking相关的东西。 这个库有大量的function 。

由于信息图表,您甚至可以在不到30秒的时间内了解它。


对于长时间运行的操作,使用AsyncTasks确实是一个非常糟糕的主意。 尽pipe如此,对于短暂的生活,比如在1到2秒之后更新一个View,这些都是不错的select。

我鼓励你下载RoboSpice Motivations应用程序 ,它真正解释了这一点,并提供了不同的方式来做一些networking相关的东西的示例和演示。


如果您正在寻找RoboSpice的非networking相关任务(例如没有caching)的替代方法,那么您也可以看看磁带 。

罗曼·盖伊是对的。 事实上,任何情况下,asynchronous任务都要负责完成自己的工作。 中断不是最好的方法,所以你应该不断检查是否有人要你取消或停止你的任务。

假设你的AsyncTask在循环中做了许多事情。 那么你应该在每个循环中检查isCancelled()

 while ( true ) { if ( isCancelled()) break; doTheTask(); } 

doTheTask()是你真正的工作,在每一个循环之前,你都要检查你的任务是否应该被取消。

一般来说,你应该在你的AsyncTask类中设置一个标志,或者从doInBackground()返回一个合适的结果,这样在你的onPostExecute() ,你可以检查你是否能完成你想要的工作,或者你的工作是否被取消了。

以下不能解决您的问题,但阻止它:在应用程序清单中执行此操作:

  <activity android:name=".(your activity name)" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden|screenSize" > //this line here </activity> 

当你添加这个时,你的活动不会在configuration改变时重新加载,如果你想改变方向时做一些改变,你可以重写下面的活动方法:

 @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); //your code here } 

活动重新定位改变,是的,这是真的。 但是当这个事件发生的时候,你可以继续你的异想天开。

你检查它

 @Override protected void onCreate(Bundle savedInstanceState) { if ( savedInstanceState == null ) { startAsyncTask() } else { // ** Do Nothing async task will just continue. } } 

-干杯

如果asynctask不在线程池(并行处理)中,你不能停止执行asynctask,因为它已经被CPU执行了,你的停止或取消命令将在CPU空闲后执行(cpu由asynctask完成)。 但是它在线程池中,这些任务将被排队并逐一执行。 所以如果你的cancel命令在执行asynchronous任务时排队,它可以停止你的asynchronous任务。

MVC的angular度来看,Activity是Controller ; 控制器执行超过View的操作是错误的(从android.view.View派生,通常你只是重用现有的类)。 因此,应该是Model启动AsyncTasks的责任。

你可以使用这个post中的 class MagicAppRestart杀死所有的AsyncTasks; Android会恢复活动堆栈(用户不会提到任何东西)。 重要的是要注意,重启进程之前的唯一通知是调用onPause() ; 根据Android应用程序生命周期逻辑 ,您的应用程序必须准备好这样的终止。

我已经尝试过了,它似乎工作。 尽pipe如此,目前我打算使用“更文明”的方法,比如Application类中的弱引用(我的AsyncTasks比较短,希望没有那么多的内存消耗)。

这里是一些你可以玩的代码:

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)); } } 

剩下的就是Eclipse为com.xyz.AsyncTaskTestActivity的一个新的Android项目创build的东西:

AsyncTaskTestActivity.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中

 <?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> 

和日志的相关部分(注意只有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