zoukankan      html  css  js  c++  java
  • 深入了解Android中的AsyncTask

            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);
            }
        }
    }
  • 相关阅读:
    render 动态增减表单项校验 小结
    面试题总结2
    禁止蒙层底部页面跟随滚动
    前端面试题总结1
    ||与??的区别,??非空运算符,??=非空赋值运算符 ??.链判断运算符 Object.defineProperty 与Proxy的区别
    chrome 代码调试实用小技巧
    Ubuntu安装ibmmq
    Python语言规范之Pylint的使用
    Python发送SMTP邮件指南
    快看那个运维妹子在学算法【二分查找】
  • 原文地址:https://www.cnblogs.com/huangjie123/p/6293990.html
Copyright © 2011-2022 走看看