我有一个AsyncTask对象,它在创建活动时开始执行,并在后台执行操作(下载最多100个图像)。一切都很好,但有一种我不能理解的特殊行为。
例如:当android屏幕的方向发生变化时,活动将被销毁并再次创建。所以我重写onRetainNonConfigurationInstance()方法并保存在AsyncTask中执行的所有下载数据。我这样做的目的是每次在方向更改期间销毁活动时都不会运行AsyncTask,但正如我在日志中看到的那样,之前的AsyncTask仍然在执行。 (虽然数据保存正确)
我甚至尝试在活动的onDestroy()方法中取消AsyncTask,但日志仍然显示AsyncTask正在运行。
这真是一种奇怪的行为,如果有人能告诉我停止/取消AsyncTask的正确程序,我真的很感激。
由于
答案 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持续时间过长,那么您将面临两个不同的问题:
RoboSpice,我想介绍的库,使用后台服务来执行这种请求。它专为网络请求而设计。它提供了其他功能,例如自动缓存请求的结果。
以下是AsyncTasks对长时间运行的任务不利的原因。以下推理是对RoboSpice motivations的版本的改编:该应用程序解释了为什么使用RoboSpice满足Android平台的需求。
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相比更简单。
最好的选择是使用服务来执行长时间运行的后台任务。这正是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