Android HandlerThread源码解析

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

1 使用方法如下

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

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

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

1 @Override 2 protected void onCreate(Bundle savedInstanceState) { 3 super.onCreate(savedInstanceState); 4 setContentView(R.layout.activity_main); 5 //1 开启线程 6 thread.start(); 7 8 //2 获取线程对应的looper,并用这个looper构造出一个Handler 9 //3 并重写Handler的handleMessage()方法 10 handler = new Handler(thread.getLooper()){ 11 @Override 12 public void handleMessage(Message msg) { 13 super.handleMessage(msg); 14 if(msg.what == 100){ 15 Log.e("TAG","线程名=" + Thread.currentThread().getName()); 16 Log.e("TAG","接收到的数据为:" + msg.obj.toString()); 17 } 18 } 19 }; 20 21 findViewById(R.id.tv_hello).setOnClickListener(new View.OnClickListener() { 22 @Override 23 public void onClick(View v) { 24 Log.e("TAG","线程名:" + Thread.currentThread().getName()); 25 Message message = handler.obtainMessage(); 26 message.what = 100; 27 message.obj = "hello world"; 28 handler.sendMessage(message); 29 } 30 }); 31 }

点击事件,输出如下:

12018-11-24 12:49:06.575 13589-13589/com.fax E/TAG: 线程名:main 22018-11-24 12:49:06.576 13589-13612/com.fax E/TAG: 线程名=test 32018-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类定义如下:

1public class HandlerThread extends Thread { 2 int mPriority; 3 int mTid = -1; 4 Looper mLooper; 5 private @Nullable Handler mHandler; 6 7 ...... 8}

HandlerThread从字面意思上看,是一个和Handler结合起来用的Thread。 再看HandlerThread的构造函数:

1public HandlerThread(String name) { 2 super(name); 3 mPriority = Process.THREAD_PRIORITY_DEFAULT; 4 }

构造函数仅仅是对线程的优先级和名字进行赋值。 接着往下看,我们调用了 thread.start() ,由于HandlerThread是一个继承Thread,所以会调用run()方法,源码如下:

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

通过上面可知: 1 HandlerThread就是一个线程类,在run()方法的开头调用了Looper.prepare()来创建一个线程对应的Looper对象,并保存起来。 2 在线程的最后面调用了Looper.loop()对消息进行循环。

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

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

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

1 /** 2 * @return a shared {@link Handler} associated with this thread 3 * @hide 4 */ 5 @NonNull 6 public Handler getThreadHandler() { 7 if (mHandler == null) { 8 mHandler = new Handler(getLooper()); 9 } 10 return mHandler; 11 }

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

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

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

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

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

1 public boolean quit() { 2 Looper looper = getLooper(); 3 if (looper != null) { 4 looper.quit(); 5 return true; 6 } 7 return false; 8 }

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

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )