美文网首页Android干货分享Android开发Android技术知识
Android AsyncTask(1)-使用方法和线程池解析

Android AsyncTask(1)-使用方法和线程池解析

作者: 明镜本清净anany | 来源:发表于2016-02-22 18:05 被阅读2177次

    当我们在android开发中需要开N个线程的时候,很多人的做法是直接new N 个 Thread。这样做不但不利于线程管理、而且大量的线程没有得到释放将会消耗性能,而且一般的Thread是没有返回线程执行完毕的返回值的,我们在Android开发中可以通过handler来告诉主线程线程是否执行完毕了。其实,Google工程师已经为我们设计好了一套强大的异步任务管理类 - AsyncTask。那么它到底强大在何处呢?接下来就开始慢慢讲解它的强大之处。

    一、AsyncTask优点:

    • 内部采用了线程池机制,可以有效的管理线程。
    • 可以自定义线程池,实现多个线程按顺序同步执行、异步并发执行。
    • 提供了回调方法,后台任务执行完毕后会返回需要的数据,拿到数据后可以直接更新UI控件

    二、AsyncTask用法步骤

    2.1 构建自定义AsyncTask的参数

    AsyncTask是一个抽象类,通常用于被继承,继承AsyncTask需要指定三个泛型参数


    1. Params :启动任务时输入参数的类型。

       【就是传进去耗时操作任务需要用到的参数,比如联网请求String类型的URL】
      
    2. Progress:后台任务执行中返回进度值的类型

       【可用于更新ProgressBar】如果要设置进度,则第二个参数一般设置为Integer
      
    3. Result:后台执行任务完成之后返回结果的类型

       【耗时操作结束后返回的类型,比如返回Bitmap】
      

      你设置的第三个参数类型是什么,doInBackGround的返回值就是什么类型



      doInBackground返回的Bitmap最终会传递到onPostExecute(Bitmap bitmap)中作为参数
      【AsyncTask底层是把异步耗时任务得到的数据result通过handler发送到了主线程】

    2.2 必须实现AsyncTask的抽象方法

    执行顺序从上往下。案例代码如下面2.3节代码所示:
    (1)onPreExecute:执行后台耗时操作前被调用,通常用于完成一些初始化操作
    (2)doInBackGround:必须实现,异步执行后台线程将要完成的任务【该方法在子线程运行】
    (3)onProgressUpdate:在doInBackGround方法中调用publishProgress方法,就可以更新
    任务的执行进度
    (4)onPostExecute:当doInBackGround完成后,系统会自动调用,并将doInBackGround方法返回的值传给该方法

    2.3 AsyncTask的用法案例

    1.实例化自定义AsyncTask类,传入一个图片url和ImagView,execute执行。

    new MyAsyncTask(url, imageView).execute();
    

    2.自定义AsyncTask类代码案例:

    private class MyAsyncTask extends AsyncTask<Void, Void, Bitmap> {
    
        private String mUrl;
        private ImageView mImageView;
    
        public MyAsyncTask(String url, ImageView imageView) {
            mUrl = url;
            mImageView = imageView;
        }
    
        @Override
        protected Bitmap doInBackground(Void... params) {
            return getBitmapFromUrl(mUrl);
        }
    
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            if (mUrl.equals(mImageView.getTag()))
                mImageView.setImageBitmap(bitmap);
        }
    } 
    

    三、AsyncTask线程池

    AsyncTask之所以如此强大,核心功臣就是它背后的线程池。
    在AsyncTask中提供有两种线程池,一个是THREAD_POOL_EXECUTOR,另一个是SERIAL_EXECUTOR。
    【但是,特别注意,其实在AsyncTask中只有一个线程池THREAD_POOL_EXECUTOR,只不过SERIAL_EXECUTOR实现了线程队列,最终还是使用的THREAD_POOL_EXECUTOR】

    1、THREAD_POOL_EXECUTOR 
            多个任务可以在线程池中异步并发执行。
    2、SERIAL_EXECUTOR
            把多个线程按串行的方式执行,所以是同步执行的。
            也就是说,只有当一个线程执行完毕之后,才会执行下个线程。
    

    3.1 异步、同步、并行、串行的区别

    • 异步:
      发送方发出数据后,不用等接收方发回响应,接着发送下个数据包的通讯方式。
      【比如,主main函数的代码从上往下执行,new一个Thread并在子线程中途执行了sleep 10秒钟,而主main函数后面的代码不需要等子线程sleep完10秒再执行,而是直接继续执行下面的代码。】
    • 同步:
      发送方发出数据后,需要等接收方发回响应以后才发下一个数据包的通讯方式。
      【比如,主main函数的代码从上往下执行,如果中途执行了sleep 10秒钟,则后面的代码都要等10秒后才会执行。】
    • 并行:
      也称为并发。从宏观上来理解,就是在同一时间内同时执行多个线程任务。
      【比如,同时开启10张图片下载,宏观上他们是10张图同时下载的。】
    • 串行:
      可以理解为,只有当一个线程执行完毕之后,才会执行下个线程。
      【比如,10张图片下载线程串行执行,只能是第一张下载完后,才会开始执行下一张图片下载。】

    3.2 THREAD_POOL_EXECUTOR 异步线程池

    ThreadPoolExecutor构造函数需要传递以下几个参数:

           /**
             * @param corePoolSize 线程池的核心运行线程数量
             * 
             * @param maximumPoolSize 线程池中可以创建的最大运行线程数量【包括核心运行线程】
             * 
             * @param keepAliveTime 当线程池线程数量超过corePoolSize时,
             * 多余的空余线程在缓冲队列的存活时间,超时后将会被移除
             * 
             * @param unit 线程池维护线程所允许的空闲时间的单位,一般设置为秒
             * 
             * @param workQueue 线程池所使用的缓冲队列,可以设置缓冲队列容纳的线程数量
             * 
             * @param threadFactory 线程工厂,用于创建线程。
             *    当一个异步任务执行execute的时候将会通过该工厂new出一个thread
             * 
             */
            public ThreadPoolExecutor(int corePoolSize,
                                      int maximumPoolSize,
                                      long keepAliveTime,
                                      TimeUnit unit,
                                      BlockingQueue<Runnable> workQueue,
                                      ThreadFactory threadFactory) {
                this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                     threadFactory, defaultHandler);
            }
    

    那么我们该如何理解这几个参数的意思呢?
    首先我们传入线程池参数:

    Executor executor = new ThreadPoolExecutor(3,5,10,
                    TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(5));
    

    然后,同时execute执行10个任务
    看图示解析:


    • 第一个参数传入是3,表示初始化核心并发运行的有三个线程,当execute任务大于3时,将会放入缓冲队列中
    • 当缓冲队列满了之后,【缓冲队列最多存5个线程】,此时已经加入进来了8个进程。
    • 但是,这还并没有结束,我们设置了第二个参数为5,就意味着,在线程池中可以创建的最大运行线程数量为5个。现在已经有3个核心线程创建了,
      所以还可以再创建两个线程。整个线程池最大支持容纳10个线程
    • 重点来了:这个时候,将会并发执行5个核心线程,是哪5个呢?答案是:1,2,3,9,10。因为新创建的两个运行线程也会开始工作。
      【1,2,3线程是核心运行线程,4,5,6,7,8在缓冲队列,9,10在运行线程池中。】
    • 那么5个线程结束之后?是继续并发执行3个,还是5个呢?答案是5个,因为运行线程已经创建为5个了。
    • 特别注意:【只有当缓冲队列满的时候,才会创建新的运行线程,否则默认只按corePoolSize的数量执行。直到超出maximumPoolSize大小,抛出异常】
    • 当再加一个线程进来时,AsyncTask中有一个拒绝策略的handler抛出异常,此时线程池已经无法再容纳更多线程任务了。
      不多说,上演示图:


    从AsyncTask默认构造的THREAD_POOL_EXECUTOR可以看出,AsyncTask最大支持的缓冲任务队列是128个。


    3.3 SERIAL_EXECUTOR 串行线程池

    在ActivityThread中的有一段代码,设置了当Android 版本api 小于12,也就是版本小于3.1时,默认是使用AsyncTask默认的异步线程池THREAD_POOL_EXECUTOR
    Android 3.1 之后使用的是 SERIAL_EXECUTOR

    四、执行AsyncTask的两个方法:

    1、execute 
        第一个执行方法,使用的是AsyncTask默认的线程池
    2、executeOnExecutor
        第二个执行方法,需要传递进去一个Executor,可以实现自定义线程池
    

    4.1 execute 【默认线程池执行】

    刚刚提到了,AsyncTask在3.1版本之前默认使用THREAD_POOL_EXECUTOR线程池,也就是说,3.1版本之前的使用AsynTask,将会是异步并发执行

    4.1.1 api8系统执行execute

    我们来看2.3系统同时execute执行10个异步任务,得到的结果:
    3.1版本之前的系统默认最大并发执行5个线程,缓冲线程队列最大128个。虽然开了10个异步任务,但是只能同时并发执行5个,其他的任务都得等前面5个执行完后才继续执行,接着也是5次并发执行。


    4.1.2 api11以上系统执行execute

    那么,3.1版本之后系统,默认是使用SERIAL_EXECUTOR串行任务执行,可以预料到异步任务将会是一个个顺序执行。

    果不其然,5.0系统同时execute执行10个异步任务,是一个个线程按加入顺序同步执行的。也就是说,线程池中只有一个核心线程在工作,其他线程都要等之前的线程执行完才能执行。


    4.2 executeOnExecutor 【自定义线程池执行,仅支持3.1以上系统】

    很显然,AsyncTask的默认线程池完全不能满足我们的需求,这个时候就需要用到executeOnExecutor自定义线程池了。

    但是,现在问题来了,3.1版本以下的系统是不支持自定义线程池执行的!所以3.1版本以下的系统需要实现同步执行异步任务,只能用Thread自己定义线程池。


    4.2.1 使用默认提供的AsyncTask.THREAD_POOL_EXECUTOR线程池

    AsyncTask.THREAD_POOL_EXECUTOR,代码如下:

    new MyAsyncTask(progressBar).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    

    这样,3.1版本以上的系统便可以实现异步并发执行任务了。我们可以看到,和3.1版本之前执行默认execute一样,采用了AsyncTask默认的线程池,最大并发执行5个线程,后面的线程都只能等之前5个结束之后再执行。


    4.2.1 自定义线程池

    重磅炸弹来了,我们可以自己定义线程池来执行异步并发,这样我们就可以自由控制线程池了。
    下面代码可以实现10个核心任务并发执行,而且缓存队列最多能够存储100个任务,当队列满了之后还可以创建40个运行线程,瞬间爆炸。

    Executor executor = new ThreadPoolExecutor(10,50,10,
                    TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(100));
    
    new MyAsyncTask(progressBar).executeOnExecutor(executor);
    new MyAsyncTask(progressBar2).executeOnExecutor(executor);
    

    如图所示:10个线程并发执行了


    五、总结

    前面已经提到过了AsyncTask的优点,我们在开发中如果合理运用AsyncTask,将会比自己new Thread好很多。那么,就来总结一下AsyncTask的最佳适用场景:

    • AsyncTask比较适合有大量线程执行的情况,让线程池去管理会更加高效。
    • 假如想让几个线程按顺序执行时,可以使用AsyncTask。(前提是系统版本不能小于11)
    • 尽量使用自定义线程池,按需调节线程池参数。

    5.1 AsyncTask缺点

    • 之前已经解析过了,AsyncTask在3.1系统以下默认使用AsyncTask的线程池,不可以自定义线程,假如在线程不多的情况下,是很耗性能的。因为假如你只有一个异步任务,还是会创建另外4个核心线程。

    • 容易造成内存泄露,如果持有context引用的话


    线程太多有风险,开的时候需谨慎呐!下一节,将带来AsyncTask的源码解析部分。

    相关文章

      网友评论

      • 9bfc7878f5b3:博主的 demo 很生动形象,谢谢~
      • 李牧羊:ThreadPoolExecutor你给出的是六个参数的构造方法,实际使用的时候用的是五个参数的构造方法,博主是不是写的时候没注意呢
        李牧羊:@明镜本清净anany 我知道,但是你写的时候应该上下对应啊,搞的我特地去翻了下源码
        明镜本清净anany:@李牧羊 你看ThreadPoolExecutor类它的构造方法,有4个,其中一个就是6个参数的,我这里只是拿出一个举例。
      • neumeng:基神经典
      • 一个脱离高级趣味的人:分析的很详细 赞
      • __Berial___:友情撸过 :smirk:
      • 15ade3bd4809:基神出品,必属精品

      本文标题:Android AsyncTask(1)-使用方法和线程池解析

      本文链接:https://www.haomeiwen.com/subject/kvsbkttx.html