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


76

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

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

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

这确实是一种奇怪的行为,如果有人可以告诉我停止/取消AsyncTask的正确过程,这将非常感谢。

谢谢

Answers:


144

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

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没有很好的文档。天真(虽然直观)的异步任务实现和使用会迅速导致内存泄漏。

我想介绍的RoboSpice库使用后台服务执行这种请求。它是为网络请求而设计的。它提供了其他功能,例如自动缓存请求的结果。

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

AsyncTask和Activity生命周期

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

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

内存泄漏问题

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

但是,这意味着内部类将在其外部类实例(活动)上保留一个不可见的引用。

从长远来看,这会导致内存泄漏:如果AsyncTask持续很长时间,它将使活动保持“活跃”状态,而Android希望摆脱它,因为它不再显示。该活动不能被垃圾收集,这是Android在设备上保留资源的主要机制。

您的任务进度将丢失

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

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

使用Android服务

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

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


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

我鼓励您下载RoboSpice Motivations应用程序,它确实对此进行了深入的解释,并提供了一些示例和示范,以做一些与网络相关的事情。


如果您正在寻找RoboSpice的替代品来解决与网络无关的任务(例如,不进行缓存),则还可以查看Tape


1
@Snicolas,您可以添加droidQuery作为替代RoboSpiceGithub的页面?我去年开发了它,并且除其他功能外,还提供了使用jQuery风格的Ajax(用纯Android Java编写)执行网络任务的功能。
Phil

@Phil,完成了。链接的Thx。顺便说一句,您的语法确实非常接近Ion(后来出现)。
Snicolas 2013年

@Snicolas,谢谢!看起来droidQueryIon都从Picasso进行了一些语法设计。github.com/koush/ion/search?q=picasso&ref=cmdform ;)
菲尔-

“对于长时间运行的操作使用AsyncTasks确实是一个非常非常糟糕的主意。但是,对于短暂的操作(例如在1或2秒后更新View),它们是很好的选择。” 。我认为这不一定是真的。Android建议这样做的原因是AsyncTask使用全局线程池执行程序。您可以通过execute方法简单地传递自定义线程池执行程序,从而摆脱可能的阻塞问题。尽管AsyncTask不是您提到的“泄漏”的灵丹妙药。
stdout

问题不在于游泳池,实际上是泄漏。
尼克斯

16

罗曼·盖伊(Romain Guy)是对的。实际上,无论如何,异步任务负责完成其自身的工作。中断不是最好的方法,因此您应该不断检查是否有人要您取消或停止任务。

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

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

doTheTask() 是您的真正工作,在每个循环中执行此操作之前,请检查是否应取消任务。

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


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
    }

o,...一旦改变设备的方向,该方法将被调用?@FranePoljak ... ic,ic,...
gumuruh 2014年

1

该活动是根据方向变化重新创建的,是的。但只要发生此事件,您就可以继续执行异步任务。

你检查一下

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

-干杯


-1

MVC的角度来看,活动是控制者Controller执行超出View寿命的操作是错误的(派生自android.view.View,通常仅重用现有类)。因此,启动AsyncTasks应该是Model的责任。


-4

您可以使用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项目创建的内容

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

在2013年(Android 2.x时代)回答,在2017年(Android 6和7时代)投票。很多必须自那时以来已经改变了,我知道,应用程序重启黑客并没有由于Android 4.工作
18446744073709551615
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.