HandlerThread 使用及其源码完全解析
关联篇:深入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界面。运行截图如下:
![](/assets/blank.gif)
到此,HandlerThread的基本使用我们都有所了解了,接下来我们掰掰HandlerThread源码,挖挖其实现原理。
三.HandlerThread源码解析
/*** 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 使用及其源码完全解析相关推荐
- java handlerthread_深入Android HandlerThread 使用及其源码完全解析
本篇我们将来给大家介绍HandlerThread这个类,以前我们在使用线程执行一个耗时任务时总会new一个Thread的线程去跑,当任务执行完后,线程就会自动被销毁掉,如果又由新的任务,我们又得新建线 ...
- 英雄远征Erlang源码分析(1)-源码结构解析
偶然得到了一份英雄远征的Erlang服务端源代码,想着通过对源代码的分析,来熟悉使用Erlang编程语言的游戏服务器的设计,游戏中关键逻辑的实现. 解压压缩文件后,在Idea内导入文件夹创建相关工程, ...
- 码出高效:Java开发手册笔记(线程池及其源码)
码出高效:Java开发手册笔记(线程池及其源码) 码出高效:Java开发手册笔记(线程池及其源码) 码出高效:Java开发手册笔记(线程池及其源码) 前言 一.线程池的作用 线程的生命周期 二.线程池 ...
- Mybatis拦截器的使用及其源码详解
Mybatis拦截器的使用及其源码详解 Mybatis相关全览 一.简介 执行与添加顺序 拦截器生效入口 二.使用 例子 三.原理 加载入口 生成代理 遍历拦截器 匹配&生成代理 四.实践例子 ...
- H.264/H265码流解析
H.264/H265码流解析 一.H.264码流解析 一个原始的H.264 NALU 单元常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成 一个原始的 ...
- [开源]C#二维码生成解析工具,可添加自定义Logo
原文:[开源]C#二维码生成解析工具,可添加自定义Logo 二维码又称 QR Code,QR 全称 Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的 Bar Co ...
- iOS开发之Masonry框架源码深度解析
Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁.Masonry简化了NSLayoutConstraint的使用方式,让 ...
- 【TarsosDSP】TarsosDSP 简介 ( TarsosDSP 功能 | 相关链接 | 源码和相关资源收集 | TarsosDSP 示例应用 | TarsosDSP 源码路径解析 )
文章目录 I . TarsosDSP 函数库简介 II . TarsosDSP 功能 III . TarsosDSP 相关资源链接 ( 官方资料 ) IV . TarsosDSP 源码和相关资源收集 ...
- 机器学习算法源码全解析(三)-范数规则化之核范数与规则项参数选择
前言 参见上一篇博文,我们聊到了L0,L1和L2范数,这篇我们絮叨絮叨下核范数和规则项参数选择.知识有限,以下都是我一些浅显的看法,如果理解存在错误,希望大家不吝指正.谢谢. 机器学习算法源码全解析( ...
最新文章
- UOJ#7. 【NOI2014】购票 | 线段树 凸包优化DP
- 机器学习漫谈:还有很长的路要走
- Stefan Tilkov:跳过单体应用,从微服务开始
- IIS中架设二级域名网站
- win10里安装和配置flex、bison
- yum安装mysql5.6
- 纯CSS实现table表头固定(自创备忘)
- 使用Python完美管理和调度你的多个任务
- 机器人动力学建模之理解惯性张量
- 注塑模具设计需要注意哪些要点?
- 网页设计经典案例(Web)
- Python可视化模块——Matplotlib(2)
- pycharm配置robot framework
- 短信转发器 SmsForwarder,备用机必备神器,开源免费
- 虚拟机下安装linux mysql weblogic过程
- Centos liunx系统在VM10虚拟机中的安装教程。
- 亿信华辰:企业如何做好大数据项目的选型
- C语言的历史和常见的标准
- matlab智能算法超级学习手册 pdf,MATLAB智能算法超级学习手册
- Linux创建分区步骤手写,手写笔记应用程序Write,附Write for Linux版的安装说明