当前位置: 移动技术网 > IT编程>移动开发>Android > 深入了解Android中的AsyncTask

深入了解Android中的AsyncTask

2019年07月24日  | 移动技术网IT编程  | 我要评论

郑靓歆女装,爱丽丝学园全集,商丘师院教务管理系统

asynctask,即异步任务,是android给我们提供的一个处理异步任务的类。通过此类,可以实现ui线程和后台线程进行通讯,后台线程执行异步任务,并把结果返回给ui线程。  我们知道,android中只有ui线程,也就是主线程才能进行对ui的更新操作,而其他线程是不能直接操作ui的.这样的好处是保证了ui的稳定性和准确性,避免多个线程同时对ui进行操作而造成ui的混乱。  但android是一个多线程的操作系统,我们总不能把所有的任务都放在主线程中进行实现,比如网络操作,文件读取等耗时操作,如果全部放到主线程去执行,就可能会造成后面任务的阻塞。android会去检测这种阻塞,当阻塞时间太长的时候,就会抛出application not responsed(anr)错误.所以我们需要将这些耗时操作放在非主线程中去执行.这样既避免了android的单线程模型,又避免了anr。  虽说现在做网络请求有了volley全家桶和okhttp这样好用的库,但是在处理其他后台任务以及与ui交互上,还是需要用到asynctask。任何一个用户量上千万的产品绝对不会在代码里面使用系统原生的asyntask,因为它蛋疼的兼容性以及极高的崩溃率实在让人不敢恭维。  asynctask到底是什么呢?很简单,它不过是对线程池和handler的封装;用线程池来处理后台任务,用handler来处理与ui的交互。线程池使用的是executor接口,我们先了解一下线程池的特性。

jdk5带来的一大改进就是java的并发能力,它提供了三种并发武器:并发框架executor,并发集合类型如concurrenthashmap,并发控制类如countdownlatch等;尽量使用exector而不是直接用thread类进行并发编程。

asynctask内部也使用了线程池处理并发;线程池通过threadpoolexector类构造,这个构造函数参数比较多,它允许开发者对线程池进行定制,我们先看看这每个参数是什么意思,然后看看android是以何种方式定制的。

threadpoolexecutor的其他构造函数最终都会调用如下的构造函数完成对象创建工作:

  public threadpoolexecutor(int corepoolsize,
       int maximumpoolsize,
       long keepalivetime,
       timeunit unit,
       blockingqueue<runnable> workqueue,
       threadfactory threadfactory,
       rejectedexecutionhandler handler);

corepoolsize: 核心线程数目,即使线程池没有任务,核心线程也不会终止(除非设置了allowcorethreadtimeout参数)可以理解为“常驻线程”

maximumpoolsize: 线程池中允许的最大线程数目;一般来说,线程越多,线程调度开销越大;因此一般都有这个限制。

keepalivetime: 当线程池中的线程数目比核心线程多的时候,如果超过这个keepalivetime的时间,多余的线程会被回收;这些与核心线程相对的线程通常被称为缓存线程

unit: keepalivetime的时间单位

workqueue: 任务执行前保存任务的队列;这个队列仅保存由execute提交的runnable任务

threadfactory: 用来构造线程池的工厂;一般都是使用默认的;

handler: 当线程池由于线程数目和队列限制而导致后续任务阻塞的时候,线程池的处理方式。

如果线程池中线程的数目少于corepoolsize,就算线程池中有其他的没事做的核心线程,线程池还是会重新创建一个核心线程;直到核心线程数目到达corepoolsize(常驻线程就位)。如果线程池中线程的数目大于或者等于corepoolsize,但是工作队列workqueue没有满,那么新的任务会放在队列workqueue中,按照fifo的原则依次等待执行。当有核心线程处理完任务空闲出来后,会检查这个工作队列然后取出任务默默执行去,如果线程池中线程数目大于等于corepoolsize,并且工作队列workqueue满了,但是总线程数目小于maximumpoolsize,那么直接创建一个线程处理被添加的任务。如果工作队列满了,并且线程池中线程的数目到达了最大数目maximumpoolsize,那么就会用最后一个构造参数handler处理;**默认的处理方式是直接丢掉任务,然后抛出一个异常。总结起来,也即是说,当有新的任务要处理时,先看线程池中的线程数量是否大于 corepoolsize,再看缓冲队列 workqueue 是否满,最后看线程池中的线程数量是否大于 maximumpoolsize。另外,当线程池中的线程数量大于 corepoolsize 时,如果里面有线程的空闲时间超过了 keepalivetime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。

asynctask里面有“两个”线程池;一个thread_pool_executor一个serial_executor;之所以打引号,是因为其实serial_executor也使用thread_pool_executor实现的,只不过加了一个队列弄成了串行而已。asynctask里面线程池是一个核心线程数为cpu + 1,最大线程数为cpu * 2 + 1,工作队列长度为128的线程池;并且没有传递handler参数,那么使用的就是默认的handler(拒绝执行)。如果任务过多,那么超过了工作队列以及线程数目的限制导致这个线程池发生阻塞,那么悲剧发生,默认的处理方式会直接抛出一个异常导致进程挂掉。假设你自己写一个异步图片加载的框架,然后用asynctask实现的话,当你快速滑动listview的时候很容易发生这种异常;这也是为什么各大imageloader都是自己写线程池和handlder的原因。这个线程池是一个静态变量;那么在同一个进程之内,所有地方使用到的asynctask默认构造函数构造出来的asynctask都使用的是同一个线程池,如果app模块比较多并且不加控制的话,很容易满足第一条的崩溃条件;如果你不幸在不同的asynctask的doinbackgroud里面访问了共享资源,那么就会发生各种并发编程问题。

在asynctask全部执行完毕之后,进程中还是会常驻corepoolsize个线程;在android 4.4 (api 19)以下,这个corepoolsize是hardcode的,数值是5;api 19改成了cpu + 1;也就是说,在android 4.4以前;如果你执行了超过五个asynctask;然后啥也不干了,进程中还是会有5个asynctask线程。

      asynctask里面的handler很简单,如下(api 22代码):

   private static final internalhandler shandler = new internalhandler();
    public internalhandler() {
    super(looper.getmainlooper());
   }

注意,这里直接用的主线程的looper;如果去看api 22以下的代码,会发现它没有这个构造函数,而是使用默认的;默认情况下,handler会使用当前线程的looper,如果你的asynctask是在子线程创建的,那么很不幸,你的onpreexecute和onpostexecute并非在ui线程执行,而是被handler post到创建它的那个线程执行;如果你在这两个线程更新了ui,那么直接导致崩溃。这也是大家口口相传的asynctask必须在主线程创建的原因。另外,asynctask里面的这个handler是一个静态变量,也就是说它是在类加载的时候创建的;如果在你的app进程里面,以前从来没有使用过asynctask,然后在子线程使用asynctask的相关变量,那么导致静态handler初始化,如果在api 16以下,那么会出现上面同样的问题;这就是asynctask必须在主线程初始化 的原因。事实上,在android 4.1(api 16)以后,在app主线程activitythread的main函数里面,直接调用了ascyntask.init函数确保这个类是在主线程初始化的;另外,init这个函数里面获取了internalhandler的looper,由于是在主线程执行的,因此,asynctask的handler用的也是主线程的looper。这个问题从而得到彻底的解决。

asynctask的使用较为简单,只需要关注三个参数和四个方法即可。

asynctask<params,progress,result>是一个抽象类,通常用于被继承.继承asynctask需要指定如下三个泛型参数:

  •      params:启动任务时输入的参数类型.
  •      progress:后台任务执行中返回进度值的类型.
  •      result:后台任务执行完成后返   回结果的类型.

asynctask主要有如下几个方法:

  •      doinbackground:必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成.
  •      onpreexecute:执行后台耗时操作前被调用,通常用于进行初始化操作.
  •      onpostexecute:当doinbackground方法完成后,系统将自动调用此方法,并将doinbackground方法返回的值传入此方法.通过此方法进行ui的更新.
  •      onprogressupdate:当在doinbackground方法中调用publishprogress方法更新任务执行进度后,将调用此方法.通过此方法我们可以知晓任务的完成进度.

下面通过代码演示一个典型的异步处理的实例--加载网络图片.网络操作作为一个不稳定的耗时操作,从4.0开始就被严禁放入主线程中.所以在显示一张网络图片时,我们需要在异步处理中下载图片,并在ui线程中设置图片。

mainactivity.java

import android.app.activity;
import android.content.intent;
import android.os.bundle;
import android.view.view;
import android.view.view.onclicklistener;
import android.widget.button;
public class mainactivity extends activity {
 private button btn_image;
 @override
 protected void oncreate(bundle savedinstancestate) {
  super.oncreate(savedinstancestate);
  setcontentview(r.layout.activity_main);
  btn_image = (button) findviewbyid(r.id.btn_image);
  btn_image.setonclicklistener(new onclicklistener() {
   @override
   public void onclick(view v) {
    startactivity(new intent(mainactivity.this,imageactivity.class));
   }
  });
 }
}

imageactivity.java

import android.app.activity;
import android.graphics.*;
import android.os.*;
import android.view.view;
import android.widget.*;
import java.io.*;
import java.net.*;
public class imageactivity extends activity {
 private imageview imageview ;
 private progressbar progressbar ;
 private static string url = "http://tupian.baike.com/a2_50_64_01300000432220134623642199335_jpg.html?prd=so_tupian";
 @override
 protected void oncreate(bundle savedinstancestate) {
  super.oncreate(savedinstancestate);
  setcontentview(r.layout.image);
  imageview = (imageview) findviewbyid(r.id.image);
  progressbar = (progressbar) findviewbyid(r.id.progressbar);
  //通过调用execute方法开始处理异步任务.相当于线程中的start方法.
  new myasynctask().execute(url);
 }
 class myasynctask extends asynctask<string,void,bitmap> {
  //onpreexecute用于异步处理前的操作
  @override
  protected void onpreexecute() {
   super.onpreexecute();
   //此处将progressbar设置为可见.
   progressbar.setvisibility(view.visible);
  }
  //在doinbackground方法中进行异步任务的处理.
  @override
  protected bitmap doinbackground(string... params) {
   //获取传进来的参数
   string url = params[0];
   bitmap bitmap = null;
   urlconnection connection ;
   inputstream is ;
   try {
    connection = new url(url).openconnection();
    is = connection.getinputstream();
    //为了更清楚的看到加载图片的等待操作,将线程休眠3秒钟.
    thread.sleep(3000);
    bufferedinputstream bis = new bufferedinputstream(is);
    //通过decodestream方法解析输入流
    bitmap = bitmapfactory.decodestream(bis);
    is.close();
    bis.close();
   } catch (ioexception e) {
    e.printstacktrace();
   } catch (interruptedexception e) {
    e.printstacktrace();
   }
   return bitmap;
  }
  //onpostexecute用于ui的更新.此方法的参数为doinbackground方法返回的值.
  @override
  protected void onpostexecute(bitmap bitmap) {
   super.onpostexecute(bitmap);
   //隐藏progressbar
   progressbar.setvisibility(view.gone);
   //更新imageview
   imageview.setimagebitmap(bitmap);
  }
 }
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持移动技术网!

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网