一、概述

本篇文章是对Android HandlerThread源码分析并应用,谈一下自己对HandlerThread的认知理解,并通过Demo来讲解它的使用方法。

二、HandlerThread是什么

2.1 概念

从字面上来讲,它是 Handler + Thread 的组合,首先它是一个Thread线程,它是用来做耗时任务用的,其次在HandlerThread中已经实现好了Looper消息循环处理,我们在应用的时候只需要通过Handler发送具体的消息给HandlerThread执行对应的任务即可。

首先明确一点:Handler 是Android系统  同进程  不同线程间 通信用的。

为了理解上面这句话,我们引入进程和线程的概念

进程:进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发。

线程:线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发,每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。

两者的区别

1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。

2. 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。

比如在某个Activity界面,默认有一个UI主线程,我们称为A线程,在里面创建一个Handler, 然后在创建一个线程B,在里面做耗时任务,待任务完成,在B中通过Handler发送消息,在A中更新UI显示,这样就相当于在把线程B中的消息分发到线程A中处理,从而完成了线程间通信。

为什么呢?因为线程A 和 线程B 可以共享数据段(Handler对象)。

2.2 特点

1. HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。

2. 优点是不会有堵塞,减少对性能的消耗,缺点是不能同时进行多任务的处理,需要排队,处理效率较低。

3. 与线程池注重并发不同,HandlerThread是一个窜行队列,因为HandlerThread背后只有一个线程。

三、源码解析

1. 两个构造方法

//构造方法,设置线程名称,默认线程优先级0
public HandlerThread(String name) {super(name);mPriority = Process.THREAD_PRIORITY_DEFAULT;
}//构造方法,设置线程名称和自定义线程优先级
public HandlerThread(String name, int priority) {super(name);mPriority = priority;
}

2. onLooperPrepared,此方法是 Looper消息循环前的准备工作,可以重写。

/*** Call back method that can be explicitly overridden if needed to execute some* setup before Looper loops.* Looper消息循环前的准备工作,可以重写*/
protected void onLooperPrepared() {}

3. run方法:当线程调用start()方法时,回调此方法

@Override
public void run() {mTid = Process.myTid(); //线程idLooper.prepare(); // 创建Looper对象和消息队列MessageQueuesynchronized (this) {  //通过同步锁机制获取当前线程的Looper对象mLooper = Looper.myLooper();notifyAll();  //通知getLooper方法looper已经创建成功,对应getLooper方法中的wait()}Process.setThreadPriority(mPriority); //设置线程优先级onLooperPrepared(); //重写此方法,作用是在消息循环之前进行一些准备工作Looper.loop(); //开启消息循环mTid = -1;
}

4. getLpoper()方法,与run()方法中有个同步锁机制,目的是为了准确且必定要获取当前线程的Looper对象

    /*** This method returns the Looper associated with this thread. If this thread not been started* or for any reason isAlive() returns false, this method will return null. If this thread* has been started, this method will block until the looper has been initialized.* @return The looper.此方法返回与此线程关联的Looper。如果此线程尚未启动或不处于活跃状态,
则此方法将返回null。如果此线程已启动,则此方法将阻塞,直到循环器已初始化。*/public Looper getLooper() {if (!isAlive()) {return null;}boolean wasInterrupted = false;// If the thread has been started, wait until the looper has been created.
// 使用对象同步锁机制:如果线程已开启, 进入等待状态直到looper成功初始化.synchronized (this) {while (isAlive() && mLooper == null) {try {wait(); //等待Looper初始化完成,对应run方法中的notifyAll} catch (InterruptedException e) {wasInterrupted = true;}}}/** We may need to restore the thread's interrupted flag, because it may* have been cleared above since we eat InterruptedExceptions*/if (wasInterrupted) {Thread.currentThread().interrupt();}return mLooper;}

为了理解上面synchronized同步锁机制,我们通过一个Demo来讲解

/*** author : me* date   : 22-11-14下午2:49* desc   : 线程测试类* version: 1.0*/
public class TestMain {public static void main(String[] args){Object object = new Object();MyThreadRunnable myThread1 = new MyThreadRunnable(object,"thread11111");Thread test1 = new Thread(myThread1);//通过public Thread(Runnable runnable)方法创建线程test1test1.start();//开始启动try {Thread.sleep(6000);System.out.println("6秒后调用notifiyAll方法唤醒线程,让线程继续执行任务");synchronized (object) {object.notifyAll();}} catch (InterruptedException  e) {e.printStackTrace();}}
}/*** author : me* date   : 22-11-14下午2:49* desc   :* version: 1.0*/
public class MyThreadRunnable implements Runnable {private Object object;private String threadName;public MyThreadRunnable(Object object, String threadName) {this.object = object;this.threadName = threadName;}@Overridepublic void run() {try {for (int i=1; i<6; i++) {if (i == 3) {synchronized (this.object) {System.out.println("当i==3时,线程调用wait方法 等待");this.object.wait();}}System.out.println(this.threadName + " i的值: " + i );Thread.sleep(1000);}} catch (InterruptedException e) {e.printStackTrace();}}
}

打印如下:

thread11111 i的值: 1
thread11111 i的值: 2
当i==3时,线程调用wait方法 等待
6秒后调用notifiyAll方法唤醒线程,让线程继续执行任务
thread11111 i的值: 3
thread11111 i的值: 4
thread11111 i的值: 5

通过此例子,再来理解上面3和4中的代码,就可以明白了。

5. getThreadHandler()方法

/*** @return a shared {@link Handler} associated with this thread* @hide
// 返回与该线程关联的handler对象
//注意这是一个隐藏的方法,无法直接通过 HandlerThread对象调用  getThreadHandler() 获取Handler
*/@NonNullpublic Handler getThreadHandler() {if (mHandler == null) {mHandler = new Handler(getLooper());}return mHandler;}

此方法可以忽略,是一个hide方法,无法直接调用获取Handler, 一般还是得通过

public Handler(@NonNull Looper looper)方法来 获取handler对象, 比如

Handler mThreadHandler = new Handler(myHandlerThread.getLooper()) {@Overridepublic void handleMessage(@NonNull Message msg) {//消息处理}}

6. quit()和quitSafely()方法

// 直接退出线程的looper消息循环,强制退出循环public boolean quit() {Looper looper = getLooper();if (looper != null) {looper.quit();return true;}return false;}//待消息队列中剩余消息全部处理完毕后,退出线程的looper消息循环,安全退出消息循环public boolean quitSafely() {Looper looper = getLooper();if (looper != null) {looper.quitSafely();return true;}return false;}

四、HandlerThread使用步骤

// 步骤1:创建HandlerThread实例对象HandlerThread mHandlerThread = new HandlerThread("handlerThread");// 步骤2:启动线程mHandlerThread.start();//如果不调用start方法,会报错:
11-15 17:30:32.347  8763  8763 E AndroidRuntime: Caused by: java.lang.NullPointerException: Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue' on a null object reference
11-15 17:30:32.347  8763  8763 E AndroidRuntime:    at android.os.Handler.<init>(Handler.java:257)
11-15 17:30:32.347  8763  8763 E AndroidRuntime:    at android.os.Handler.<init>(Handler.java:162)//通过报错的堆栈log信息找到源码:Handler.java:257
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {mLooper = looper;mQueue = looper.mQueue;  //这行代码报错,空对象异常mCallback = callback;mAsynchronous = async;
}
//looper为空对象,说明looper对象没有创建成功。 这也间接地表明 源码中通过同步锁机制必须保证要成功创建Looper对象的缘由。// 步骤3:创建工作线程Handler 并 复写handleMessage()
//关联HandlerThread的Looper对象、实现消息处理操作与其他线程进行通信Handler workHandler = new Handler( mHandlerThread.getLooper() ) {@Override//消息处理public boolean handleMessage(Message msg) {.......//当然这里可以做耗时任务,因为是处于子线程中,不会堵塞UI线程,不会引起ANRreturn true;}});// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息Message msg = Message.obtain();msg.what = 1;workHandler.sendMessage(msg);// 步骤5:一般在Activity的OnDestory()方法中,结束线程,即停止线程的Looper消息循环mHandlerThread.quit();

五、实例演示

/*** author : me* date   : 22-11-15  下午3:46* desc   : 主界面* version: 1.0*/public class MainActivity extends AppCompatActivity {private Button mButton1;private Button mButton2;private MyHandlerThread myHandlerThread;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mButton1 = findViewById(R.id.btn_one);mButton2 = findViewById(R.id.btn_two);//1. 创建HandlerThread实例对象myHandlerThread = new MyHandlerThread("handlerThread test");//2. 启动线程myHandlerThread.start();//3. 创建工作线程Handler, 并复写handleMessageHandler mThreadHandler = new Handler(myHandlerThread.getLooper()) {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if (msg.what == 1) {try {Log.e("test", "用线程睡眠6秒,模拟耗时任务,此任务是在子线程中处理的,所以不会引起ANR");Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}Log.e("test", "====接收主线程中发来的消息  处理消息的线程名称为:" + Thread.currentThread().getName());} else if (msg.what == 2) {try {Log.e("test", "用线程睡眠12秒,模拟耗时任务,此任务是在子线程中处理的,所以不会引起ANR");Thread.sleep(12000);} catch (InterruptedException e) {e.printStackTrace();}Log.e("test", "====接收自定义线程中发来的消息  处理消息的线程名称为:" + Thread.currentThread().getName());}}};//点击此按钮: UI主线程 与 HandlerThread线程 进行通信mButton1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Message msg = Message.obtain();msg.what = 1;mThreadHandler.sendMessage(msg);Log.e("test", "调用线程的名称:" + Thread.currentThread().getName());}});//点击此按钮: 自定义线程 与 HandlerThread线程 进行通信mButton2.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {Message msg = Message.obtain();msg.what = 2;mThreadHandler.sendMessage(msg);Log.e("test", "调用线程的名称:" + Thread.currentThread().getName());}});thread.start();}});@Overrideprotected void onDestroy() {super.onDestroy();//退出app时,子线程中的looper要退出,终止消息循环myHandlerThread.quitSafely();}}/*** author : me* date   : 22-11-14下午6:09* desc   :* version: 1.0*/
public class MyHandlerThread extends HandlerThread {public MyHandlerThread(String name) {super(name);}@Overrideprotected void onLooperPrepared() {//如果需要执行某些Looper循环之前的设置,可以在该方法中处理super.onLooperPrepared();Log.e("test", "=====onLooperPrepared======");}@Overridepublic boolean quitSafely() {Log.e("test", "=====quitSafely======");return super.quitSafely();}
}

当进入主界面,点击button1,然后退出app,打印log如下:

11-16 17:18:49.977 23277 23384 E test    : =====onLooperPrepared======
11-16 17:18:53.121 23277 23277 E test    : 调用线程的名称:main
11-16 17:18:53.121 23277 23384 E test    : 用线程睡眠6秒,模拟耗时任务,此任务是在子线程中处理的,所以不会引起ANR
11-16 17:18:59.122 23277 23384 E test    : ====接收主线程中发来的消息  处理消息的线程名称为:handlerThread test
11-16 17:21:33.309 23277 23277 E test    : =====quitSafely======

当进入主界面,点击button2,然后退出app,打印log如下:

11-16 17:22:43.080 23277 23620 E test    : =====onLooperPrepared======
11-16 17:22:44.758 23277 23622 E test    : 调用线程的名称:Thread-2
11-16 17:22:44.758 23277 23620 E test    : 用线程睡眠12秒,模拟耗时任务,此任务是在子线程中处理的,所以不会引起ANR
11-16 17:22:56.759 23277 23620 E test    : ====接收自定义线程中发来的消息  处理消息的线程名称为:handlerThread test
11-16 17:23:06.764 23277 23277 E test    : =====quitSafely======

好了,通过此Demo可以知道Handler可以在不同进程间通信,并且了解HandlerThread的使用方法啦。

总结

一. 关于Handler对象创建,取决于与哪个线程的Looper挂钩.

       1. Handler  uiHandler =  new Handler()  或 new  Handler(getMainLooper()) 或 new Handler(Looper.myLooper());   这3个方法都是创建主线程的Handler对象.

       2. Handler  workHandler = new Handler(new  HandlerThread("workthread").getLooper());  它是创建子线程中的Handler对象.

通过workHandler 这个来发送消息,然后在handleMessage方法中处理任务,因为是任务在子线程中处理,所以不会引起ANR现象.

二. 使用注意

HandlerThread在线程内部创建Looper和消息队列,并且在线程启动之后开启消息循环。我们可以使用该Looper构建Handler对象从而绑定工作线程,然后通过Handler向消息队列发送消息,这样HandlerThread就可以取出消息并根据消息类型执行具体的后台任务,这里执行任务是在子线程中,它不会阻塞UI线程,所以不会引起ANR现象。

        由于HandlerThread的run方法是一个无限循环,因此当我们不再使用HandlerThread的时候应该调用它的quit或quitSafely方法来终止线程。

HandlerThread源码理解相关推荐

  1. 【Android 异步操作】Handler 机制 ( Handler 常用用法 | HandlerThread 简介 | HandlerThread 源码注释分析 )

    文章目录 一.Handler 常用用法 二.HandlerThread 简介 三.HandlerThread 源码 一.Handler 常用用法 主线程 Handler 主要作用 : Looper 和 ...

  2. 从hotspot底层对象结构理解锁膨胀升级过程||深入jdk源码理解longadder的分段cas优化机制——分段CAS优化

    深入jdk源码理解longadder的分段cas优化机制 longadder

  3. faster rcnn源码理解(二)之AnchorTargetLayer(网络中的rpn_data)

    转载自:faster rcnn源码理解(二)之AnchorTargetLayer(网络中的rpn_data) - 野孩子的专栏 - 博客频道 - CSDN.NET http://blog.csdn.n ...

  4. faster rcnn的源码理解(一)SmoothL1LossLayer论文与代码的结合理解

    转载自:faster rcnn的源码理解(一)SmoothL1LossLayer论文与代码的结合理解 - 野孩子的专栏 - 博客频道 - CSDN.NET http://blog.csdn.net/u ...

  5. TLD(Tracking-Learning-Detection)学习与源码理解之(六)

    TLD(Tracking-Learning-Detection)学习与源码理解之(六) zouxy09@qq.com http://blog.csdn.net/zouxy09 下面是自己在看论文和这些 ...

  6. TLD(Tracking-Learning-Detection)学习与源码理解之(五)

    TLD(Tracking-Learning-Detection)学习与源码理解之(五)   zouxy09@qq.com http://blog.csdn.net/zouxy09 下面是自己在看论文和 ...

  7. TLD(Tracking-Learning-Detection)学习与源码理解之(四)

    TLD(Tracking-Learning-Detection)学习与源码理解之(四) zouxy09@qq.com http://blog.csdn.net/zouxy09 下面是自己在看论文和这些 ...

  8. TLD(Tracking-Learning-Detection)学习与源码理解之(三)

    TLD(Tracking-Learning-Detection)学习与源码理解之(三) zouxy09@qq.com http://blog.csdn.net/zouxy09 下面是自己在看论文和这些 ...

  9. TLD(Tracking-Learning-Detection)学习与源码理解之(二)

    TLD(Tracking-Learning-Detection)学习与源码理解之(二) zouxy09@qq.com http://blog.csdn.net/zouxy09 OpenTLD下载与编译 ...

最新文章

  1. Android内核开发:源码的版本与分支详解
  2. SIC插槽,WSIC插槽,XSIC插槽
  3. 苹果应用开发架构及项目结构
  4. Java 中JProgressBar,Java JProgressBar
  5. android+4.2+mtp+在此设备上不支持+文件类型,Nexus 4无法通过MTP显示文件
  6. Linux 远程开机(walk on lan)
  7. nssl1269-射击【贪心,堆】
  8. aix 查看内存,CPU 配置信息
  9. c++学习笔记(16) 递归
  10. mysql odbc 卸载_Linux卸载MySQL
  11. jsonp原理详解——终于搞清楚jsonp是啥了
  12. 这种铜公加工时很容易变开,加工时要用新刀,刀要小点,进刀也不能太大
  13. 淘宝京东的6位数字支付密码实现
  14. java简单识别闰年和平年问题
  15. matlab练习程序(渲染三原色)
  16. 数学公式快速计算方法
  17. ChatGPT助力之论文速成秘籍
  18. 关于堆栈的讲解(我见过的最经典的)
  19. Allegro使用 Z-Copy绘制 Rout Keepin
  20. 大数据-安装 Hadoop3.1.3 详细教程-单机/伪分布式配置(Centos)

热门文章

  1. 我需要几个变量来记录游戏声音大小,游戏音效大小,游戏是否静音,请帮我给这些变量取名...
  2. 大学生创业成功率仅有5%,剩下95%到底缺什么?
  3. 新款最火表情包壁纸独立后台美化二开版本新增加喝酒神器功能微信小程序源码下载+教程自动采集
  4. hdoj 1045 Fire Net 直接枚举 模拟就好了
  5. 一键隐藏kodi 一键隐藏app amlogic rockchip tvbox
  6. XMU C语言程序设计实践(1)
  7. Pr教程之如何制作文字书写效果
  8. 欢庆“腊八节” 美国纽约华埠居民掀“聚餐热”
  9. “植物的生殖”知识总结
  10. iOS 获取系统时间(服务器时间)问题