关联篇:深入Android的消息机制源码详解-Handler,MessageQueue与Looper关系

关联篇:Handler内存泄漏及其解决方案

本篇我们将来给大家介绍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.创建实例对象

HandlerThread handlerThread = new HandlerThread("downloadImage");

参数的作用主要是标记当前线程的名字,可以任意字符串。

2.启动HandlerThread线程

//必须先开启线程handlerThread.start();

到此,我们就构建完一个循环线程。那么我们怎么将一个耗时的异步任务投放到HandlerThread线程中去执行呢?接下来看下面步骤:

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

  /*** 该callback运行于子线程*/class ChildCallback implements Handler.Callback {@Overridepublic boolean handleMessage(Message msg) {//在子线程中进行相应的网络请求//通知主线程去更新UImUIHandler.sendMessage(msg1);return false;}}

构建异步handler

//子线程Handler
Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());

第3步是构建一个可以用于异步操作的handler,并将前面创建的HandlerThread的Looper对象和Callback接口类作为参数传递给当前的handler,这样当前的异步handler就拥有了HandlerThread的Looper对象,而其中的handlerMessage方法来处理耗时任务,Looper+Handler+MessageQueue+Thread异步循环机制构建完成。下面我们来看一个使用案例

二.HandlerThread的使用案例

package com.zejian.handlerlooper;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.widget.ImageView;
import com.zejian.handlerlooper.model.ImageModel;
import com.zejian.handlerlooper.util.LogUtils;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
/*** Created by zejian on 16/3/5.*/
public class HandlerThreadActivity extends Activity {/*** 图片地址集合,图片来自网络.*/private String url[]={"https://img-my.csdn.net/uploads/201407/26/1406383299_1976.jpg","https://img-my.csdn.net/uploads/201407/26/1406383291_6518.jpg","https://img-my.csdn.net/uploads/201407/26/1406383291_8239.jpg","https://img-my.csdn.net/uploads/201407/26/1406383290_9329.jpg","https://img-my.csdn.net/uploads/201407/26/1406383290_1042.jpg","https://img-my.csdn.net/uploads/201407/26/1406383275_3977.jpg","https://img-my.csdn.net/uploads/201407/26/1406383265_8550.jpg","https://img-my.csdn.net/uploads/201407/26/1406383264_3954.jpg","https://img-my.csdn.net/uploads/201407/26/1406383264_4787.jpg","https://img-my.csdn.net/uploads/201407/26/1406383264_8243.jpg","https://img-my.csdn.net/uploads/201407/26/1406383248_3693.jpg","https://img-my.csdn.net/uploads/201407/26/1406383243_5120.jpg","https://img-my.csdn.net/uploads/201407/26/1406383242_3127.jpg","https://img-my.csdn.net/uploads/201407/26/1406383242_9576.jpg","https://img-my.csdn.net/uploads/201407/26/1406383242_1721.jpg","https://img-my.csdn.net/uploads/201407/26/1406383219_5806.jpg","https://img-my.csdn.net/uploads/201407/26/1406383214_7794.jpg","https://img-my.csdn.net/uploads/201407/26/1406383213_4418.jpg","https://img-my.csdn.net/uploads/201407/26/1406383213_3557.jpg","https://img-my.csdn.net/uploads/201407/26/1406383210_8779.jpg","https://img-my.csdn.net/uploads/201407/26/1406383172_4577.jpg"};private ImageView imageView;private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {LogUtils.e("次数:"+msg.what);ImageModel model = (ImageModel) msg.obj;imageView.setImageBitmap(model.bitmap);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler_thread);imageView= (ImageView) findViewById(R.id.image);//创建异步HandlerThreadHandlerThread handlerThread = new HandlerThread("downloadImage");//必须先开启线程handlerThread.start();//子线程HandlerHandler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());for(int i=0;i<10;i++){//每个1秒去更新图片childHandler.sendEmptyMessageDelayed(i,1000*i);}}/*** 该callback运行于子线程*/class ChildCallback implements Handler.Callback {@Overridepublic boolean handleMessage(Message msg) {//在子线程中进行网络请求Bitmap bitmap=downloadUrlBitmap(url[msg.what]);ImageModel imageModel=new ImageModel();imageModel.bitmap=bitmap;imageModel.url=url[msg.what];Message msg1 = new Message();msg1.what = msg.what;msg1.obj =imageModel;//通知主线程去更新UImUIHandler.sendMessage(msg1);return false;}}private Bitmap downloadUrlBitmap(String urlString) {HttpURLConnection urlConnection = null;BufferedInputStream in = null;Bitmap bitmap=null;try {final URL url = new URL(urlString);urlConnection = (HttpURLConnection) url.openConnection();in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);bitmap=BitmapFactory.decodeStream(in);} catch (final IOException e) {e.printStackTrace();} finally {if (urlConnection != null) {urlConnection.disconnect();}try {if (in != null) {in.close();}} catch (final IOException e) {e.printStackTrace();}}return bitmap;}
}

思路分析:在这个案例中,我们创建了两个Handler,一个用于更新UI线程的mUIHandler和一个用于异步下载图片的childHandler。最终的结果是childHandler会每个隔1秒钟通过sendEmptyMessageDelayed方法去通知ChildCallback的回调函数handleMessage方法去下载图片并告诉mUIHandler去更新UI界面。运行截图如下:

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

三.HandlerThread源码解析

HandlerThread的源码不多只有140多行,我们一步一步来分析,先来看看其构造函数:
/*** Handy class for starting a new thread that has a looper. The looper can then be * used to create handler classes. Note that start() must still be called.*/
public class HandlerThread extends Thread {int mPriority;//线程优先级int mTid = -1;Looper mLooper;//当前线程持有的Looper对象public HandlerThread(String name) {super(name);mPriority = Process.THREAD_PRIORITY_DEFAULT;}/*** Constructs a HandlerThread.* @param name* @param priority The priority to run the thread at. The value supplied must be from * {@link android.os.Process} and not from java.lang.Thread.*/public HandlerThread(String name, int priority) {super(name);mPriority = priority;}/*** Call back method that can be explicitly overridden if needed to execute some* setup before Looper loops.*/protected void onLooperPrepared() {}

从源码可以很明显的看出HandlerThread继续自Thread,构造函数也相当简单传递参数有两个,一个是name指的是线程的名称,一个是priority指的是线程优先级,我们根据需要调用即可。成员变量mLooper就是HandlerThread自己持有的Looper对象。onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,但是重写时机是在Looper循环启动前。还记得我们在前面创建完HandlerThread后还要去调用start()方法后才可以去创建Handler吗?这是为什么呢?接下来我们就来揭晓其中奥秘:

@Override
public void run() {mTid = Process.myTid();Looper.prepare();synchronized (this) {mLooper = Looper.myLooper();notifyAll(); //唤醒等待线程}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid = -1;}

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

 public Looper getLooper() {if (!isAlive()) {return null;}// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait();//等待唤醒} catch (InterruptedException e) {}}}return mLooper;}

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

 public boolean quit() {Looper looper = getLooper();if (looper != null) {looper.quit();return true;}return false;}public boolean quitSafely() {Looper looper = getLooper();if (looper != null) {looper.quitSafely();return true;}return false;}

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

HandlerThread 使用及其源码完全解析相关推荐

  1. java handlerthread_深入Android HandlerThread 使用及其源码完全解析

    本篇我们将来给大家介绍HandlerThread这个类,以前我们在使用线程执行一个耗时任务时总会new一个Thread的线程去跑,当任务执行完后,线程就会自动被销毁掉,如果又由新的任务,我们又得新建线 ...

  2. 英雄远征Erlang源码分析(1)-源码结构解析

    偶然得到了一份英雄远征的Erlang服务端源代码,想着通过对源代码的分析,来熟悉使用Erlang编程语言的游戏服务器的设计,游戏中关键逻辑的实现. 解压压缩文件后,在Idea内导入文件夹创建相关工程, ...

  3. 码出高效:Java开发手册笔记(线程池及其源码)

    码出高效:Java开发手册笔记(线程池及其源码) 码出高效:Java开发手册笔记(线程池及其源码) 码出高效:Java开发手册笔记(线程池及其源码) 前言 一.线程池的作用 线程的生命周期 二.线程池 ...

  4. Mybatis拦截器的使用及其源码详解

    Mybatis拦截器的使用及其源码详解 Mybatis相关全览 一.简介 执行与添加顺序 拦截器生效入口 二.使用 例子 三.原理 加载入口 生成代理 遍历拦截器 匹配&生成代理 四.实践例子 ...

  5. H.264/H265码流解析

    H.264/H265码流解析 一.H.264码流解析 一个原始的H.264 NALU 单元常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成 一个原始的 ...

  6. [开源]C#二维码生成解析工具,可添加自定义Logo

    原文:[开源]C#二维码生成解析工具,可添加自定义Logo 二维码又称 QR Code,QR 全称 Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的 Bar Co ...

  7. iOS开发之Masonry框架源码深度解析

    Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁.Masonry简化了NSLayoutConstraint的使用方式,让 ...

  8. 【TarsosDSP】TarsosDSP 简介 ( TarsosDSP 功能 | 相关链接 | 源码和相关资源收集 | TarsosDSP 示例应用 | TarsosDSP 源码路径解析 )

    文章目录 I . TarsosDSP 函数库简介 II . TarsosDSP 功能 III . TarsosDSP 相关资源链接 ( 官方资料 ) IV . TarsosDSP 源码和相关资源收集 ...

  9. 机器学习算法源码全解析(三)-范数规则化之核范数与规则项参数选择

    前言 参见上一篇博文,我们聊到了L0,L1和L2范数,这篇我们絮叨絮叨下核范数和规则项参数选择.知识有限,以下都是我一些浅显的看法,如果理解存在错误,希望大家不吝指正.谢谢. 机器学习算法源码全解析( ...

最新文章

  1. UOJ#7. 【NOI2014】购票 | 线段树 凸包优化DP
  2. 机器学习漫谈:还有很长的路要走
  3. Stefan Tilkov:跳过单体应用,从微服务开始
  4. IIS中架设二级域名网站
  5. win10里安装和配置flex、bison
  6. yum安装mysql5.6
  7. 纯CSS实现table表头固定(自创备忘)
  8. 使用Python完美管理和调度你的多个任务
  9. 机器人动力学建模之理解惯性张量
  10. 注塑模具设计需要注意哪些要点?
  11. 网页设计经典案例(Web)
  12. Python可视化模块——Matplotlib(2)
  13. pycharm配置robot framework
  14. 短信转发器 SmsForwarder,备用机必备神器,开源免费
  15. 虚拟机下安装linux mysql weblogic过程
  16. Centos liunx系统在VM10虚拟机中的安装教程。
  17. 亿信华辰:企业如何做好大数据项目的选型
  18. C语言的历史和常见的标准
  19. matlab智能算法超级学习手册 pdf,MATLAB智能算法超级学习手册
  20. Linux创建分区步骤手写,手写笔记应用程序Write,附Write for Linux版的安装说明

热门文章

  1. Pytorch入门+实战系列七:图片风格迁移和GAN
  2. Pulsar-io-iotdb Sink 开发步骤
  3. 复制模型到骨骼-脚本教程
  4. GT1030和730哪个好?GT1030与GT730区别对比 (全文)
  5. JSONObject的过滤
  6. XMU C语言程序设计实践(1)
  7. js中的全局作用域和局部的作用域
  8. java 好友推荐 算法_基于jsp的好友推荐-JavaEE实现好友推荐 - java项目源码
  9. 今天研发部来了一个看起来有50多岁的面试者
  10. np.argsort详解