Handler是什么?
handler允许你发送和处理与线程的MessageQueue关联的消息和runnable(可运行对象)。每个handler实例都与一个线程和该线程的消息队列关联。当创建一个新的handler时,它会绑定到一个looper(循环器)。handler将消息和runnable传递到该循环器的消息队列,并在该循环器的线程上执行它们。
能把消息发送给MessageQueue,并负责处理Looper分给它的消息。
为什么要使用handler?
handler使用主要有两个原因:
-
安排延时定时消息和线程在未来某个时间执行。
-
将要在不同于自己的线程上执行的操作排队。
如何理解 Message、 MessageQueue、 Looper?
Message:消息对象,类似于链表的一个结点。
-
2个整型数值:轻量级存储int类型的数据。
-
1个Object:任意对象。
-
replyTo:线程通信时使用。
-
what:用户自定义的消息码,让接收者识别消息。
MessageQueue:消息队列,用于存放消息对象的数据结构。
-
采用先进先出的方式管理Message。(这里的先依据的不是谁先入队,而是消息待发送的时间)
-
每一个线程最多可以拥有一个。
Looper:消息队列的处理者(用于轮询消息队列的消息对象)
-
每个线程只有一个Looper。
-
Looper.prepare():为当前线程创建Looper对象。
-
Looper.myLooper():可以获得当前线程的Looper对象。
Handler如何实现的消息发送与处理?
-
post形式将可运行对象(runnable)排进消息队列;(一般用于单个场景比如单一的倒计时弹框功能)
post(Runnable),
postAtTime(java.lang.Runnable, long),
postDelayed(Runnable, Object, long)
对于延时、定时消息,可以通过removeCallbacks(Runnable r)、removeCallbacks(Runnable r, Object token)、removeCallbacksAndMessages(Object token)方法进行取消。 -
sendMessage形式将包含处理程序的数据内容排进消息队列(需要实现处理程序hanldeMessage);(Message做为参数多用于判断条件的场景)
sendEmptyMessage(int),
sendMessage(Message),
sendMessageAtTime(Message, long),
sendMessageDelayed(Message, long)
对于延时、定时消息,可以通过removeMessages(int what)、或removeMessages(int what, Object object)、removeCallbacksAndMessages(Object token)将指定消息移除。
无论是post还是sendMessage发送消息,最终都会调用MessageQueue的enqueueMessage插入一条信息到MessageQueue,Looper不断轮询调用MeaasgaQueue的next方法,如果发现message就调用handler的dispatchMessage,dispatchMessage被成功调用,接着执行message的Runnable callback 或handleMessage方法。
最后,列举一些关于Handler的常见问题
1.子线程中能不能直接new一个Handler,为什么主线程可以?
不能,因为Handler 的构造方法中,会通过Looper.myLooper()获取looper对象,如果为空,则抛出异常,主线程则因为已在入口处ActivityThread的main方法中通过 Looper.prepareMainLooper()获取到这个对象,并通过 Looper.loop()开启循环,在子线程中若要使用handler,可先通过Loop.prepare获取到looper对象,并使用Looper.loop()开启循环
public Handler(@Nullable Callback callback, boolean async) {
//获取thread上的消息循环器
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.Handler导致的内存泄露原因及其解决方案
原因:
1..Java中非静态内部类和匿名内部类都会隐式持有当前类的外部引用
2.我们在Activity中使用非静态内部类初始化了一个Handler,此Handler就会持有当前Activity的引用。
3.我们想要一个对象被回收,那么前提它不被任何其它对象持有引用,所以当我们Activity页面关闭之后,存在引用关系:"未被处理 / 正处理的消息 -> Handler实例 -> 外部类",如果在Handler消息队列还有未处理的消息 / 正在处理消息时导致Activity不会被回收,从而造成内存泄漏
解决方案:
1.将Handler的子类设置成静态内部类,使用WeakReference弱引用持有Activity实例
2.当外部类结束生命周期时,清空Handler内消息队列
3.Handler postDealy后消息队列有什么变化?进队延时更短的消息,消息队列怎样处理 队内消息?
postDelayed传入的时间,会和当前的时间SystemClock.uptimeMillis()做加和,而不是单纯的只是用延时时间。延时消息会和当前消息队列里的消息头的执行时间做对比,如果比头的时间靠前,则会做为新的消息头,不然则会从消息头开始向后遍历,找到合适的位置插入延时消息。 postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper; Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;直到阻塞时间到或者下一次有Message进队;
4.Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
MessageQueue#next 在没有消息的时候会阻塞,如何恢复?他不阻塞的原因是epoll机制,他是linux里面的,在native层会有一个读取端和一个写入端,当有消息发送过来的时候会去唤醒读取端,然后进行消息发送与处理,没消息的时候是处于休眠状态,所以他不会阻塞他。
5.一个线程可以有几个Handler,几个Looper,几个MessageQueue对象?
一个线程可以有多个Handler,只有一个Looper对象,只有一个MessageQueue对象。Looper.prepare()函数中知道,。在Looper的prepare方法中创建了Looper对象,并放入到ThreadLocal中,并通过ThreadLocal来获取looper的对象, ThreadLocal的内部维护了一个ThreadLocalMap类,ThreadLocalMap是以当前thread做为key的,因此可以得知,一个线程最多只能有一个Looper对象,在Looper的构造方法中创建了MessageQueue对象,并赋值给mQueue字段。因为Looper对象只有一个,那么Messagequeue对象肯定只有一个。