在上一章Handler源码解析文章中,我们知道App的主线程通过Handler机制完成了一个线程的消息循环。那么我们自己也可以新建一个线程,在线程里面创建一个Looper,完成消息循环,可以做一些定时的任务或者写日志的功能。这就是HandlerThread的作用

Android Handler消息机制源码解析

1 使用方法如下

在MainActivity中添加一个HandlerThread的变量,如下:

public class MainActivity extends AppCompatActivity {
HandlerThread thread = new HandlerThread("test");
Handler handler;

在 onCreate()函数中开启线程,获取线程的looper,如下:

 @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1 开启线程
thread.start(); //2 获取线程对应的looper,并用这个looper构造出一个Handler
//3 并重写Handler的handleMessage()方法
handler = new Handler(thread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 100){
Log.e("TAG","线程名=" + Thread.currentThread().getName());
Log.e("TAG","接收到的数据为:" + msg.obj.toString());
}
}
}; findViewById(R.id.tv_hello).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("TAG","线程名:" + Thread.currentThread().getName());
Message message = handler.obtainMessage();
message.what = 100;
message.obj = "hello world";
handler.sendMessage(message);
}
});
}

点击事件,输出如下:

2018-11-24 12:49:06.575 13589-13589/com.fax E/TAG: 线程名:main
2018-11-24 12:49:06.576 13589-13612/com.fax E/TAG: 线程名=test
2018-11-24 12:49:06.576 13589-13612/com.fax E/TAG: 接收到的数据为:hello world

由上面可以看到,我们新建了一个Handler,对应的looper是从HandlerThread实例thead获取的,我们在点击事件中,获取一个消息并用handler分发,主线程发送的消息,在子线程中处理了。

比如我们有这样一个需求:

在用户使用APP的时候,需要记录用户的行为,需要把日志记录到本地文件中,等到一定的时机我们再统一一次性把文件上传到我们的服务器。

那么我们就可以开一个线程,在后台等待写日志的任务的消息到来,收到消息后就把日志顺序的写入到文件中。这时就可以用HandlerThread,省去了我们自己开线程,写任务队列,完成消息循环,这些HandlerThread都帮我们封装好了。下面我们来分析HandlerThread的源码。

2 HandlerThread源码分析

首付在使用的时候,我们直接 new 了一个HandlerThread对象 HandlerThread thread = new HandlerThread("test");

HandlerThread类定义如下:

public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler; ......
}

HandlerThread从字面意思上看,是一个和Handler结合起来用的Thread。

再看HandlerThread的构造函数:

public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

构造函数仅仅是对线程的优先级和名字进行赋值。

接着往下看,我们调用了 thread.start() ,由于HandlerThread是一个继承Thread,所以会调用run()方法,源码如下:

  @Override
public void run() {
//1 保存线程的id,没什么好说的
mTid = Process.myTid(); //2 主要是这句,调用了Looper.prepare()
// 由上篇Handler源码分析可知,这里创建了一个Looper对象
Looper.prepare(); //3 获取当前线程对应的Looper对象,保存起来
// 加锁是为了防止多线程问题
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority); //4 在循环之前有一个回调,空实现
onLooperPrepared(); //5 进行消息循环
Looper.loop();
mTid = -1;
}

通过上面可知:

1 HandlerThread就是一个线程类,在run()方法的开头调用了Looper.prepare()来创建一个线程对应的Looper对象,并保存起来。

2 在线程的最后面调用了Looper.loop()对消息进行循环。

所以如果外面想要用的话,HandlerThread必须有一个对外的方法,来返回当前线程对应的Looper对象,找一下源码,果然有一个getLooper()方法:源码如下:

    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实例,这样外面想用这个的时候,就可以调用getLooper()获取Looper对象,然后再创建一个Handler对象,并把looper传入,这样就可以在其它线程中发送消息,在当前创建的子线程中处理了。

既然这样,那么有没有这样一个方法,直接返回对应的Handler呢,里面就保存了Looper对象。还真有这样一个方法,如下:

  /**
* @return a shared {@link Handler} associated with this thread
* @hide
*/
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}

看这个方法,new 了一个Handler对象,并调用getLooper()把当前的Looper对象传入了,并返回了当前这个Handler对象

但是我们注意到,这个方法是@hide,就是我们在外面并不能调用这个方法,为什么Google已经写了这个方法但是又把这个方法给隐藏起来了不让我们调用呢?

个人猜测是因为我们调用getThreadHandler()的前提是得先调用start()方法,有了Looper对象后才能调用这个方法,要不获取到的Handler里面是没有Looper实例的,也就没法完成消息循环,所以Google把这个方法给隐藏了。

所以我们还是像上面的那样用法,先start(),再获取Looper对象,再创建Handler对象。

那么线程有运行的时候,也应该有退出的时候,当前有,我们看quit()方法:

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

这就是HandlerThread的源码,下篇我们讲IntentService的源码,和HandlerThread结合起来用的。

最新文章

  1. 【WP8.1开发】选择与搜索联系人
  2. 强大的<canvas>
  3. 在GitHub上管理项目
  4. PHP学习笔记:伪静态规则的书写
  5. C# 第三方DLL,可以实现PDF转图片,支持32位系统、64位系统
  6. Windows平台Atom编辑器的常用快捷键小抄Cheat Sheet
  7. JavaScript高级程序设计55.pdf
  8. php——composer 1、安装使用
  9. poj 3411 Paid Roads(dfs)
  10. UVA 1610 Party Games
  11. ifconfig 源码
  12. 神经网络 Neuroph - Java Neural Network Platform Neuroph
  13. linux route 路由设置小记
  14. Android Studio安装后配置默认新工程目录以及.gradle,.android,.m2和system,config目录
  15. 分布式系统里session同步
  16. 吉特日化MES-日化生产称料基本步骤
  17. 三种bean创建方式
  18. C语言实现随机生成0或1
  19. C#6.0语言规范(六) 转换
  20. 深入理解ajax系列第七篇——传递JSON

热门文章

  1. linux下查看网卡信息的命令
  2. Ubuntu16.04安装openjdk-7-jdk
  3. SQL server 子查询的应用
  4. js中对arry数组的各种操作小结 瀑布流AJAX无刷新加载数据列表--当页面滚动到Id时再继续加载数据 web前端url传递值 js加密解密 HTML中让表单input等文本框为只读不可编辑的方法 js监听用户的键盘敲击事件,兼容各大主流浏览器 HTML特殊字符
  5. sql server 关于表中只增标识问题 C# 实现自动化打开和关闭可执行文件(或 关闭停止与系统交互的可执行文件) ajaxfileupload插件上传图片功能,用MVC和aspx做后台各写了一个案例 将小写阿拉伯数字转换成大写的汉字, C# WinForm 中英文实现, 国际化实现的简单方法 ASP.NET Core 2 学习笔记(六)ASP.NET Core 2 学习笔记(三)
  6. 创建一个zookeeper的会话(实现watcher)
  7. day 18 面向对象的 继承
  8. HDU 4821 String 字符串hash
  9. jvm 命令
  10. CarbonData