欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

深入了解Android中的AsyncTask

程序员文章站 2024-02-15 19:08:41
asynctask,即异步任务,是android给我们提供的一个处理异步任务的类。通过此类,可以实现ui线程和后台线程进行通讯,后台线程执行异步任务,并把结果返回给ui线程...

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);
  }
 }
}

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