Android技术

Handler机制和原理

2022-04-20  本文已影响0人  Qphine

一、作用

定义:消息传递过程中,收消息,与处理消息;

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.png

1、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

image.png

六、内存泄漏解决方案:

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操作的控制。

上一篇下一篇

猜你喜欢

热点阅读