Handler机制和原理
一、作用
定义:消息传递过程中,收消息,与处理消息;
1、可以让对应的message和runnable在未来的某个时间进行相应处理;
2、让耗时操作放在子线程,让更新UI操作放在主线程;
3、队列就是依次执行,Handler会处理完一个消息或者执行完某个处理在进行下一步,不会出现多个线程同时要求进行UI处理而引发的混乱现象;
二、handler 中 sendMessage 和post 区别
sendMessage:
在工作线程中处理完耗时操作后调用handler的sendMessage(message)把message对象发送给主线程,在主线程中重写handlerMessage()方法,判断接收到的消息进行更新UI的操作;
Post:
post方法传递的是一个runnable对象,更新UI的操作也是在这个runnable的run方法中进行的,也就是说run方法中的代码是执行在主线程中的,虽然它是写在工作线程中,主线程在接收到消息后自动执行runnable的run方法中的代码。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
由上源码可见,handler.post和handler.sendMessage本质上是没有区别的,都是发送一个消息到消息队列中,只不过post使用方式更简单。
三、sendMessage调用链
image.png四、流程
image.png1、Handler将Message发送到Looper的消息队列中(MessageQueue)
2、等待Looper的循环读取Message,处理Message,然后调用Message的target,即附属的Handler的dispatchMessage()方法,
3、将该消息回调到handleMessage()方法中,然后完成更新UI操作。
五、Handler引起的内存泄露及解决办法
泄漏的原因:
(普通的说法):handler发送的消息在当前handler的消息队列中,如果此时activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类(非静态内部类),
内部类天然持有外部类的实例引用,这样在GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,因而就不会去回收这个Activity,进而导致activity泄露。
持有链说法:
ActivityThred持有Looper,
Looper持有MessageQueue=>>Message=>>Handler=>>Activity
六、内存泄漏解决方案:
1、把handler设置成静态内部类,因为静态内部类不持有外部类的引用,所以使用静态的handler不会导致activity的泄露
2、onDestroy生命周期中调用 handler.removeCallbacks();,进行释放
3、handler内部类持有外部activity的弱引用
七、MessageQueue是什么数据结构
MessageQueue内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表;
和传统的队列有点不一样,主要区别在于Android的这个队列中的消息是按照时间先后顺序来存储的,时间较早的消息,越靠近队头;
八、什么是消息同步屏障
同步屏障只在Looper死循环获取待处理消息时才会起作用,也就是说同步屏障在MessageQueue.next函数中发挥着作用。
在next()方法中,有一个屏障的概念(message.target ==null为屏障消息), 遇到target为null的Message,说明是同步屏障,循环遍历找出一条异步消息,然后处理。 在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞 当出现屏障的时候,会滤过同步消息,而是直接获取其中的异步消息并返回, 就是这样来实现「异步消息优先执行」的功能 ;
怎么用
1、Handler构造方法中传入async参数,设置为true,使用此Handler添加的Message都是异步的;
2、创建Message对象时,直接调用setAsynchronous(true) 3.removeSyncBarrier() 移除同步屏障:
应用
在 View 更新时,draw、requestLayout、invalidate 等很多地方都调用了ViewRootImpl#scheduleTraversals(Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障
九、Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
不阻塞的原因是epoll机制,他是linux里面的,在native层会有一个读取端和一个写入端,当有消息发送过来的时候会去唤醒读取端,然后进行消息发送与处理,没消息的时候是处于休眠状态,所以不回卡死
十、子线程能不能更新UI
可以
刷新UI, 都会调用到ViewRootImpl.Android每次刷新UI的时候,最终根布局ViewRootImpl.checkThread()来检验线程是否是View的创建线程。 ViewRootImpl创建的第一个地方,从Acitivity声明周期handleResumeActivity会被优先调用到,也就是说在OnResume后ViewRootImpl就被创建,这个时候无法在在子线程中访问UI了,上面子线程延迟了一会,handleResumeActivity已经被调用了,所以发生了崩溃 不延迟在creae里直接设置不会崩溃 线程更新UI也行,但是只能更新自己创建的View
1、子线程可以在ViewRootImpl还没有被创建之前更新UI;
2、访问UI是没有加对象锁的,在子线程环境下更新UI,会造成不可预期的风险;
3、开发者更新UI一定要在主线程进行操作;
同问:为什么Android系统不建议子线程访问UI?
在android中子线程可以有好多个,但是如果每个线程都可以对ui进行访问,我们的界面可能就会变得混乱不堪,这样多个线程操作同一资源就会造成线程安全问题。
需要解决线程安全问题的时候,我们第一想到的可能就是加锁,但是加锁会降低运行效率,所以android出于性能的考虑,并没有使用加锁来进行ui操作的控制。