Handler相关
-
Handler的作用
在Android中,为了保证UI操作是线程安全的,所以只允许在主线程中去更新UI,而在一些场景中,为了提高工作效率,会在工作线程中更新UI的相关信息,此时就需要一种机制将工作线程的信息传递到主线程去更新UI。存在一个消息队列 MessageQueue,它是一个基于消息触发时间的优先级队列,还有一个基于此消息队列的事件循环 Looper,Looper 通过循环,不断的从 MessageQueue 中取出待处理的 Message,再交由对应的事件处理器 Handler/callback 来处理。 -
Handler的相关概念
- Message:传递消息的载体
- MessageQueue:先进先出的存储消息的队列
- Handler:1. 用于主线程和子线程之前传递消息(Handler.post, Handler.sendMessage) 2.实际上处理消息的地方(Handler.handlerMessage)
- Looper:MessageQueue和Handler之间的媒介,循环从MessageQueue中取出消息,然后发送给对应的Handler
-
Looper和Handler创建和准备过程
- 在主线程创建时,会调用Looper.prepareMainLooper(),创建主线程的Looper和对应的MessageQueue(如果是在子线程,需要手动调用Looper.prepare(),创建对应的Looper和MessageQueue),并执行Looper.loop()开始消息循环
- Handler初始化时,会自动绑定Looper和MessageQueue
- 在Looper.loop的过程中,通过for循环,不断调用MessageQueue.next,获取消息队列中的消息
3.1 如果到了消息执行的时间,则通过Handler.dispatchMessage分发给对应的Handler,通过回调Handler.handlerMessage进行消息的处理
3.2 如果没有到消息执行的时间,则通过Message.nativePollOnce进入线程阻塞,等待执行的时间 - 在使用Message时,可以通过
new Message()
或者Message.obtain()
获得Message对象。Message.obtain()
会去尝试复用Message对象,而new Message()
则是直接创建一个Message对象 - 通过
Handler.sendMessage
或者Handler.sendMessageDelayed
将Message发送到MessageQueue中。如果线程处于阻塞状态,则唤醒线程。
5.1 如果此时队列为空则将消息放到队列头部
5.2 如果队列有消息,则根据执行时间或者创建时间,插入到队列中。
-
sendMessage
和post
的区别
sendMessage
的参数是Message
,然后直接调用sendMessageDelayed(msg, 0)
post
的参数是Runnable
,通过getPostMessage
将Runnable
赋值给Message.callback
,然后返回一个Message
对象。最后还是调用sendMessageDelayed(getPostMessage(r), 0)
在Handler.dispatchMessage
中通过判断callback
是否为null,决定执行handleCallback
还是handleMessage
所以,实际上sendMessage
和post
本质上没有区别,只是在使用方法上有区别。 -
sendMessageDelayed的原理
(参考)
Message内部维护一个when
,用于记录Message的创建时间,当一个新的Message插入MessageQueue中,会根据when
进行排序。- 当使用
sendMessageDelayed
发送的Message,会在当前的系统时间上加设置的delayMillis
设置为该Message的when
。 - MessageQueue在通过
next()
获取消息时,会对比当前的时间now
和Message的when
,如果now > when
则取出消息交给Looper,如果now < when
,则通过nativePollOnce
阻塞线程,直到执行时间到了,或者被新的Message唤醒
注:所以通过Handler延时处理的任务,并不一定会准时执行,因为有可能会因为前一条消息执行时间太长,而出现延迟。
- 当使用
-
Handler内部有一个死循环,但是为什么不会ANR
因为ANR指的是在一定的时间内,系统没有响应输入事件,或者是在组件规定的时间内,没有执行对应的生命周期,而出现的异常。而Handler的死循环并不需要响应事件,所以不存在ANR。 -
Looper会一直消耗系统资源吗?(参考)
Looper不会一直消耗系统资源,当Looper的MessageQueue中没有消息时,或者定时消息没到执行时间时,当前持有Looper的线程通过nativePollOnce
进入阻塞状态。直到有新消息到,或者时间到了,才会被唤醒。 -
Handler的内存泄漏
因为在Java中,非静态内部类 & 匿名内部类都默认持有 外部类的引用,所以,如果在Activity中创建一个Handler,所以该Handler会持有Activity的实例。
当Handler中有未处理完的Message时,关闭Activity,此时就会因为Handler持有Activity,而Activity无法被GC,导致内存泄漏
解决方案:通过将Handler设置为静态内部类,并且通过弱引用创建Handler实例 -
Handler如何在多线程环境下保证线程安全?(参考)
Handler.sendMessage
最后调用到了MessageQueue.enqueueMessage
,在这里通过synchronized(this)给自己加了锁,保证了在多线程环境下消息的插入是线程安全的。同时在MessageQueue.next
中,也加了锁,保证取出消息是线程安全的。 -
为什么MessageQueue队列没有设置上限?
为了避免队列满了,导致死锁。因为出队和入队的时候持有的是同一把锁,如果入队时先持有锁,但是队列满了,无法入队,导致无法释放锁,而出队又拿不到锁,进行处理。 -
同步消息屏障(参考)
- 一般情况下,通过Handler发送的Message放到MessageQueue中,这些消息都是同步消息,都会在MessageQueue中排队等候处理。
- 而在Handler中,还有另外一种异步消息。这种异步消息的应用场景主要是为了避免在消息队列中,消息太多了处理不完,而使用的一种“加急”处理紧急消息的机制。
- 而要使用异步消息,就需要使用到同步消息屏障。这是一种特殊的Message,当它被插入队列之后,当队列头遇到它之后,在
MessageQueue.next()
方法中,会通过while
循环,取出队列中的异步消息,进行处理。// msg.target == null,就是同步消息屏障 if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); }
- 使用场景:在Android屏幕绘制的过程中,为了能及时的处理绘制的消息(TraversalRunnable ),就会使用同步屏障,将MessageQueue中的消息暂时阻塞,优先处理绘制相关的异步消息,当异步消息处理完成之后,还会移除同步屏障,接着处理MessageQueue中的同步消息。
- 同步消息屏障的原理
了解过同步消息屏障的人,都知道一句话
如果没有及时移除同步屏障,他会一直存在且不会执行同步消息。因此使用完成之后必须即时移除。
因为在MessageQueue.next()
处理消息的时候,会执行:
从这里可以看到,如果if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; return msg;
prevMsg != null
,就会将prevMsg.next
指向msg.next
,即将msg从链表中取出来。因为我们知道当有同步屏障存在的时候,prevMsg
肯定不为null
,所以此时mMessage不会改变,还依旧是同步消息屏障,所以等到下一个next()
循环,又会重新从队列里面找到异步消息,如果没有找到就会进入线程阻塞状态。只有将同步消息屏障移除之后,才会恢复。代码如下:public void removeSyncBarrier(int token) { synchronized (this) { Message prev = null; Message p = mMessages; // 在链表中找到同步屏障 while (p != null && (p.target != null || p.arg1 != token)) { prev = p; p = p.next; } final boolean needWake; // 如果同步屏障在链表中,就直接移除 if (prev != null) { prev.next = p.next; needWake = false; } else { // 这里同步屏障是链表头部,则直接将mMessages赋值为链表下一个节点 mMessages = p.next; needWake = mMessages == null || mMessages.target != null; } } }
参考阅读:https://blog.csdn.net/carson_ho/article/details/51290360
-
IdleHandler 是什么?怎么用?(参考)
IdleHandler是在Looper事件循环的过程中,当出现空闲的时候,可以去执行一些不耗时的任务。
IdleHandler.queueIdle()
的返回值表示,如果返回true
则表示该任务会一直重复,false
表示该任务只执行一次。
使用场景:GC操作 -
当 mIdleHandlers一直不为空时,为什么不会进入死循环?
1. mIdleHandlers执行依赖的变量是pendingIdleHandlerCount
,当pendingIdleHandlerCount < 0
时,才会获取mIdleHandlers中的数量。
1.1. 如果pendingIdleHandlerCount <= 0
,就会continue
重新循环,并且在此次的for循环中,pendingIdleHandlerCount
不会再< 0
,所以mIdleHandlers没有机会再执行,只能等待下一次'next()'的调用
1.2. 如果pendingIdleHandlerCount > 0
,则会执行for (int i = 0; i < pendingIdleHandlerCount; i++) {}
,意味着此次mIdleHanders中的任务也只会被遍历一次。之后pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0;
都置为0了,会唤醒线程,判断Message的执行时间是否到了。就算此次Message执行时间还未到,那么参考1.1,mIdleHandlers的任务还是不会再次执行。 -
ThreadLocal有什么作用?(参考)
ThreadLocal类可实现线程本地存储的功能,把共享数据的可见范围限制在同一个线程之内,无须同步就能保证线程之间不出现数据争用的问题,这里可理解为ThreadLocal帮助Handler找到本线程的Looper。
每个线程的Thread对象中都有一个ThreadLocalMap对象,它存储了一组以ThreadLocal.threadLocalHashCode为key、以本地线程变量为value的键值对,而ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,也就包含了一个独一无二的threadLocalHashCode值,通过这个值就可以在线程键值值对中找回对应的本地线程变量。 -
如何判断当前线程是主线程:
return Looper.getMainLooper() == Looper.myLooper();
因为myLooper返回的是用ThreadLocal存储的Looper,ThreadLocal对每个线程都单独保留一份 -
ThreadLocal:以当前线程对象(thread)为key,value为要保存的对象。这样即可保持线程之间的独立性。