首页 > 代码库 > HandlerThread 的使用及其源码完全解析

HandlerThread 的使用及其源码完全解析

本篇我们将来给大家介绍HandlerThread这个类,以前我们在使用线程执行一个耗时任务时总会new一个Thread的线程去跑,当任务执行完后,线程就会自动被销毁掉,如果又由新的任务,我们又得新建线程.....我们假设这样的一个情景,我们通过listview去加载图文列表,当我们往下滑动时,这时需要不断去请求网络资源,也就是需要不断开线程去加载网络资源,如果每次都new一个Thread,这显然是不合理的,那么该怎么办呢?相信大家都应该用过图片加载框架ImageLoader,其实ImageLoader内部就是通过Handler+Looper+Thread来实现的,内部维持一个线程池,通过Handler+Looper+Thread构建循环线程,每次有任务就取出其中的任务放到线程池去执行,没有就一直处于等待状态,直到有新任务被投放进来,如果任务过多就加入等待队列,直到其中一个线程执行完毕就从等待队列获取下一个执行的任务,这样就可以避免过多创建Thread所造成的资源消耗。当然Handler+Looper+Thread的实现方式并不是本篇的讨论重点,我们要讨论的是其实现替代者-HandlerThread,继承自Thread,本质是Thread,它与普通Thread的差别就在于,它有个Looper成员变量。其内部就是通过Thread+Looper来实现的,说白了HandlerThread就是Android已经封装好的一个拥有自己looper的线程,我们可以利用它执行一些耗时任务。我们先来看看HandlerThread的使用步骤并提供给大家一个使用案例:

一.HandlerThread的使用步骤

1.创建实例对象

[java] view plain copy
 print?技术分享技术分享
  1. HandlerThread handlerThread = new HandlerThread("downloadImage");  
参数的作用主要是标记当前线程的名字,可以任意字符串

2.启动HandlerThread线程

[java] view plain copy
 print?技术分享技术分享
  1. //必须先开启线程  
  2.   handlerThread.start();  
到此,我们就构建完一个循环线程。那么我们怎么将一个耗时的异步任务投放到HandlerThread线程中去执行呢?接下来看下面步骤:

3.构建循环消息处理机制

[java] view plain copy
 print?技术分享技术分享
  1. /** 
  2.    * 该callback运行于子线程 
  3.    */  
  4.   class ChildCallback implements Handler.Callback {  
  5.       @Override  
  6.       public boolean handleMessage(Message msg) {  
  7.           //在子线程中进行相应的网络请求  
  8.         
  9.           //通知主线程去更新UI  
  10.           mUIHandler.sendMessage(msg1);  
  11.           return false;  
  12.       }  
  13.   }  
构建异步handler
[java] view plain copy
 print?技术分享技术分享
  1. //子线程Handler  
  2. Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());  
第3步是构建一个可以用于异步操作的handler,并将前面创建的HandlerThread的Looper对象和Callback接口类作为参数传递给当前的handler,这样当前的异步handler就拥有了HandlerThread的Looper对象,而其中的handlerMessage方法来处理耗时任务,Looper+Handler+MessageQueue+Thread异步循环机制构建完成。下面我们来看一个使用案例

二.HandlerThread的使用案例

[java] view plain copy
 print?技术分享技术分享
  1. package com.zejian.handlerlooper;  
  2. import android.app.Activity;  
  3. import android.graphics.Bitmap;  
  4. import android.graphics.BitmapFactory;  
  5. import android.os.Bundle;  
  6. import android.os.Handler;  
  7. import android.os.HandlerThread;  
  8. import android.os.Message;  
  9. import android.widget.ImageView;  
  10. import com.zejian.handlerlooper.model.ImageModel;  
  11. import com.zejian.handlerlooper.util.LogUtils;  
  12. import java.io.BufferedInputStream;  
  13. import java.io.IOException;  
  14. import java.net.HttpURLConnection;  
  15. import java.net.URL;  
  16. /** 
  17.  * Created by zejian on 16/3/5. 
  18.  */  
  19. public class HandlerThreadActivity extends Activity {  
  20.     /** 
  21.      * 图片地址集合,图片来自网络. 
  22.      */  
  23.     private String url[]={  
  24.             "http://img.my.csdn.net/uploads/201407/26/1406383299_1976.jpg",  
  25.             "http://img.my.csdn.net/uploads/201407/26/1406383291_6518.jpg",  
  26.             "http://img.my.csdn.net/uploads/201407/26/1406383291_8239.jpg",  
  27.             "http://img.my.csdn.net/uploads/201407/26/1406383290_9329.jpg",  
  28.             "http://img.my.csdn.net/uploads/201407/26/1406383290_1042.jpg",  
  29.             "http://img.my.csdn.net/uploads/201407/26/1406383275_3977.jpg",  
  30.             "http://img.my.csdn.net/uploads/201407/26/1406383265_8550.jpg",  
  31.             "http://img.my.csdn.net/uploads/201407/26/1406383264_3954.jpg",  
  32.             "http://img.my.csdn.net/uploads/201407/26/1406383264_4787.jpg",  
  33.             "http://img.my.csdn.net/uploads/201407/26/1406383264_8243.jpg",  
  34.             "http://img.my.csdn.net/uploads/201407/26/1406383248_3693.jpg",  
  35.             "http://img.my.csdn.net/uploads/201407/26/1406383243_5120.jpg",  
  36.             "http://img.my.csdn.net/uploads/201407/26/1406383242_3127.jpg",  
  37.             "http://img.my.csdn.net/uploads/201407/26/1406383242_9576.jpg",  
  38.             "http://img.my.csdn.net/uploads/201407/26/1406383242_1721.jpg",  
  39.             "http://img.my.csdn.net/uploads/201407/26/1406383219_5806.jpg",  
  40.             "http://img.my.csdn.net/uploads/201407/26/1406383214_7794.jpg",  
  41.             "http://img.my.csdn.net/uploads/201407/26/1406383213_4418.jpg",  
  42.             "http://img.my.csdn.net/uploads/201407/26/1406383213_3557.jpg",  
  43.             "http://img.my.csdn.net/uploads/201407/26/1406383210_8779.jpg",  
  44.             "http://img.my.csdn.net/uploads/201407/26/1406383172_4577.jpg"  
  45.     };  
  46.     private ImageView imageView;  
  47.     private Handler mUIHandler = new Handler(){  
  48.         @Override  
  49.         public void handleMessage(Message msg) {  
  50.             LogUtils.e("次数:"+msg.what);  
  51.             ImageModel model = (ImageModel) msg.obj;  
  52.             imageView.setImageBitmap(model.bitmap);  
  53.         }  
  54.     };  
  55.     @Override  
  56.     protected void onCreate(Bundle savedInstanceState) {  
  57.         super.onCreate(savedInstanceState);  
  58.         setContentView(R.layout.activity_handler_thread);  
  59.         imageView= (ImageView) findViewById(R.id.image);  
  60.         //创建异步HandlerThread  
  61.         HandlerThread handlerThread = new HandlerThread("downloadImage");  
  62.         //必须先开启线程  
  63.         handlerThread.start();  
  64.         //子线程Handler  
  65.         Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());  
  66.         for(int i=0;i<10;i++){  
  67.             //每个1秒去更新图片  
  68.             childHandler.sendEmptyMessageDelayed(i,1000*i);  
  69.         }  
  70.     }  
  71.     /** 
  72.      * 该callback运行于子线程 
  73.      */  
  74.     class ChildCallback implements Handler.Callback {  
  75.         @Override  
  76.         public boolean handleMessage(Message msg) {  
  77.             //在子线程中进行网络请求  
  78.             Bitmap bitmap=downloadUrlBitmap(url[msg.what]);  
  79.             ImageModel imageModel=new ImageModel();  
  80.             imageModel.bitmap=bitmap;  
  81.             imageModel.url=url[msg.what];  
  82.             Message msg1 = new Message();  
  83.             msg1.what = msg.what;  
  84.             msg1.obj =imageModel;  
  85.             //通知主线程去更新UI  
  86.             mUIHandler.sendMessage(msg1);  
  87.             return false;  
  88.         }  
  89.     }  
  90.     private Bitmap downloadUrlBitmap(String urlString) {  
  91.         HttpURLConnection urlConnection = null;  
  92.         BufferedInputStream in = null;  
  93.         Bitmap bitmap=null;  
  94.         try {  
  95.             final URL url = new URL(urlString);  
  96.             urlConnection = (HttpURLConnection) url.openConnection();  
  97.             in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);  
  98.             bitmap=BitmapFactory.decodeStream(in);  
  99.         } catch (final IOException e) {  
  100.             e.printStackTrace();  
  101.         } finally {  
  102.             if (urlConnection != null) {  
  103.                 urlConnection.disconnect();  
  104.             }  
  105.             try {  
  106.                 if (in != null) {  
  107.                     in.close();  
  108.                 }  
  109.             } catch (final IOException e) {  
  110.                 e.printStackTrace();  
  111.             }  
  112.         }  
  113.         return bitmap;  
  114.     }  
  115. }  
思路分析:在这个案例中,我们创建了两个Handler,一个用于更新UI线程的mUIHandler和一个用于异步下载图片的childHandler。最终的结果是childHandler会每个隔1秒钟通过sendEmptyMessageDelayed方法去通知ChildCallback的回调函数handleMessage方法去下载图片并告诉mUIHandler去更新UI界面。运行截图如下:
技术分享

到此,HandlerThread的基本使用我们都有所了解了,接下来我们掰掰HandlerThread源码,挖挖其实现原理。

三.HandlerThread源码解析

HandlerThread的源码不多只有140多行,我们一步一步来分析,先来看看其构造函数:
[java] view plain copy
 print?技术分享技术分享
  1. /** 
  2.  * Handy class for starting a new thread that has a looper. The looper can then be  
  3.  * used to create handler classes. Note that start() must still be called. 
  4.  */  
  5. public class HandlerThread extends Thread {  
  6.     int mPriority;//线程优先级  
  7.     int mTid = -1;  
  8.     Looper mLooper;//当前线程持有的Looper对象  
  9.     public HandlerThread(String name) {  
  10.         super(name);  
  11.         mPriority = Process.THREAD_PRIORITY_DEFAULT;  
  12.     }  
  13.       
  14.     /** 
  15.      * Constructs a HandlerThread. 
  16.      * @param name 
  17.      * @param priority The priority to run the thread at. The value supplied must be from  
  18.      * {@link android.os.Process} and not from java.lang.Thread. 
  19.      */  
  20.     public HandlerThread(String name, int priority) {  
  21.         super(name);  
  22.         mPriority = priority;  
  23.     }  
  24.       
  25.     /** 
  26.      * Call back method that can be explicitly overridden if needed to execute some 
  27.      * setup before Looper loops. 
  28.      */  
  29.     protected void onLooperPrepared() {  
  30.     }  
从源码可以很明显的看出HandlerThread继续自Thread,构造函数也相当简单传递参数有两个,一个是name指的是线程的名称,一个是priority指的是线程优先级,我们根据需要调用即可。成员变量mLooper就是HandlerThread自己持有的Looper对象。onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,但是重写时机是在Looper循环启动前。还记得我们在前面创建完HandlerThread后还要去调用start()方法后才可以去创建Handler吗?这是为什么呢?接下来我们就来揭晓其中奥秘:
[java] view plain copy
 print?技术分享技术分享
  1. @Override  
  2. public void run() {  
  3.         mTid = Process.myTid();  
  4.         Looper.prepare();  
  5.         synchronized (this) {  
  6.             mLooper = Looper.myLooper();  
  7.             notifyAll(); //唤醒等待线程  
  8.         }  
  9.         Process.setThreadPriority(mPriority);  
  10.         onLooperPrepared();  
  11.         Looper.loop();  
  12.         mTid = -1;  
  13.     }  

这个是HandlerThread的run方法,代码也比较简单,开始就通过Looper.prepare()去创建Looper对象,然后通过同步线程去给当前成员变量mLooper赋值,并唤醒等待线程(后续会解析为什么要唤醒等待线程),然后在Looper.loop()循环启动前调用了onLooperPrepared方法,到此Looper创建完成,循环线程也启动完成。现在我们也就明白了创建HandlerThread后为什么要调用start方法了,因为通过调用start方法,程序会去执行run方法,这样才会去创建Looper对象并启动Looper循环,最后我们才能把Looper对象传递给Handler实例。

[java] view plain copy
 print?技术分享技术分享
  1. public Looper getLooper() {  
  2.        if (!isAlive()) {  
  3.            return null;  
  4.        }  
  5.          
  6.        // If the thread has been started, wait until the looper has been created.  
  7.        synchronized (this) {  
  8.            while (isAlive() && mLooper == null) {  
  9.                try {  
  10.                    wait();//等待唤醒  
  11.                } catch (InterruptedException e) {  
  12.                }  
  13.            }  
  14.        }  
  15.        return mLooper;  
  16.    }  

这个方法名一看就知道是获取looper对象的,虽然很简单,但是这里有个地方还是要说明一下,方法开始后先去判断当前线程是否是启动状态,如果线程已经启动,再通过一个同步代码块去判断当前成员变量mLooper是否为空,如果为空,那就wait(),直到mLooper创建完成,否则就返回mLooper对象,那么为什么会由可能为空呢?还记得前面的Looper对象是在哪里创建的吗?没错,是在子线程,这样我们就无法保障我们在调用getLooper方法时Looper已经创建完成。因此在前面的run方法中当Looper创建完成后会调用notifyAll方法就是为了唤醒getLooper方法中的wait等待机制。小结:在获取mLooper对象的时候存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值。这里等待方法wait和run方法中的notifyAll方法共同完成同步问题。

[java] view plain copy
 print?技术分享技术分享
  1. public boolean quit() {  
  2.        Looper looper = getLooper();  
  3.        if (looper != null) {  
  4.            looper.quit();  
  5.            return true;  
  6.        }  
  7.        return false;  
  8.    }  
  9. public boolean quitSafely() {  
  10.     Looper looper = getLooper();  
  11.     if (looper != null) {  
  12.            looper.quitSafely();  
  13.            return true;  
  14.        }  
  15.        return false;  
  16.    }  

最后就是两个停止looper线程的方法了,以上有两种让当前线程退出循环的方法的区别就是quitSafely方法效率比quit方法标率低一点,但是安全。具体选择哪种就要看大家需求了。到此,HandlerThread源码就解析完了,相信大家对HandlerThread也有了比较全面的了解了,嗯,本篇结束。

HandlerThread 的使用及其源码完全解析