繁体   English   中英

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

[英]AsyncTask won't stop even when the Activity has destroyed

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

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

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

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

@Romain Guy 给出的答案是正确的。 不过,我想补充一些信息,并给出一个或 2 个库的指针,该库可用于长时间运行的 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 生命周期

AsyncTask 不遵循 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 则希望摆脱它,因为它无法再显示。 活动不能被垃圾收集,这是 Android 保留设备资源的中心机制。

你的任务进度将会丢失

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

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

使用 Android 服务

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

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


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

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


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

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

假设您的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>

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

@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.
     }
}

-干杯

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

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

您可以使用class MagicAppRestart这个帖子杀死所有AsyncTasks沿着过程; 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 项目创建的内容:

异步任务测试活动.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 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM