前言

Android 多线程还有HandleThread,看名字就可以能感觉到得到,会是handler和Thread的综合使用。那到底是怎么样的呢,现在就跟随Android的源码来看看他的工作原理是什么样的。

我们先看看他的类注解:先看看官方对他的介绍:【 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.】简单的翻译就是这是一个很方便启动一个拥有looper的的类,这个Looper在这以后是用来创建handler类的,注意:thread的start()方法一定要被调用。

理解HandleThread

看到介绍可能觉得HandleThread有点厉害,有looper还能创建handler。我们抱着这样的心情去看HandleThread比较难受,所以,我们要换一下思路,这个HandleThread就是个Thread,这个Thread执行工作任务,我们通过handler给这个Thread安排工作。【从使用角度就相当于我们自己起一个工作线程,然后在线程中自己prepareLooper一样。只不过HandleThread已经把这些做好了,还封装了优先级设置,安全退出等一些辅助功能,让我们开发人员使用起来更加方便】

handler我们知道可以用来线程间通信,之前在Handler工作流程梳理里面分析过【具体在本篇就不细说】,我们这里要知道,Handler可以把Message发送到MessageQueue中,通过Looper.loop()循环将Message取出并分派到到相应的Handler去处理。同一个线程中只有一个MessageQueue和Looper, Message的处理是串行的。当有Thread使用了Handler,那么通过Handler给Thread安排任务也是串行执行的,就是一个执行完才执行下一个,所以这个HandleThread不适多耗时任务,这样任务的执行相互收影响比较严重。对于任务量小,使用频繁的任务来说就比较友好,可以使用一个线程来实现线程池的效果,节省了资源。

源码分析

看了上面的理解描述后,我们就可以猜到HandleThread代码逻辑应该不复杂,代码量也不会很大。事实就是这样,加上注解,HandleThread的代码在160多行。

/frameworks/base/core/java/android/os/HandlerThread.java

public class HandlerThread extends Thread {int mPriority;int mTid = -1;Looper mLooper;private @Nullable Handler mHandler;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;}...
}

HandleThread继承自Thread,首先我们就看到了该有的Looper对象,然后从它的构造函数看,可以设置线程优先级和给线程命名。在使用时如果不设置优先级,会默认设置为Process.THREAD_PRIORITY_DEFAULT;

HandleThread主要的逻辑就在run()方法里面

1.run()

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

在run()里。Looper.prepare()来初始化当前线程的一个Looper。然后拿到一个looper对象 赋值给mLooper,呃呃呃。这里为啥要notifyAll(),在Java中wait()和notify/notifyAll是一起使用的,主要用在多线程中,,用在这里又是为什么?我们在HandleThread搜索一下wait()果然有:

1.2 getLooper()

public Looper getLooper() {...// 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;
}

在getLooper()方法里,当looper还没有创建时间,需要当前线程保持wait,这是为什么呢?我估计是当线程启动,在没有获取到looper之前,当前线程是空闲的,也没任务做,就先释放资源,让其他线程先执行run()方法来获取Looper对象,然后将looper赋值给mLooper,【当我看到这里时是很疑惑的,这个在文章后面来介绍】;

再回到run()方法中;获mLooper赋值后,随后调用notifyAll(),【此时其他线程的getLooper()方法也会被唤醒,就可以利用looper去创建handler。然后再handleMessage()中实现处理任务的业务代码】;然后接下里就主要是进入loop循环了。这就之前分析handler看到的线程最后的状态是一毛一样,那就是随后线程都会进入到loop循环中。

以上就是HandleThread的主要原理了。

----------------------------------------------------------华丽丽的分割线--------------------------------------------------------------------------

疑惑

现在要来说说我在分析那个mlooper被赋值时遇到的疑惑,还记得我们在Handler工作流程梳理事看到Looper.getLooper()最后是从ThreadLoacl中取出的,在Android_ThreadLoacl原理一篇中,我们知道threadLocal保存的变量是与线程相关联的,所以不同线程get到的Looper是不一样的。

疑点来了啊,注意听。假如线程A运行到HandleThread的getLooper()方法,如果此时mlooper为空。那么线程A就会wait,阻塞在这里;那么后来线程B运行到HandleThread的run()方法,从线程B中获取到线程B的looper,然后赋值给mLooper;之后线程A就可以被唤醒,返回mLooper;那么这时候线程A返回的looper其实是在线程B中创建的looper,我就很乱了,,,不应该looper和线程相关的吗?在这里怎么可以这么玩?

反正我是很乱的,问了同事也还没问个明白。有一点肯定的,源码中这么玩肯定是没错的,我需要一个可以让自己信服的理由。

既然没问道直接的答案,按我自己试试,然后,我写了个demo,在主线程中获取主线程的MainLooper,然后在子线程创建Handler时直接传入MainLooper。最后发现, 子线程是可以使用主线程的looper。恩恩,终于,用事实说明了,多线是可以公用looper的。好了,HandleThread中的疑惑解决了;

接下来,我就有了新疑惑,我需要继续搞明白,为什么多线程可以共用looper。这和我之前的理解不一样,我之前的理解是线程中的looper和线程相关,当前线程只能使用当前线程的Looper。

解惑

答案还是得要在源码里面找,经过观察发现HandleThread和我们一般的在子线程中创建Handler的方式是不同的;

  • 子线程中直接我们一般是这样的:
/ * * <pre>*  class LooperThread extends Thread {*      public Handler mHandler;**      public void run() {*          Looper.prepare();**          mHandler = new Handler() {*              public void handleMessage(Message msg) {*                  // process incoming messages here*              }*          };**          Looper.loop();*      }*  }</pre>* /
  • 使用HandelrThread时是这样的
private HandlerThread mMyhandleThread = new HandlerThread("MyHandleThread");
Handler mHandler;
mMyhandleThread.start();
mHandler = new Handler(mMyhandleThread.getLooper()) {@Overridepublic void handleMessage(Message msg) {// TODO}
};

我感觉问题还是在Handler上。那我们就去看看Handler 的构造函数:

/frameworks/base/core/java/android/os/Handler.java

// 1.我们一般手动在子线程就是这样创建Handler的
public Handler() {this(null, false);
}
...
public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;
}
//------------------------华丽丽的分割线------------------------// 2.我们一般使用HandlerThread时是这样创建Handler的
public Handler(Looper looper) {this(looper, null, false);
}
...
public Handler(Looper looper, Callback callback, boolean async) {mLooper = looper;mQueue = looper.mQueue;mCallback = callback;mAsynchronous = async;
}

通过对不同方式创建Handler时调用的构造方法,我们发现 ,handler的如果在构造函数中指定了Looper,就直接使用传进来的looper对象,如果在构造事没有传入looper对象,则此时就需要使用本线程自己的looper;至于messageQueue只是和Looper相关,MessageQueue和handler并没有直接联系。

结论

所以我们得出结论,多线程可以使用同一个Looper,相应的也就是使用了相同的MessageQueue。

Android_HandlerThread 源码梳理相关推荐

  1. ed2k 网络中搜索资源并选择资源下载的分析及eMule源码梳理

    上一篇博客中,客户端已连接到ed2k网络及客户端与服务器交互的eMule源码梳理,这里将开始搜索资源并下载及客户端与客户端交互的eMule源码梳理 emule 源码下载地址  http://downl ...

  2. Android 源码梳理

    Android 源码梳理 前言 作为霜枫司机一年学习的总结,附上帅照一张. 目录 1. Android系统启动过程分析 2. Linux内核文件系统 3. Android进程间通信源码梳理 4. An ...

  3. 【ORB-SLAM2源码梳理5】关于双目帧Frame的构造函数

    文章目录 前言 一.双目图像帧Frame的构造函数 二.计算特征点匹配与成功匹配点对的深度ComputeStereoMatches() 三.具体过程 1. 准备阶段 2. 右目图每行特征点统计 3. ...

  4. vite预构建源码梳理

    对于"为什么要进行依赖预构建?"这个问题vite 文档已经解释的很清楚了,那么预构建大概的流程是什么样的呢? 启动预构建 从文档中我们知道在服务启动前会进行预构建,对应源码位置在s ...

  5. 【ORB-SLAM2源码梳理6】Track()函数的第一步:单目初始化MonocularInitialization()

    文章目录 前言 一.Track()函数 二.单目初始化MonocularInitialization() 1. 判断单目初始化器是否创建,若没有就创建. 2. 已创建初始化器,判断特征点数目 3. 在 ...

  6. Linux动态库加载函数dlopen源码梳理(一)

    下载了libc的源码,现在就开始libc源码的学习,最近了解到了linux动态库的相关知识,那么就从linux动态库加载函数dlopen进行梳理学习吧. 如果还没下载libc源码,可通过 https: ...

  7. 源码梳理——Jedis中的集合JedisByteHashMap

    一.JedisByteHashMap JedisByteHashMap是Jedis中实现存储键和值均为byte[]字节数组的Map集合类,它利用HashMap作为键-值对实际存储集合,对Map中的方法 ...

  8. vsomeip源码梳理 -- OfferService流程

    本文基于vsomeip 3.1.20.3总结而成 源码地址:https://github.com/GENIVI/vsomeip.git 本文主要涉及vsomeip库中的如下代码: implementa ...

  9. LineMod源码梳理

    LineMod算法 代码来源:https://github.com/meiqua/shape_based_matching 一.总体结构说明 1.Feature结构体描述了一个特征点,即(x,y)位置 ...

最新文章

  1. 如何在代码中获取Java应用当前的版本号?
  2. 12000+字Java反射,一起全面了解Java反射机制,为学习框架铺路
  3. android 添加随意拖动的桌面悬浮窗口,android 添加随意拖动的桌面悬浮窗口
  4. iOS音乐后台播放及锁屏信息显示
  5. android 应用开发(第70章)---二维码
  6. life of a NPTL pthread
  7. ubuntu12.04下安装NVIDIA驱动
  8. 浪漫星空python
  9. C#计算MD5结果不一致
  10. 机顶盒系统服务器忙,网络机顶盒卡顿怎么办?教你最新三个方法让系统更流畅...
  11. linux node安装菜鸟教程,Docker 安装 Node.js
  12. Python学习路线
  13. Laravel中使用GuzzleHttp调用第三方服务的API接口
  14. 某宝2020最新登录加密算法和滑小鸡算法
  15. 基于R语言的方差分析及多重比较
  16. uhs3内存卡有哪些_什么是UHS超高速SD卡?三代有什么区别
  17. 笔记本控制台开启热点
  18. Jenkins部署项目时,git报错fatal: index file smaller than expected
  19. 3万字总结!华三H3CNE知识点大集合,网络工程师收藏
  20. 彪马与国际象棋世界冠军Magnus Carlsen及国际象棋巡回赛开展合作

热门文章

  1. 大数据入门-五分钟读懂HDFS
  2. idea的maven项目中报错:前言中不允许有内容
  3. 对JSON数据进行排序
  4. Linux让终端只接受键盘输入
  5. pdf从结构新建书签_怎么让福昕的pdf自动生成书签
  6. CMMI认识和访谈的一些问题
  7. 【收藏】SVM算法原理(适合有一定基础的人观看)
  8. 耶鲁 计算机科学,耶鲁大学计算机科学世界排名2019年最新排名第16(THE世界排名)...
  9. Cadence Allegro PCB 铺铜管理
  10. mybatis 字段名自动转小写_mybatis 返回Map类型key改为小写的操作