浅谈android中异步加载之"取消异步加载"

转载自:http://blog.csdn.net/u013064109/article/details/51590621



标签: android中的异步加载取消异步加载线程
  8700人阅读  评论(4)  收藏  举报
  分类:
 

首先,我得解释一下为什么我的标题取消异步加载打引号,这是因为可能最后实现效果并不是你自己想象中的那样。大家看取消异步加载,这不是很简单吗?AsyncTask中不是有一个cancel方法吗?直接调用该方法不就行了吗?但是事实上是这样的吗?如果真是这样,我相信我就没有以写这个作为一篇博客的必要了。为什么会有这样的想法呢?实际上源于我上一篇中Demo中的一个BUG,然后解决该BUG,需要去取消异步任务,是怎么样,我们不妨来看看。

 首先,还是来一起回顾一下上篇博客中加载进度条Demo吧。

AsyncTask子类:

[java]  view plain  copy
 print ?
  1. 01.package com.mikyou.utils;    
  2. 02.    
  3. 03.import android.os.AsyncTask;    
  4. 04.import android.widget.ProgressBar;    
  5. 05.import android.widget.TextView;    
  6. 06.    
  7. 07.public class MikyouAsyncTaskProgressBarUtils extends AsyncTask<Void, Integer, String>{    
  8. 08.    private TextView tv;    
  9. 09.    private ProgressBar bar;    
  10. 10.    public MikyouAsyncTaskProgressBarUtils(TextView tv,ProgressBar bar){//这里我就采用构造器将TextView,ProgressBar直接传入,然后在该类中直接更新UI    
  11. 11.        this.bar=bar;    
  12. 12.        this.tv=tv;    
  13. 13.    
  14. 14.    }    
  15. 15.    @Override    
  16. 16.    protected String doInBackground(Void... params) {    
  17. 17.        for(int i=1;i<101;i++){    
  18. 18.            try {    
  19. 19.                Thread.sleep(1000);    
  20. 20.            } catch (InterruptedException e) {    
  21. 21.                e.printStackTrace();    
  22. 22.            }    
  23. 23.            publishProgress(i);    
  24. 24.        }    
  25. 25.        return "下载完成";    
  26. 26.    }    
  27. 27.    @Override    
  28. 28.    protected void onProgressUpdate(Integer... values) {    
  29. 29.        bar.setProgress(values[0]);    
  30. 30.        tv.setText("下载进度:"+values[0]+"%");    
  31. 31.        super.onProgressUpdate(values);    
  32. 32.    }    
  33. 33.    @Override    
  34. 34.    protected void onPostExecute(String result) {    
  35. 35.        tv.setText(result);    
  36. 36.        super.onPostExecute(result);    
  37. 37.    }    
  38. 38.}    

MainActivity

[java]  view plain  copy
 print ?
  1. 01.package com.mikyou.asynctask;    
  2. 02.    
  3. 03.import com.mikyou.utils.MikyouAsyncTaskProgressBarUtils;    
  4. 04.    
  5. 05.import android.app.Activity;    
  6. 06.import android.os.Bundle;    
  7. 07.import android.view.View;    
  8. 08.import android.widget.Button;    
  9. 09.import android.widget.ProgressBar;    
  10. 10.import android.widget.TextView;    
  11. 11.    
  12. 12.public class MainActivity extends Activity {    
  13. 13.    private Button downLoad;    
  14. 14.    private ProgressBar bar;    
  15. 15.    private TextView tv;    
  16. 16.    @Override    
  17. 17.    protected void onCreate(Bundle savedInstanceState) {    
  18. 18.        super.onCreate(savedInstanceState);    
  19. 19.        setContentView(R.layout.activity_main);    
  20. 20.        initView();    
  21. 21.    }    
  22. 22.    private void initView() {    
  23. 23.        bar=(ProgressBar) findViewById(R.id.bar);    
  24. 24.        tv=(TextView) findViewById(R.id.tv);    
  25. 25.    }    
  26. 26.    public void download(View view){    
  27. 27.        MikyouAsyncTaskProgressBarUtils mikyouAsyncTaskProgressBarUtils=new MikyouAsyncTaskProgressBarUtils(tv, bar);    
  28. 28.        mikyouAsyncTaskProgressBarUtils.execute();    
  29. 29.    }    
  30. 30.    
  31. 31.}    

运行结果的Demo的BUG:



通过分析上面的demo会发现:

当我们点击开始下载后,进度条就开始更新了,然后就在更新中途退出Activity,然后再次进入Activity,点击开始下载会发现,等了好一会,进度条才开始更新。

原因:这并不是本程序的一个BUG,而是异步加载的一个机制,因为异步加载从源码中我们可以得出,它底层的实现还是Thread或者Thread-pool+Handler机制

那么它的机制就是如果当前开始的线程没有执行完毕,其他的线程必须等待,这也就解释了为什么说异步加载是安全,因为可以基于该机制避免了线程同步带来的安全问题。
解决办法:其实很简单的就是将异步加载的生命周期和我们的Activity的生命周期进行绑定,当我第一次在中途中断的时候,退出Activity时会调用OnPause
方法,只需要在OnPause方法中顺便把本次中断的异步任务取消即可,也即把当前的线程池中的线程给取消了,当下次重新进入的时候,线程池中并没有
其他的子线程,那么它就无需等待其他的线程了,直接运行。

通过以上的分析,我们就可以得到了一个解决办法,那就是如何去取消当前的异步任务,那就我们就按照大家的一致想法使用cancel方法来终止异步任务。

来了看看是否可以达到我们想要的效果呢??一起来看看

[java]  view plain  copy
 print ?
  1. package com.mikyou.asynctask;  
  2.   
  3. import com.mikyou.utils.MikyouAsyncTaskProgressBarUtils;  
  4.   
  5. import android.app.Activity;  
  6. import android.os.AsyncTask;  
  7. import android.os.Bundle;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. import android.widget.ProgressBar;  
  11. import android.widget.TextView;  
  12.   
  13. public class MainActivity extends Activity {  
  14.     private Button downLoad;  
  15.     private ProgressBar bar;  
  16.     private TextView tv;  
  17.     private MikyouAsyncTaskProgressBarUtils mTask;  
  18.     @Override  
  19.     protected void onCreate(Bundle savedInstanceState) {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_main);  
  22.         initView();  
  23.     }  
  24.     private void initView() {  
  25.         bar=(ProgressBar) findViewById(R.id.bar);  
  26.         tv=(TextView) findViewById(R.id.tv);  
  27.     }  
  28.     public void download(View view){  
  29.         mTask=new MikyouAsyncTaskProgressBarUtils(tv, bar);  
  30.         mTask.execute();  
  31.     }  
  32.     //将异步任务的生命周期与Activity进行绑定  
  33. @Override  
  34. protected void onPause() {  
  35.     //判断当前的异步任务是否为空,并且判断当前的异步任务的状态是否是运行状态{RUNNING(运行),PENDING(准备),FINISHED(完成)}  
  36. if (mTask!=null&&mTask.getStatus()==AsyncTask.Status.RUNNING) {  
  37.     /** 
  38.      *cancel(true) 取消当前的异步任务,传入的true,表示当中断异步任务时继续已经运行的线程的操作, 
  39.      *但是为了线程的安全一般为让它继续设为true 
  40.      * */  
  41.     mTask.cancel(true);  
  42.   
  43. }  
  44.     super.onPause();  
  45. }  
  46. }  
运行结果:

大家仔细看下,发现好像我们加的cancel方法并没有什么用,这也就我这篇需要讲的了,

为什么无法去停止一个异步任务,这是为什么呢?

注意:这是因为cancel方法只是发出一个请求取消异步任务的信号, 将对应当前的异步任务标记为CANCEL状态,而并不是真正取消线程的执行,而此时异步任务中的线程仍然在执行并没有结束,所以效果依然是这样的,并且在Java中我们是无法直接暴力将一个线程给停止掉 既然我们知道无法去取消一个已经正在运行的线程,但是我们如何去解决这个BUG呢?在异步任务中还给我们提供一个isCanceled的回调方法,也就是当我已经给当前的异步任,调用了cancel(true)方法,发出一个请求取消异步任务的信号,那么此时的isCanceled的回调方法会直接返回一个true,那么我们就可以通过判断当前异步任务isCanceled是否为true,来终止线程中的操作而不是去终止线程,从而达到了界面显示好像线程中的操作被终止了,而实际上该线程依然在运行

因为我们是无法去彻彻底底地采用暴力的方法直接kill一个线程,所以我们不能直接去取消一个异步任务,但是我们可以通过调用cancel方法来发送一个取消异步任务的请求信号,这时候就会给mCanceled的值设置为true,标记为该异步任务是取消了,通过回调方法isCanceled来返回一个true,表示该异步任务取消。如果有疑问我们来看下cancel反方法的源码就知道了。

[java]  view plain  copy
 print ?
  1. /** 
  2.  * <p>Attempts to cancel execution of this task.  This attempt will 
  3.  * fail if the task has already completed, already been cancelled, 
  4.  * or could not be cancelled for some other reason. If successful, 
  5.  * and this task has not started when <tt>cancel</tt> is called, 
  6.  * this task should never run. If the task has already started, 
  7.  * then the <tt>mayInterruptIfRunning</tt> parameter determines 
  8.  * whether the thread executing this task should be interrupted in 
  9.  * an attempt to stop the task.</p> 
  10.  *  
  11.  * <p>Calling this method will result in {@link #onCancelled(Object)} being 
  12.  * invoked on the UI thread after {@link #doInBackground(Object[])} 
  13.  * returns. Calling this method guarantees that {@link #onPostExecute(Object)} 
  14.  * is never invoked. After invoking this method, you should check the 
  15.  * value returned by {@link #isCancelled()} periodically from 
  16.  * {@link #doInBackground(Object[])} to finish the task as early as 
  17.  * possible.</p> 
  18.  * 
  19.  * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this 
  20.  *        task should be interrupted; otherwise, in-progress tasks are allowed 
  21.  *        to complete. 
  22.  * 
  23.  * @return <tt>false</tt> if the task could not be cancelled, 
  24.  *         typically because it has already completed normally; 
  25.  *         <tt>true</tt> otherwise 
  26.  * 
  27.  * @see #isCancelled() 
  28.  * @see #onCancelled(Object) 
  29.  */  
  30. public final boolean cancel(boolean mayInterruptIfRunning) {  
  31.     mCancelled.set(true);  
  32.     return mFuture.cancel(mayInterruptIfRunning);  
  33. }  

会看mCanceled会设置为true,该值将会通过isCanceled方法回调传出去。所以,我们采用这样一个想法,既然我们不能去终止一个线程,那么我们可以间接解决这个bug

通过判断该值直接终止线程中的操作,而不是去终止线程。

[java]  view plain  copy
 print ?
  1. package com.mikyou.asynctask;  
  2.   
  3. import com.mikyou.utils.MikyouAsyncTaskProgressBarUtils;  
  4.   
  5. import android.app.Activity;  
  6. import android.os.AsyncTask;  
  7. import android.os.Bundle;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. import android.widget.ProgressBar;  
  11. import android.widget.TextView;  
  12.   
  13. public class MainActivity extends Activity {  
  14.     private Button downLoad;  
  15.     private ProgressBar bar;  
  16.     private TextView tv;  
  17.     private MikyouAsyncTaskProgressBarUtils mTask;  
  18.     @Override  
  19.     protected void onCreate(Bundle savedInstanceState) {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_main);  
  22.         initView();  
  23.     }  
  24.     private void initView() {  
  25.         bar=(ProgressBar) findViewById(R.id.bar);  
  26.         tv=(TextView) findViewById(R.id.tv);  
  27.     }  
  28.     public void download(View view){  
  29.         mTask=new MikyouAsyncTaskProgressBarUtils(tv, bar);  
  30.         mTask.execute();  
  31.     }  
  32.     //将异步任务的生命周期与Activity进行绑定  
  33. @Override  
  34. protected void onPause() {  
  35.     //判断当前的异步任务是否为空,并且判断当前的异步任务的状态是否是运行状态{RUNNING(运行),PENDING(准备),FINISHED(完成)}  
  36. if (mTask!=null&&mTask.getStatus()==AsyncTask.Status.RUNNING) {  
  37.     /** 
  38.      *cancel(true) 取消当前的异步任务,传入的true,表示当中断异步任务时继续已经运行的线程的操作, 
  39.      *但是为了线程的安全一般为让它继续设为true 
  40.      * */  
  41.     mTask.cancel(true);  
  42.     /** 
  43.      * 但是重新运行后会发现还是不能起到效果, 
  44.      * 注意:这是因为cancel方法只是发出一个请求取消异步任务的信号, 
  45.      * 将对应当前的异步任务标记为CANCEL状态,而并不是真正取消线程的执行, 
  46.      * 而此时异步任务中的线程仍然在执行并没有结束 
  47.      * 所以效果依然是这样的,并且在java中我们是无法直接暴力将一个线程给停止掉 
  48.      * 既然我们知道无法去取消一个已经正在运行的线程,但是我们如何去解决这个BUG呢? 
  49.      * 在异步任务中还给我们提供一个isCanceled的回调方法,也就是当我已经给当前的异步任务 
  50.      * 调用了cancel(true)方法,发出一个请求取消异步任务的信号,那么此时的isCanceled的回调方法 
  51.      * 会直接返回一个true,那么我们就可以通过判断当前异步任务isCanceled是否为true,来终止 
  52.      * 线程中的操作而不是去终止线程,从而达到了界面显示好像线程中的操作被终止了,而实际上 
  53.      * 该线程依然在运行 
  54.      * */  
  55. }  
  56.     super.onPause();  
  57. }  
  58. }  

AsyncTask的子类

[java]  view plain  copy
 print ?
  1. package com.mikyou.utils;  
  2.   
  3. import android.os.AsyncTask;  
  4. import android.widget.ProgressBar;  
  5. import android.widget.TextView;  
  6.   
  7. public class MikyouAsyncTaskProgressBarUtils extends AsyncTask<Void, Integer, String>{  
  8.     private TextView tv;  
  9.     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.   
  18.         for(int i=1;i<101;i++){  
  19.             try {  
  20.                 if (isCancelled()) {//判断如果为true那么说明已经有请求取消当前任务的信号了,既然无法终止线程的运行,但是可以终止运行在线程中一系列操作  
  21.                     break;  
  22.                 }  
  23.                 Thread.sleep(100);  
  24.             } catch (InterruptedException e) {  
  25.                 e.printStackTrace();  
  26.             }  
  27.             publishProgress(i);  
  28.         }  
  29.         return "下载完成";  
  30.     }  
  31.     @Override  
  32.     protected void onProgressUpdate(Integer... values) {  
  33.         if (isCancelled()) {//判断如果为true那么说明已经有请求取消当前任务的信号了,既然无法终止线程的运行,但是可以终止运行在线程中一系列操作,使它空运行,无法达到更新UI的效果  
  34.             return ;  
  35.         }  
  36.         bar.setProgress(values[0]);  
  37.         tv.setText("下载进度:"+values[0]+"%");  
  38.         super.onProgressUpdate(values);  
  39.     }  
  40.     @Override  
  41.     protected void onPostExecute(String result) {  
  42.         tv.setText(result);  
  43.         super.onPostExecute(result);  
  44.     }  
  45. }  

运行结果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值