77

我有一个AsyncTask对象,它在创建时开始执行Activity并在后台执行操作(最多下载 100 张图像)。一切正常,但有一种我无法理解的特殊行为。

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

我什至试图取消活动AsyncTaskonDestroy()方法,但日志仍然显示AsyncTask为正在运行。

这是一种非常奇怪的行为,如果有人能告诉我停止/取消AsyncTask.

4

6 回答 6

146

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

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
}

使用该Thread.interrupted标志将帮助您的线程正确退出阻塞 io 状态。您的线程将对cancel方法的调用更加敏感。

AsyncTask 设计缺陷

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

  1. 活动与活动生命周期的联系很差,如果活动终止,您将无法获得 AsyncTask 的结果。确实,是的,你可以,但这将是一种粗略的方式。
  2. AsyncTask 没有很好的文档记录。asynctask 的简单但直观的实现和使用会很快导致内存泄漏。

我想介绍的库RoboSpice使用后台服务来执行此类请求。它专为网络请求而设计。它提供了附加功能,例如请求结果的自动缓存。

这就是为什么 AsyncTasks 不适合长时间运行的任务的原因。以下推理改编自 RoboSpice动机的摘录:解释为什么使用 RoboSpice 的应用程序满足了 Android 平台上的需求。

AsyncTask 和 Activity 生命周期

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

当它完成时,AsyncTask 不会更新新 Activity 的 UI。实际上,它会更新不再显示的活动的前一个实例。这可能会导致 java.lang.IllegalArgumentException: View not attach to window manager 类型的异常,例如,如果您使用 findViewById 来检索 Activity 中的视图。

内存泄漏问题

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

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

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

您的任务进度将丢失

您可以使用一些变通方法来创建一个长时间运行的异步任务,并根据活动的生命周期管理其生命周期。您可以在活动的 onStop 方法中取消 AsyncTask ,也可以让您的异步任务完成,并且不会丢失其进度并将其重新链接到您的下一个活动实例

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

使用 Android 服务

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

借助信息图表,您甚至可以在 30 秒内了解它。


将 AsyncTasks 用于长时间运行的操作确实是一个非常非常糟糕的主意。不过,它们适用于短暂的生命周期,例如在 1 或 2 秒后更新视图。

我鼓励您下载RoboSpice Motivations 应用程序,它确实深入地解释了这一点,并提供了一些与网络相关的不同方法的示例和演示。


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

于 2012-10-30T21:41:53.930 回答
16

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

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

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

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

通常,您应该在班级中设置一个标志AsyncTask或从您的班级返回适当的结果,doInBackground()以便在您的班级onPostExecute()中检查您是否可以完成您想要的工作,或者您的工作是否在中间被取消。

于 2011-06-04T18:34:56.147 回答
1

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

    <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
    }
于 2013-10-15T12:36:59.470 回答
1

活动是在方向改变时重新创建的,是的,这是真的。但是只要发生此事件,您就可以继续执行异步任务。

你检查一下

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

-干杯

于 2015-01-13T02:23:51.193 回答
-1

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

于 2013-01-31T05:46:16.880 回答
-3

您可以使用class MagicAppRestart这篇文章中的所有 AsyncTask 来终止该进程;Android 将恢复活动堆栈(用户不会提及任何内容)。需要注意的是,进程重启前的唯一通知是调用onPause();根据Android 应用程序生命周期逻辑,无论如何,您的应用程序都必须准备好终止。

我已经尝试过了,它似乎有效。尽管如此,目前我计划使用“更文明”的方法,例如来自 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(); }
}

主要的.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
于 2013-01-30T05:09:36.357 回答