浅谈android中异步加载之"取消异步加载"二
Posted 熊喵先生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈android中异步加载之"取消异步加载"二相关的知识,希望对你有一定的参考价值。
首先,我得解释一下为什么我的标题取消异步加载打引号,这是因为可能最后实现效果并不是你自己想象中的那样。大家看取消异步加载,这不是很简单吗?AsyncTask中不是有一个cancel方法吗?直接调用该方法不就行了吗?但是事实上是这样的吗?如果真是这样,我相信我就没有以写这个作为一篇博客的必要了。为什么会有这样的想法呢?实际上源于我上一篇中Demo中的一个BUG,然后解决该BUG,需要去取消异步任务,是怎么样,我们不妨来看看。
首先,还是来一起回顾一下上篇博客中加载进度条Demo吧。
AsyncTask子类:
01.package com.mikyou.utils;
02.
03.import android.os.AsyncTask;
04.import android.widget.ProgressBar;
05.import android.widget.TextView;
06.
07.public class MikyouAsyncTaskProgressBarUtils extends AsyncTask<Void, Integer, String>{
08. private TextView tv;
09. private ProgressBar bar;
10. public MikyouAsyncTaskProgressBarUtils(TextView tv,ProgressBar bar){//这里我就采用构造器将TextView,ProgressBar直接传入,然后在该类中直接更新UI
11. this.bar=bar;
12. this.tv=tv;
13.
14. }
15. @Override
16. protected String doInBackground(Void... params) {
17. for(int i=1;i<101;i++){
18. try {
19. Thread.sleep(1000);
20. } catch (InterruptedException e) {
21. e.printStackTrace();
22. }
23. publishProgress(i);
24. }
25. return "下载完成";
26. }
27. @Override
28. protected void onProgressUpdate(Integer... values) {
29. bar.setProgress(values[0]);
30. tv.setText("下载进度:"+values[0]+"%");
31. super.onProgressUpdate(values);
32. }
33. @Override
34. protected void onPostExecute(String result) {
35. tv.setText(result);
36. super.onPostExecute(result);
37. }
38.}
MainActivity
01.package com.mikyou.asynctask;
02.
03.import com.mikyou.utils.MikyouAsyncTaskProgressBarUtils;
04.
05.import android.app.Activity;
06.import android.os.Bundle;
07.import android.view.View;
08.import android.widget.Button;
09.import android.widget.ProgressBar;
10.import android.widget.TextView;
11.
12.public class MainActivity extends Activity {
13. private Button downLoad;
14. private ProgressBar bar;
15. private TextView tv;
16. @Override
17. protected void onCreate(Bundle savedInstanceState) {
18. super.onCreate(savedInstanceState);
19. setContentView(R.layout.activity_main);
20. initView();
21. }
22. private void initView() {
23. bar=(ProgressBar) findViewById(R.id.bar);
24. tv=(TextView) findViewById(R.id.tv);
25. }
26. public void download(View view){
27. MikyouAsyncTaskProgressBarUtils mikyouAsyncTaskProgressBarUtils=new MikyouAsyncTaskProgressBarUtils(tv, bar);
28. mikyouAsyncTaskProgressBarUtils.execute();
29. }
30.
31.}
运行结果的Demo的BUG:
通过分析上面的demo会发现:
当我们点击开始下载后,进度条就开始更新了,然后就在更新中途退出Activity,然后再次进入Activity,点击开始下载会发现,等了好一会,进度条才开始更新。
原因:这并不是本程序的一个BUG,而是异步加载的一个机制,因为异步加载从源码中我们可以得出,它底层的实现还是Thread或者Thread-pool+Handler机制
那么它的机制就是如果当前开始的线程没有执行完毕,其他的线程必须等待,这也就解释了为什么说异步加载是安全,因为可以基于该机制避免了线程同步带来的安全问题。
解决办法:其实很简单的就是将异步加载的生命周期和我们的Activity的生命周期进行绑定,当我第一次在中途中断的时候,退出Activity时会调用OnPause
方法,只需要在OnPause方法中顺便把本次中断的异步任务取消即可,也即把当前的线程池中的线程给取消了,当下次重新进入的时候,线程池中并没有
其他的子线程,那么它就无需等待其他的线程了,直接运行。
通过以上的分析,我们就可以得到了一个解决办法,那就是如何去取消当前的异步任务,那就我们就按照大家的一致想法使用cancel方法来终止异步任务。
来了看看是否可以达到我们想要的效果呢??一起来看看
package com.mikyou.asynctask;
import com.mikyou.utils.MikyouAsyncTaskProgressBarUtils;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button downLoad;
private ProgressBar bar;
private TextView tv;
private MikyouAsyncTaskProgressBarUtils mTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
bar=(ProgressBar) findViewById(R.id.bar);
tv=(TextView) findViewById(R.id.tv);
}
public void download(View view){
mTask=new MikyouAsyncTaskProgressBarUtils(tv, bar);
mTask.execute();
}
//将异步任务的生命周期与Activity进行绑定
@Override
protected void onPause() {
//判断当前的异步任务是否为空,并且判断当前的异步任务的状态是否是运行状态{RUNNING(运行),PENDING(准备),FINISHED(完成)}
if (mTask!=null&&mTask.getStatus()==AsyncTask.Status.RUNNING) {
/**
*cancel(true) 取消当前的异步任务,传入的true,表示当中断异步任务时继续已经运行的线程的操作,
*但是为了线程的安全一般为让它继续设为true
* */
mTask.cancel(true);
}
super.onPause();
}
}
运行结果:
大家仔细看下,发现好像我们加的cancel方法并没有什么用,这也就我这篇需要讲的了,
为什么无法去停止一个异步任务,这是为什么呢?
注意:这是因为cancel方法只是发出一个请求取消异步任务的信号, 将对应当前的异步任务标记为CANCEL状态,而并不是真正取消线程的执行,而此时异步任务中的线程仍然在执行并没有结束,所以效果依然是这样的,并且在java中我们是无法直接暴力将一个线程给停止掉 既然我们知道无法去取消一个已经正在运行的线程,但是我们如何去解决这个BUG呢?在异步任务中还给我们提供一个isCanceled的回调方法,也就是当我已经给当前的异步任,调用了cancel(true)方法,发出一个请求取消异步任务的信号,那么此时的isCanceled的回调方法会直接返回一个true,那么我们就可以通过判断当前异步任务isCanceled是否为true,来终止线程中的操作而不是去终止线程,从而达到了界面显示好像线程中的操作被终止了,而实际上该线程依然在运行
因为我们是无法去彻彻底底地采用暴力的方法直接kill一个线程,所以我们不能直接去取消一个异步任务,但是我们可以通过调用cancel方法来发送一个取消异步任务的请求信号,这时候就会给mCanceled的值设置为true,标记为该异步任务是取消了,通过回调方法isCanceled来返回一个true,表示该异步任务取消。如果有疑问我们来看下cancel反方法的源码就知道了。
/**
* <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 will result 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>
*
* @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
* task should be interrupted; otherwise, in-progress tasks are allowed
* to complete.
*
* @return <tt>false</tt> if the task could not be cancelled,
* typically because it has already completed normally;
* <tt>true</tt> otherwise
*
* @see #isCancelled()
* @see #onCancelled(Object)
*/
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
会看mCanceled会设置为true,该值将会通过isCanceled方法回调传出去。所以,我们采用这样一个想法,既然我们不能去终止一个线程,那么我们可以间接解决这个bug
通过判断该值直接终止线程中的操作,而不是去终止线程。
package com.mikyou.asynctask;
import com.mikyou.utils.MikyouAsyncTaskProgressBarUtils;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button downLoad;
private ProgressBar bar;
private TextView tv;
private MikyouAsyncTaskProgressBarUtils mTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
bar=(ProgressBar) findViewById(R.id.bar);
tv=(TextView) findViewById(R.id.tv);
}
public void download(View view){
mTask=new MikyouAsyncTaskProgressBarUtils(tv, bar);
mTask.execute();
}
//将异步任务的生命周期与Activity进行绑定
@Override
protected void onPause() {
//判断当前的异步任务是否为空,并且判断当前的异步任务的状态是否是运行状态{RUNNING(运行),PENDING(准备),FINISHED(完成)}
if (mTask!=null&&mTask.getStatus()==AsyncTask.Status.RUNNING) {
/**
*cancel(true) 取消当前的异步任务,传入的true,表示当中断异步任务时继续已经运行的线程的操作,
*但是为了线程的安全一般为让它继续设为true
* */
mTask.cancel(true);
/**
* 但是重新运行后会发现还是不能起到效果,
* 注意:这是因为cancel方法只是发出一个请求取消异步任务的信号,
* 将对应当前的异步任务标记为CANCEL状态,而并不是真正取消线程的执行,
* 而此时异步任务中的线程仍然在执行并没有结束
* 所以效果依然是这样的,并且在java中我们是无法直接暴力将一个线程给停止掉
* 既然我们知道无法去取消一个已经正在运行的线程,但是我们如何去解决这个BUG呢?
* 在异步任务中还给我们提供一个isCanceled的回调方法,也就是当我已经给当前的异步任务
* 调用了cancel(true)方法,发出一个请求取消异步任务的信号,那么此时的isCanceled的回调方法
* 会直接返回一个true,那么我们就可以通过判断当前异步任务isCanceled是否为true,来终止
* 线程中的操作而不是去终止线程,从而达到了界面显示好像线程中的操作被终止了,而实际上
* 该线程依然在运行
* */
}
super.onPause();
}
}
AsyncTask的子类
package com.mikyou.utils;
import android.os.AsyncTask;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MikyouAsyncTaskProgressBarUtils extends AsyncTask<Void, Integer, String>{
private TextView tv;
private ProgressBar bar;
public MikyouAsyncTaskProgressBarUtils(TextView tv,ProgressBar bar){//这里我就采用构造器将TextView,ProgressBar直接传入,然后在该类中直接更新UI
this.bar=bar;
this.tv=tv;
}
@Override
protected String doInBackground(Void... params) {
for(int i=1;i<101;i++){
try {
if (isCancelled()) {//判断如果为true那么说明已经有请求取消当前任务的信号了,既然无法终止线程的运行,但是可以终止运行在线程中一系列操作
break;
}
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
publishProgress(i);
}
return "下载完成";
}
@Override
protected void onProgressUpdate(Integer... values) {
if (isCancelled()) {//判断如果为true那么说明已经有请求取消当前任务的信号了,既然无法终止线程的运行,但是可以终止运行在线程中一系列操作,使它空运行,无法达到更新UI的效果
return ;
}
bar.setProgress(values[0]);
tv.setText("下载进度:"+values[0]+"%");
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(String result) {
tv.setText(result);
super.onPostExecute(result);
}
}
运行结果:
以上是关于浅谈android中异步加载之"取消异步加载"二的主要内容,如果未能解决你的问题,请参考以下文章
浅谈C#取消令牌CancellationTokenSource
浅谈C#取消令牌CancellationTokenSource