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

Android Handler消息机制源码解析

1 使用方法如下

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

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

点击事件,输出如下:

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

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

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

再看HandlerThread的构造函数:

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

通过上面可知:

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

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

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

  1. public Looper getLooper() {
  2. if (!isAlive()) {
  3. return null;
  4. }
  5. // If the thread has been started, wait until the looper has been created.
  6. synchronized (this) {
  7. while (isAlive() && mLooper == null) {
  8. try {
  9. wait();
  10. } catch (InterruptedException e) {
  11. }
  12. }
  13. }
  14. return mLooper;
  15. }

返回当前线程的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结合起来用的。

Android HandlerThread源码解析的更多相关文章

  1. Android -- AsyncTask源码解析

    1,前段时间换工作的时候,关于AsyncTask源码这个点基本上大一点的公司都会问,所以今天就和大家一起来总结总结.本来早就想写这篇文章的,当时写<Android -- 从源码解析Handle+ ...

  2. Android HandlerThread 源码分析

    HandlerThread 简介: 我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了.如果此时我又有一 个耗时任务需要执行,我们不得不重新创建线 ...

  3. 【Android】IntentService & HandlerThread源码解析

    一.前言 在学习Service的时候,我们一定会知道IntentService:官方文档不止一次强调,Service本身是运行在主线程中的(详见:[Android]Service),而主线程中是不适合 ...

  4. Android AsyncTask 源码解析

    1. 官方介绍 public abstract class AsyncTask extends Object  java.lang.Object    ↳ android.os.AsyncTask&l ...

  5. Android EventBus源码解析 带你深入理解EventBus

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...

  6. Android -- 从源码解析Handle+Looper+MessageQueue机制

    1,今天和大家一起从底层看看Handle的工作机制是什么样的,那么在引入之前我们先来了解Handle是用来干什么的 handler通俗一点讲就是用来在各个线程之间发送数据的处理对象.在任何线程中,只要 ...

  7. Android LayoutInflater源码解析:你真的能正确使用吗?

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 好久没写博客了,最近忙着换工作,没时间写,工作刚定下来.稍后有时间会写一下换工作经历.接下来进入本篇主题,本来没想写LayoutInflater的 ...

  8. Android——LruCache源码解析

    以下针对 Android API 26 版本的源码进行分析. 在了解LruCache之前,最好对LinkedHashMap有初步的了解,LruCache的实现主要借助LinkedHashMap.Lin ...

  9. 还怕问源码?Github上神级Android三方源码解析手册,已有7.6 KStar

    或许对于许多Android开发者来说,所谓的Android工程师的工作"不过就是用XML实现设计师的美术图,用JSON解析服务器的数据,再把数据显示到界面上"就好了,源码什么的,看 ...

随机推荐

  1. JAVA设计模式之单例模式(转)

    本文继续介绍23种设计模式系列之单例模式. 概念: java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单 ...

  2. 【微信支付】分享一个失败的案例 跨域405(Method Not Allowed)问题 关于IM的一些思考与实践 基于WebSocketSharp 的IM 简单实现 【css3】旋转倒计时 【Html5】-- 塔台管制 H5情景意识 --飞机 谈谈转行

    [微信支付]分享一个失败的案例 2018-06-04 08:24 by stoneniqiu, 2744 阅读, 29 评论, 收藏, 编辑 这个项目是去年做的,开始客户还在推广,几个月后发现服务器已 ...

  3. Android NDK JNI WARNING: illegal start byte 0x

    今天攻克了JNI WARNING: illegal start byte 0x81这个问题. 问题出现的现象是通过jni调用加密方法,调用之后返回密文内容,结果就出现这个问题. 在国外查找一段时间之后 ...

  4. Java网络编程知识点(1)

    怎样将一个String对象转换成InputStream对象? ByteArrayInputStream inputStream = new ByteArrayInputStream(str.getBy ...

  5. xcode4中build Settings常见参数解析

    本文转载至 http://shiminghua234.blog.163.com/blog/static/263912422012411103526386/     1.Installation Dir ...

  6. 组合式+迭代式+链式 MapReduce

    1.迭代式mapreduce 一些复杂的任务难以用一次mapreduce处理完成,需要多次mapreduce才能完成任务,例如Pagrank,Kmeans算法都需要多次的迭代,关于mapreduce迭 ...

  7. C++虚复制构造函数,设置Clone()方法返回基类指针,并设置为虚函数

    构造函数不能是虚函数.但有时候确实需要能传递一个指向基类对象的指针,并且有已创建的派生类对象的拷贝.通常在类内部创建一个Clone()方法,并设置为虚函数. //Listing 12.11 Virtu ...

  8. VC FTP服务器程序分析(二)

    上面讲到了CClientThread类,打开这个类的实现,这个类实现了4个函数.依次分析: 1.InitInstance   其说明如下:InitInstance必须被重载以初始化每个用户界面线程的新 ...

  9. scrollView 代理方法的实现顺序的些许区别

  10. (linux)container_of()宏

      在学习Linux驱动的过程中,遇到一个宏叫做container_of. 该宏定义在include/linux/kernel.h中,首先来贴出它的代码: /**  * container_of - ...