HandlerThread源码解析

HandlerThread源码解析
一、HandlerThread是什么
在Android开发中,由于不能在主线程执行耗时任务,否则会导致应用出现ANR。因此,所有耗时任务,都需要开启一个线程进行处理。但是,对于一些需要频繁执行的异步任务,例如定期与服务器之间进行数据传输、交互等操作,多次创建线程或销毁线程,又会带来一些潜在的性能问题。
正是由于在Android开发过程中,有时会遇到这一种场景,HandlerThread就是专门用于处理这一种需求的异步处理机制。它的特点是在不通过手动销毁的情况下,会一直在后台处于循环等待状态,当接收到一个需要处理的耗时任务后,会立即进行耗时处理,执行完成后会再次进入循环等待状态。因此,HandlerThread可以用于处理一些定期需要执行的耗时任务,但它有个特点,就是一次只能处理一个耗时任务,如果这时有多个耗时任务都提交到了HandlerThread中,那么,这些耗时任务将会被排队执行。
二、HandlerThread和Thread的区别
HandlerThread是通过继承JavaJDK提供的Thread实现的,虽然这两者都是线程。但HandlerThread与Thread不同的地方在于,HandlerThread内部拥有一个轮询器Looper。
由于在Android开发中,Handler是专门用于发送消息和处理的,但是,如果在子线程中实例化Handler,应用就会产生异常。产生异常的原因主要是Handler在工作时,都需要一个MessageQueue消息队列来保存Message,而子线程中默认不会存在消息轮询器Looper,由因为MessageQueue是由Looper进行管理的。正是由于Android系统异步消息处理机制的原理,所以,在子线程中,如果未进行轮询器Looper的初始化,直接使用Handler,就会导致应用产生异常。
这里写图片描述

观察Handler的构造方法可以看出,Handler在构建时,会通过消息轮询器Looper获取mQueue这个实例,该实例就是消息队列MessageQueue。而在获取消息队列前,对Looper轮询器进行了null验证。也就是说,如果Handler在实例化时,如果没有获取到非空的Looper,此时就会抛出异常RuntimeException。正是由于在子线程中默认没有Looper,通过结合Handler的构造方法,这也就证实了,在子线程中直接实例化Handler,就会直接抛出该异常信息。
如果需要在子线程中使用Handler,并开启消息循环,就必须在Handler创建之前,完成Looper初始化,也就是必须先执行Looper的prepare()方法。为什么只有这样才能确保接下来可以正常使用Handler呢,接下来我们通过分析Looper的源码就可以看出。

这里写图片描述
在prepare这个方法中,首先通过阅读该方法的注释就可以看出,该方法用于初始化当前线程的轮询器Looper。在初始化过程中,首先通过ThreadLocal检查了当前线程是否已经存在Looper轮询器,如果当前线程已经初始化了轮询器,那么,此时同样会抛出RuntimeException异常,从而终止当前应用进程,这也是Looper轮询器只能在当前线程中初始化一次的原因。
接下来继续分析,如果当前线程没有初始化过Looper,这时会执行Looper轮询器的构造方法,并将已经初始化的Looper,记录到ThreadLocal中。
在Looper的构造方法中,由初始化了MessageQueue,同时记录了当前线程的句柄。由此可见,MessageQueue消息队列是由消息循环Looper来进行初始化并负责管理的。
这里写图片描述
接下来继续回到Handler的构造方法进行分析,可以看出,在构造方法中,是通过Looper的myLooper方法来获取轮询器Looper的。所以,接下来进入该方法进行分析。
这里写图片描述
查看该方法可以看出,是通过访问ThreadLocal变量来获取的Looper实例。分析到这里并结合前面分析的prepare方法就可以看出,在使用Handler时,如果没有通过Looper执行prepare,就会导致Handler抛出异常。
那为什么在四大组件中可以不通过调用Looper的prepare方法就可以直接实例化Handler呢,其原因在于Android底层已经默认将Looper进行了初始化,也就是将该过程给简化了,所以能直接实例化Handler。但是,在子线程中,默认并没有进行Looper轮询的初始化,所以,直接使用Handler就会抛出异常。
接下来分析HandlerThread的特点,它与Thread本质上的区别就是它内部拥有一个轮询器Looper,可以进行使HandlerThread始终进行后台轮询。另外一个特点就是如果将HandlerThread中的Looper对象传递给Handler对象,那么,这个Handler对象则可以在handleMessage方法中处理异步耗时任务。这就又证明了一点,Handler的handleMessage方法在执行时所属的线程,与Looper初始化时所在的线程有关。也就是说,如果Looper是在主线程中初始化的,那么,Handler的handleMessage方法就会执行在主线程。相反,则会在子线程中执行。
综上所述,由于Handler默认绑定的是主线程的消息循环Looper和消息队列,这样就可以通过消息机制实现异步处理。但是,对于需要通过消息机制来处理的后台异步任务,那么就可以使用HandlerThread来完成了。它在使用过程中,并不会对主线程进行阻塞,同时HandlerThread也不会被频繁创建,这就确保了定期执行的耗时操作,频繁创建和销毁线程时产生的内存开销。
但是,HandlerThread也有它自身的不足之处,由于Android消息机制是通过排队执行的,也就是在Looper的轮询过程中,只有当前这个执行完了,才会去执行下一个等待执行的操作。因此,用HandlerThread去处理异步耗时任务时,也只能去排队执行,它自身无法支持多个耗时任务的并行执行。
三、HandlerThread源码分析
首先通过阅读HandlerThread类的描述可以看出,在HandlerThread中,可以创建Handler,这将会与主线程的Handler有不同之处,通过HandlerThread中的Looper,创建的Handler可用于处理异步耗时操作。
这里写图片描述
由于HandlerThread继承自Thread,所以,当HandlerThread被启动后,run方法将会被执行。在HandlerThread中,它自身持有一个轮询器Looper实例,而这个轮询器的初始化,就是由run方法来完成的。在run方法中,首先通过prepare初始化了Looper实例,并将其记录到了Looper内部的ThreadLocal中,并且此时Looper内部也完成了HandlerThread线程内部的消息队列的初始化。接下来将会进入一个synchronized同步锁,在这个同步代码块当中,将会为HandlerThread中的轮询器进行赋值,结合前面对Looper源码的分析,可以看出,prepare初始化Looper后,就可以通过myLooper方法来获取当前线程的轮询器Looper实例。
但是,这里如果不通过synchronized同步锁来保护mLooper实例的获取,就会造成一个线程同步问题,也就会导致外部访问HandlerThread的中的Looper实例时,会出现非线程安全的问题。
因为要想通过消息机制和HandlerThread来实现异步任务的频繁执行,外部创建Handler时,就需要通过HandlerThread中的Looper来完成Handler创建。由于外部创建Handler是在主线程中进行的,而HandlerThread中的mLooper句柄赋值是在子线程中进行的,这就会存在线程同步问题,如果尚未通过synchronized同步锁机制,就会导致外部获取到的Looper实例为空,接下来就会导致Handler异步消息处理机制失效。
所以,通过查看run方法中的代码可以看出,mLooper轮询器句柄的赋值,是进行了synchronized同步锁机制进行了线程安全保护。
这里写图片描述
当synchronized代码块执行完成后,这里设置了线程的优先级,接下来执行了Looper的loop方法,这时异步消息循环将会被开启。此时,外部调用者就可以向与HandlerThread关联的Handler发起异步任务处理了。
这里写图片描述
在HandlerThread中,为外部提供了getLooper方法,外部可通过调用此方法,完成子线程Handler的创建。由于在HandlerThread的run方法中,对mLooper进行了synchronized同步处理,所以,当mLooper句柄未赋值完成时,getLooper方法将会被一直阻塞住,当run方法的同步代码块中的mLooper赋值后,就会通过notifyAll进行阻塞唤醒,这时getLooper方法中的wait将会结束,并向调用方返回初始化好的mLooper轮询器实例。
在HandlerThread中,提供了两种终止任务执行的方法,分别为quit和quitSafely。其中,quit表示强制终止,如果这时消息队列中存在待处理的异步任务,将不会被执行,这些待处理的异步任务将会失效;而quitSafely则是在所有待处理的异步任务都执行完了后,才会终止HandlerThread轮询器的。这两个方法的源代码如下所示。
这里写图片描述
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值