前言
在java的线程中,没有办法停止一个正在运行中的线程。在Android的AsyncTask中也是一样的。如果必须要停止一个线程,调用cancel,然后在线程run方法或AsyncTask的doInBackground方法中的关键步骤判断是否调用了cancel以决定是否继续执行。然后在需要终止此线程的地方改变这个标志位以达到停止线程的目的。
为什么要cancel线程呢?
比如有若干个页面,每个页面打开的时候都开启一个AsyncTask,每个线程都特别慢,当我们频繁切换页面的时候,可能上一个页面还没执行完,这时候下一个页面的线程就得等待,造成线程堵塞,有人可能会说把串行改成并行就行了呀,对,这个想法好,但是呢并行也有数量限制,还有就是如果页面关了,线程还在后台执行,但我不关心了,这时候就相当于无用操作了,还浪费资源,所以,线程不用的我们要及时的cancel。
1.生命周期
很多人认为一个在Activity中的AsyncTask会随着Activity的销毁而销毁。然后事实并非如此。AsyncTask会一直执行doInBackground()方法直到方法执行结束。一旦上述方法结束,会依据情况进行不同的操作。
•如果cancel(boolean)调用了,则执行onCancelled(Result)方法
•如果cancel(boolean)没有调用,则执行onPostExecute(Result)方法
AsyncTask的cancel方法需要一个布尔值的参数,参数名为mayInterruptIfRunning,意思是如果正在执行是否可以打断,如果这个值设置为true,表示这个任务可以被打断,否则,正在执行的程序会继续执行直到完成。如果在doInBackground()方法中有一个循环操作,我们应该在循环中使用isCancelled()来判断,如果返回为true,我们应该避免执行后续无用的循环操作。
2.不好好工作的cancel()
简而言之的答案,有时候起作用。
如果你调用了AsyncTask的cancel(false),doInBackground()仍然会执行到方法结束,只是不会去调用onPostExecute()方法。但是实际上这是让应用程序执行了没有意义的操作。那么是不是我们调用cancel(true)前面的问题就能解决呢?并非如此。如果mayInterruptIfRunning设置为true,会使任务尽早结束,但是如果的doInBackground()有不可打断的方法会失效,比如这个BitmapFactory.decodeStream()IO操作。但是你可以提前关闭IO流并捕获这样操作抛出的异常。但是这样会使得cancel()方法没有任何意义。
3.内存泄露
还有一种常见的情况就是,在Activity中使用非静态匿名内部AsyncTask类,由于Java内部类的特点,AsyncTask内部类会持有外部类的隐式引用。由于AsyncTask的生命周期可能比Activity的长,当Activity进行销毁AsyncTask还在执行时,由于AsyncTask持有Activity的引用,导致Activity对象无法回收,进而产生内存泄露。
4.结果丢失
另一个问题就是在屏幕旋转等造成Activity重新创建时AsyncTask数据丢失的问题。当Activity销毁并重新创建后,还在运行的AsyncTask会持有一个Activity的非法引用即之前的Activity实例。导致onPostExecute()没有任何作用。
那么怎么关闭呢?
通过api,我们会发现一个AsyncTask.cancel(true);
<p>Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.</p>
*
* <p>Calling this method willresult in {@link#onCancelled(Object)} being
* invoked on the UI thread after {@link #doInBackground(Object[])}
* returns. Calling this method guarantees that {@link #onPostExecute(Object)}
* is never invoked. After invoking this method, you should check the
* value returned by {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])} to finish the task as early as
* possible.</p>
什么意思呢?
1、 如果task已完成,已取消,或者由于其它原因不能被取消,cancel可能失败
2、 如果没有开始,cancel会成功,并且task不再执行
3、 cancel调用成功将会导致onCancelled执行,并且onPostExecute不执行
4、 最好在doInBackground方法里判断isCancelled(),以便于task尽早的结束
public class AsyncTaskActivity extends AppCompatActivity {
private MyAsyncTask myAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myAsyncTask = new MyAsyncTask();
}
private class MyAsyncTask extends AsyncTask<Integer, Integer, Void> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Void doInBackground(Integer... params) {
for (int i = 0; i < 10;i ++) {
if(isCancelled()){
break;
}
try {
publishProgress(i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (isCancelled()) {
return null;
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
}
@Override
protected void onCancelled(Void aVoid) {
super.onCancelled(aVoid);
}
}
@Override
protected void onPause() {
if(myAsyncTask !=null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING){
myAsyncTask.cancel(true);
myAsyncTask = null;
}
super.onPause();
}
}