目录
- 前言及消息机制简述
- Handler的日常使用
- HandlerThread和IntentService中的Handler
- Handler 使用以及源码全面解析(二)
一、前言及概述
1、前言
这篇剖析是以前做的小笔记,是时候拿出来更新完善下并共享给各位指点下了。
不管是小白还是老鸟,入门还是复习,我觉得这两篇看下来,都是会有所收获的。当然老鸟还是直接看第二篇吧。
这第一篇来说,先来看看这几个问题:
- 主线程的Looper什么时候quit(),无限循环为什么不会导致ANR?
- UI为什么“只能”在主线程刷新?
- 主线程怎么跟其它线程通信?
2、概述
消息机制简述:
在Android 中,消息机制属于必须掌握基本技能,从点击应用按钮开始,它就无处不在!
Andriod
线程间的通信(消息机制)是通过Handler
的运行机制(包括Handler、Message、MessageQueue、Looper
)来实现的!
主线程在初始时就创建了一个
Looper
对象,这个Looper
会直接使用for(;;)
进行无限循环从其对应的MessageQueue
中读取Message
并执行,没有消息则进入阻塞并等待新消息的到来。
而Message
是主线程或其它线程通过Handler
来往MessageQueue
中添加的
举个简单的例子,先不论我们在启动app的过程,Handler扮演的角色。先看看我们在启动之后的。看下面代码:
//Activity上的一个按钮
btn_1.setOnClickListener {
textView1.text = "哈哈哈" // 1
Handler().sendEmptyMessage(0) // 2
}
点击一个按钮,触发了onClick()
方法,问,是怎么触发的呢?Handler有用到吗?
textView1.text = "哈哈哈"
这句也会有Handler的参与吗?
这里首先明确一点就是:onClick()
方法是主线程执行的。
所以必然是在我们点击按钮之后,某个地方有用主线程的Handler往MessageQueue中添加了一个Message.onClick()方法体才得以执行。反之亦然。
即逻辑鬼才反推,所以onClick()方法是主线程执行的是正确的。
至于 textView1.text = "哈哈哈"
这一句表面上没有,但实际上,这里是在更新UI,更新UI就要刷新屏幕,刷新屏幕就会有主线程的配合( 比如textView1.onDraw()
的执行),那也就会有Handler的存在~
至于Handler().sendEmptyMessage(0)
...嗯,就不多说了。。
凡任何在主线程执行的代码,其源头必然是通过有经过Handler处理。
当然,排除在主线程的Looper创建之前的那为数不多的漏网之鱼。
这里回答第一个问题,主线程的无限循环为什么不会导致ANR,以及什么退出循环?
- ANR首先是因为主线程在限定的时间内没有开始或完成某些操作A,比如触摸事件或者收到广播后的执行动作。而这些事件A都是通过Handler往主线程的消息队列中添加消息,也就是当主线程在执行某些其他操作B时候,导致在排队中的某个事件A超时未能得到执行,从而导致ANR。
所以ANR跟Looper的无限循环是没有直接关系的。
至于主线程如果退出了循环,那么就再也收不到任何事件了,那么应用也该挂了。
二、Handler的日常使用
使用一般两种情况
- 线程内自己给自己发消息,指在将来的某个时间点去执行消息。也就是延时执行。
- 线程间的通信,典型就是非UI线程将消息传递给主线程让其更新UI
这里顺便引出的问题就是为什么不直接在非UI线程更改UI?
答:UI的刷新更改只能由主线程来完成,子线程需要通过消息传递给主线程来完成对UI的更改。
而之所以只能UI线程更改:
- UI控制是线程不安全的,多线程并发使UI控件处于不可控的状态。
- 为何不多线程然后上锁?一是会将UI逻辑变得复杂,二是上锁显然降低UI的执行效率。
界面流畅度大过天!
实际上!android 界面规则是:Window由哪个线程创建,就只能通过该线程更新。
此Window包括(Activity、Dialog以及各种通过WindowManager.addView()的窗口)
ViewRootImp.java
void checkThread() { //View在刷新绘制之前会先判断当前正在操作的线程
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
有些本来只是在搜索Handler的用法的小白开始os了,还BB,赶紧 show me the code~, 不然换个链接查找资料了
来了来了,简易的API解说:
1.主线程中:
重写一遍上面的描述:
主线程在初始时就创建了一个
Looper
对象,这个Looper
会直接使用for(;;)
进行无限循环从其对应的MessageQueue
中读取Message
并执行,没有消息则进入阻塞并等待新消息的到来。
而Message
是主线程或其它线程通过Handler
来往MessageQueue
中添加的
所以在主线程中,我们需要处理的就是使用Handler往MessageQueue添加Message.
有下面几个常用Api:
val message = Message()
val message2 = Message.obtain() //从系统缓存的MessagePool中取出回收的Message对象,建议使用
message.what = 2 //message的标志,用于开发者判断message的类型
message.arg1 = 1 // message可选附带的参数, int类型
message.arg2 = 2 // int类型参数
message.obj = "参数3" // obj 为任意类型
message.data = Bundle() // 可选附带的参数,Bundle()
发消息的三种方式
1.第一种方式:发送 messager.what = 1的 Message
handler.sendEmptyMessage(1)
//handler.sendEmptyMessageDelayed(1, 5000) // 延迟5s处理
2. 第二种方式 : 普通方式
val message = Message.obtain()
messager.what = 1
handler.sendMessage(message)
//handler.sendMessageDelayed(message, 5000)
3. 第三种方式 :
handler.post(Runnable {
//本质依然是Message, Message还有个 Runnable变量,系统直接执行Runnable中代码,无需设置what之类的
})
handler.postDelayed(Runnable {
}, 5000)
上述三种方式都是将消息添加到消息队列中,比如handler().sendEmptyMessageDelayed(0, 5000)
发送了一个what=0
的 message
。
那么当Looper
取出这条消息的时候,由谁来执行呢?又执行什么那些代码呢?
如果是第三种方式,那么消息取出来后,就直接执行Runable内的内容。
否则则还是需要Handler来处理。handler在创建的时候,重写handleMessage(msg: Message)
方法
val handler = object : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
0 -> {
Log.i(TAG, "handleMessage: 0")
}
1 -> {
Log.i(TAG, "handleMessage: 1")
}
else -> {
}
}
}
}
当消息被取出来之后,就会自动调用handle.handlerMessage(msg: Message)
,执行你所要做的操作。
所以对于handler的使用上手就是只要
使用handler.sendMessage()
然后通handler.handlerMessage()
处理消息
上手是不是感觉简单到爆!
当然这里需要注意一个内存泄漏的问题!
毕竟定义变量handler
时重写handleMessage()
之后就变成了一个匿名内部类,那么会持有外部类变量(一般来说就是Activity), 那么当我们发送延迟消息可能引发内存泄漏。
- 简单点做法,在
Activity.onDestory()
时handler.removeCallbacksAndMessages(null)
将所有与此Handler有关的消息从消息队列移除即可。 - 把Handler变量定义为静态内部类即可,如果在handlerMessage()需要使用到Activity的东西,Handler内持有Activity的弱引用来处理。
class MainActivity : AppCompatActivity() {
class MyHandler(context: MainActivity) : Handler() { // kotlin默认静态内部类
private val activity: WeakReference<MainActivity> = WeakReference(context)
override fun handleMessage(msg: Message?) {
when(msg?.what) {
1 -> {
activity.get()?.let {
// do Something
}
}
}
super.handleMessage(msg)
}
}
val myHandler = MyHandler(this)
...
}
2. 继续看线程间通信的处理。
Handler有两个构造方法:
public Handler() //默认使用当前线程的Looper。
public Handler(Looper looper)
Handler
在创建的时候,需要获取本线程的Looper
, 这样才能准确的将Message
投入Looper
所对应的MessageQueue
中。如果还没有创建Looper,就会发生异常崩溃。
注意:每条线程只能通过Looper.prepare()
去创建本线程的Looper
,而且只能创建一次!也就是一个线程对应一个Looper,多了会怎样呢?那就只有💥💥💥
那就是假设
handler
对象在线程A被定义,那就不管handler.sendMessage()
在其他的哪条线程执行,handlerMessage()
只会在handler
所对应Looper所在的线程中执行。
所以普通线程给主线程传递消息是,两种方式:
1.直接使用在主线程创建的Handler变量
class BlankFragment : Fragment() {
val mHandler = object :Handler() {
override fun void handleMessage() {
....
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val thread = Thread({
mHandler.sendEmptyMessage(0) // 给主线程发消息
})
thread.start()
}
}
2. 在工作线程中创建使用主线程Looper的Handler
class BlankFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val thread = Thread({
// 在主线程执行的Handler
val handler = Handler(Looper.getMainLooper())
//当然这里也可以跟上面一样,通过handleMessage()来处理。
// 在Activity中 还有个 runOnUiThread()方法。内部用的也是handler.post()
handler.post({
testBtn.visibility = View.VISIBLE
})
})
thread.start()
}
}
ps:第二种是真香,在Rxjava、LiveData等可用于异步通信的库中用得风生水起,就又比如Glide中异步加载图片之后通知主线程去显示。
另外上述代码仅仅是为举栗子方便,这种使用线程可得格外注意,一不小心又是个内存泄漏。
毕竟 Thread大括号内 是个实现了 Runable接口的匿名类!
而使用非UI线程创建Looper的具体使用可以参见HandlerThread
。
ok,骚年如果你只是来查Handler的api的使用,我想上述的描述已经满足了你的要求。
我又认真想了想,小白的话,看得懂kotlin代码吗?🤦♂️
呃...继续吧
三、HandlerThread 和IntentService
1、HandlerThread
HandlerThread是一个封装了Looper的Thread。代码量就一百来行,可以直接看源码上手。
HandlerThread : Thread
//覆盖run方法,创建Looper
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
当我们需要非UI线程来实现一个异步消息队列以及处理延时消息。
或者主线程中将一些耗时操作进行异步操作
那么HandlerThread是我们选择之一,使用也非常简单。
// 创建HandlerThread,参数为线程名。
val handlerThread = HandlerThread("HandlerThread_name")
handlerThread.start() // start之后,looper自动进入循环
//创建使用HandlerThread中Looper的Handler
val mHandler = object :Handler(thread.looper) {
override fun handleMessage(msg: Message?) {
msg?.let {
when(msg.what) {
0 -> {
Log.i(TAG, "handleMessage 0")
}
else -> {
}
}
}
}
}
//这句可以在任意线程调用,handleMessage会在HandlerThread中被执行
mHandler.sendEmptyMessage(0)
//当要退出线程时使用
handlerThread.quit() //立即退出(如果有消息正在执行,会先继续执行消息)
handlerThread.quitSafely() // 等消息队列消息全部执行完再退出,当如果消息执行速度小于插入消息的速度。。。那线程就一直执行下去咯
2、IntentService
HandlerThread虽然很简单,但现在的需求中应用很少,毕竟外面成熟的异步框架太强了。除非确实有需求在单条线程上起一个消息队列来执行很多的消息。
不过HandlerThread在Android源码中有一个典型的用法,那就是 IntentService
。是的,就是四大组件之一的Service。
IntentService可以说成是一种一次性的Service,用完即走的那种。
IntentService通过结合HandlerThread完成一些异步耗时操作,并在操作完成后,自动销毁Service。
IntentService是个抽象类,必须继承后实现
onHandleIntent(intent: Intent?)
方法,当通过context.startService(intent)
启动IntentService后,会调用onHandleIntent(intent: Intent?)
来做对应的操作。onHandleIntent(intent) 是在HandlerThread中被执行的,至于为什么要通过HandlerThread而不是普通的Thread?是因为假设startService(intent)多次,如果是普通线程,那么就会建立多条线程,而通过则HandlerThread可以自动往消息队列添加,并等待被执行,只需要一条线程。
IntentService中的Handler
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1); // 主动终止 Service
}
}
- 当消息队列中消息全部执行完之后,那么IntentService会自动退出。下一次的startService() 会重新创建一个IntentService。
以前我一直有个未知之谜,上述代码中onHandleIntent()之后就会立马执行执行stopSelf(msg.arg1),服务就应该停止了啊!毕竟已经mServiceLooper.quit()了. 为什么还会把消息队列执行完?
IntentService.java
@Override
public void onDestroy() {
mServiceLooper.quit();
}
网上文章翻了很多篇都没有提到,源码也看了很多遍,折腾一番才发现问题出在stopSelf(msg.arg1)的参数中msg.arg1,接下来就简单多了,当做课后作业,大家自己去源码翻一翻吧。
使用IntentService的简单使用可直接看下面这个例子(Android Studio 生成模板代码)
至于更为详细的IntentService源码分析,我就不分析了。。
class MyIntentService : IntentService("MyIntentService") {
// 需要实现
override fun onHandleIntent(intent: Intent?) {
when (intent?.action) {
ACTION_FOO -> {
val param1 = intent.getStringExtra(EXTRA_PARAM1)
val param2 = intent.getStringExtra(EXTRA_PARAM2)
handleActionFoo(param1, param2)
Thread.sleep(3000)
}
ACTION_BAZ -> {
val param1 = intent.getStringExtra(EXTRA_PARAM1)
val param2 = intent.getStringExtra(EXTRA_PARAM2)
handleActionBaz(param1, param2)
}
}
}
/**
* Handle action Foo in the provided background thread with the provided
* parameters.
*/
private fun handleActionFoo(param1: String, param2: String) {
Log.i(TAG, "handleActionFoo ")
}
/**
* Handle action Baz in the provided background thread with the provided
* parameters.
*/
private fun handleActionBaz(param1: String?, param2: String?) {
Log.i(TAG, "handleActionBaz ")
}
companion object {
private const val TAG = "MyIntentService"
/**
* Starts this service to perform action Foo with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
@JvmStatic
fun startActionFoo(context: Context, param1: String?, param2: String?) {
val intent = Intent(context, MyIntentService::class.java).apply {
action = ACTION_FOO
putExtra(EXTRA_PARAM1, param1)
putExtra(EXTRA_PARAM2, param2)
}
context.startService(intent)
}
/**
* Starts this service to perform action Baz with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
@JvmStatic
fun startActionBaz(context: Context, param1: String, param2: String) {
val intent = Intent(context, MyIntentService::class.java).apply {
action = ACTION_BAZ
putExtra(EXTRA_PARAM1, param1)
putExtra(EXTRA_PARAM2, param2)
}
context.startService(intent)
}
}
}