即使活动已经销毁,AsyncTask也不会停止

时间:2010-03-27 23:33:14

标签: android android-asynctask

我有一个AsyncTask对象,它在创建活动时开始执行,并在后台执行操作(下载最多100个图像)。一切都很好,但有一种我不能理解的特殊行为。

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

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

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

由于

7 个答案:

答案 0 :(得分:139)

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

AsyncTasks专为在后台进行操作而设计。是的,您可以使用cancel方法停止它。当您从互联网上下载内容时,我强烈建议您take care of your thread when it is the IO blocking state。您应按照以下方式组织下载:

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标志将帮助您的线程正确退出阻塞状态。您的线程对cancel方法的调用响应更快。

AsyncTask设计缺陷

但如果您的AsyncTask持续时间过长,那么您将面临两个不同的问题:

  1. 活动与活动生命周期关系不大,如果活动中断,您将无法获得AsyncTask的结果。的确,是的,你可以,但这将是粗糙的方式。
  2. AsyncTask没有很好的文档记录。一个天真但直观的实现和使用asynctask可能很快导致内存泄漏。
  3. RoboSpice,我想介绍的库,使用后台服务来执行这种请求。它专为网络请求而设计。它提供了其他功能,例如自动缓存请求的结果。

    以下是AsyncTasks对长时间运行的任务不利的原因。以下推理是对RoboSpice motivations的版本的改编:该应用程序解释了为什么使用RoboSpice满足Android平台的需求。

    AsyncTask和Activity生命周期

    AsyncTasks不遵循Activity实例的生命周期。如果在Activity中启动AsyncTask并旋转设备,则将销毁Activity并创建新实例。但AsyncTask不会死。它会继续生存直到它完成。

    完成后,AsyncTask将不会更新新Activity的UI。实际上,它更新了之前的活动实例 不再显示。这可能导致java.lang.IllegalArgumentException类型的异常:如果你没有附加到窗口管理器的视图 例如,使用findViewById来检索Activity内的视图。

    内存泄漏问题

    将AsyncTasks创建为活动的内部类非常方便。因为AsyncTask需要操纵视图 当Activity完成或正在进行时,使用Activity的内部类似乎很方便:内部类可以 直接访问外部类的任何字段。

    然而,这意味着内部类将在其外部类实例上保存一个不可见的引用:Activity。

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

    您的任务进度将丢失

    您可以使用一些变通方法来创建长时间运行的异步任务,并根据活动的生命周期管理其生命周期。你可以cancel the AsyncTask in the onStop method of your activity或者你可以让你的异步任务完成,而不是放松它的进度relink it to the next instance of your activity

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

    使用Android服务

    最好的选择是使用服务来执行长时间运行的后台任务。这正是RoboSpice提出的解决方案。同样,它专为网络设计,但可以扩展到非网络相关的东西。该库有一个large number of features

    由于infographics,您甚至可以在30秒内了解它。


    将AsyncTasks用于长时间运行操作真的是一个非常糟糕的主意。然而,它们适用于短期生活,例如在1或2秒后更新视图。

    我鼓励您下载RoboSpice Motivations app,它确实深入地解释了这一点,并提供了有关网络相关内容的不同方法的示例和演示。


    如果您正在为非网络相关任务(例如没有缓存)寻找RoboSpice的替代方案,您还可以查看Tape

答案 1 :(得分:13)

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

假设您的AsyncTask多次在循环中执行某些操作。然后你应该在每个循环中检查isCancelled()

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

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

一般情况下,您应在AsyncTask课程中设置一个标记,或从doInBackground()返回相应的结果,以便在onPostExecute()中,您可以检查是否可以完成所需内容或者如果你的工作在中间取消了。

答案 2 :(得分:1)

以下内容并不能解决您的问题,但会阻止它: 在app清单中执行以下操作:

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

添加此项时,您的活动不会在配置更改时重新加载,如果您想在方向更改时进行一些更改,则只需覆盖以下活动方法:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

        //your code here
    }

答案 3 :(得分:1)

在方向更改时重新创建活动,是的,这是真的。 但是只要发生这种事件,你就可以继续asynctask。

你在

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

-cheers

答案 4 :(得分:-1)

MVC 的角度来看,Activity是 Controller ; Controller 执行的操作比 View (从android.view.View派生,通常只是重用现有的类)更糟糕。因此,启动AsyncTasks应该是 Model 的责任。

答案 5 :(得分:-2)

如果asynctask不在线程池(并行处理)中,则无法停止执行asynctask,因为它已经由CPU执行,并且在CPU空闲后执行stop或cancel命令(cpu使用asynctask完成)。但是它在线程池中,任务将排队并将逐个执行。因此,如果您的cancel命令在执行异步任务时排队,它可以停止您的异步任务。

答案 6 :(得分:-4)

您可以使用this post中的class MagicAppRestart 杀死进程 以及所有AsyncTasks; Android将恢复活动堆栈(用户不会提及任何内容)。请务必注意,在重新启动流程之前,唯一的通知是调用onPause();根据{{​​3}},您的申请无论如何都必须准备好终止。

我试过了,它似乎有效。尽管如此,目前我计划使用“更文明”的方法,比如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项目创建的内容:

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