handler相关学习(三)handler必背

问题一、Android-Handler同步屏障,消息机制之同步屏障

Android-Handler同步屏障 - 简书

Handler设置同步屏障之后可以拦截Looper对同步消息的获取和分发。加入同步屏障之后,Looper只会获取和处理异步消息,如果没有异步消息会进入阻塞状态。

消息机制的同步屏障,其实就是阻碍同步消息,只让异步消息通过。而开启同步屏障的方法就是调用下面的方法:

MessageQueue#postSyncBarrier()

创建同步消息的源码如下:

@TestApi
public int postSyncBarrier() {
    // 这里传入的时间是从开机到现在的时间戳
    return postSyncBarrier(SystemClock.uptimeMillis());
}
/**
 * 这就是创建的同步屏障的方法
 * 同步屏障就是一个同步消息,只不过这个消息的target为null
 */
private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        // 从消息池中获取Message
        final Message msg = Message.obtain();
        msg.markInUse();
        // 初始化Message对象的时候,并没有给Message.target赋值,
        // 因此Message.target==null
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            // 这里的when是要加入的Message的时间
            // 这里遍历是找到Message要加入的位置
            while (p != null && p.when <= when) {
                // 如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步
                // 消息里有时间小于T,则prev也不为null
                prev = p;
                p = p.next;
            }
        }
        // 根据prev是否为null,将msg按照时间顺序插入到消息队列的合适位置
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

在这里可以看到,Message对象初始化的时候并没有给target赋值,因此target==null的来源就找得到了。这样就可以插入一条target==null的消息,这个消息就是一个同步屏障。
那么开启消息屏障后,所谓的异步消息又是如何处理的呢?
消息的最终处理其实都是在消息轮询器Looper#loop()中,而loop()循环中会调用MessageQueue#next()从消息队列中进行取消息。

在MessageQueue.java的next方法中:

while中首先判断这个msg是同步消息,!msg.isAsynchronous(),同步消息的话就执行下一个msg。

从上面的MessageQueue.next方法可以看出,当消息队列开启同步屏障的时候(即标识为msg.target==null),消息机制在处理消息的时候,会优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用

如果上图所示,在消息队列中有同步消息和异步消息(黄色部分)以及一道墙(同步屏障--红色部分)。有了同步屏障的存在,msg_2和msg_M这两个异步消息可以被优先处理,而后面的msg_3等同步消息则不会被处理。那么这些同步消息什么时候可以被处理呢?就需要先移除这个同步屏障,即调用MessageQueue#removeSyncBarrier()

如何发送异步消息
通常我们使用 Handler 发消息时,这些消息都是同步消息,如果我们想发送异步消息,那么在创建 Handler 时使用以下构造函数中的其中一种( async传true )

public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);

然后通过该Handler发送的所有消息都会变成异步消息

问题二、handler原理描述

每日一问:Android 消息机制,我有必要再讲一次! - 简书

梳理一下其实最简单的就下面四条:

1、每一个线程中最多只有一个Looper,通过ThreadLocal来保存,Looper中有Message队列,保存handler并且执行handler发送的message。

2、在线程中通过Looper.prepare()来创建Looper,并且通过ThreadLocal来保存Looper,每一个线程中只能调用一次Looper.prepare(),也就是说一个线程中最多只有一个Looper,这样可以保证线程中Looper的唯一性。

3、handler中执行sendMessage或者post操作,这些操作执行的线程是handler中Looper所在的线程,和handler在哪里创建没关系,和Handler中的Looper在那创建有关系。

4、一个线程中只能有一个Looper,但是一个Looper可以对应多个handler,在同一个Looper中的消息都在同一条线程中执行。
 

问题三、Looper会一直消耗系统资源吗?

Looper不会一直消耗系统资源,当LooperMessageQueue中没有消息时,或者定时消息没到执行时间时,当前持有Looper的线程就会进入阻塞状态。

问题四、android的Handle机制,Looper关系,主线程的Handler是怎么判断收到的消息是哪个Handler传来的?

Looper是如何判断Message是从哪个handler传来的呢?其实很简单,在1中分析过,handler在sendMessage的时候会构建一个Message对象,并且把自己放在Message的target里面,这样的话Looper就可以根据Message中的target来判断当前的消息是哪个handler传来的。


问题五、Handler是如何引起内存泄漏的?如何解决?

在子线程中,如果手动为其创建Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。

  Looper.myLooper().quit()

那么,如果在Handler的handleMessage方法中(或者是run方法)处理消息,如果这个是一个延时消息,会一直保存在主线程的消息队列里,并且会影响系统对Activity的回收,造成内存泄露。

解决Handler内存泄露主要2点

1 、有延时消息,要在Activity销毁的时候移除Messages

2、 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。

问题六、handler机制中如何确保Looper的唯一性?

Looper是保存在线程的ThreadLocal里面的,使用Handler的时候要调用Looper.prepare()来创建一个Looper并放在当前的线程的ThreadLocal里面。

      private static void prepare(boolean quitAllowed) {
          if (sThreadLocal.get() != null) {
              throw new RuntimeException("Only one Looper may be created per thread");
          }
          sThreadLocal.set(new Looper(quitAllowed));
      }

可以看到,如果多次调用prepare的时候就会报Only one Looper may be created per thread,所以这样就可以保证一个线程中只有唯一的一个Looper

问题七、Handler 是如何能够线程切换,发送Message的?

handler的执行跟创建handler的线程无关,跟创建looper的线程相关,如果在子线程中创建一个Handler,但是Handler相关的Looper是主线程的,这样,如果handler执行post一个runnable,或者sendMessage,最终的handle Message都是在主线程中执行的。
 

问题八、如何判断当前线程是安卓主线程?

使用Looper 判断

Looper.getMainLooper()
Looper.getMainLooper().getThread()

问题九、Handler 中使用了什么设计模式?

Message 使用了享元模式 ----减少对象的创建,对象可以反复使用

MessageQueue 生产者消费者

问题十、正确创建Message实例的方式?

  1. 通过 Message 的静态方法 Message.obtain() 获取;
  2. 通过 Handler 的公有方法 handler.obtainMessage()

所有的消息会被回收,放入sPool中,使用享元设计模式。** 享元模式主要用于减少对象创建的数量,以减少内存占用和提高性能**

问题十一、主线程中的Looper.loop()一直无限循环为什么不会造成ANR?

主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗

因为MessageQueue的阻塞机制,会让主线程会处于阻塞状态,这个过程涉及到MessageQueue 类中的两个方法: next() 和 enqueueMessage(Message, long)

(1) next() :从队列中获取并返回下一个消息. 如果队列为空(无返回值), 则该方法将调用 native void nativePollOnce(long, int), 此操作为阻塞操作程序就会便阻塞在loop的queue.next()中的nativePollOnce()方里,该方法将一直阻塞直到添加新消息为止。

(2)enqueueMessage():将 Message 添加到队列时, 会调用该方法, 该方法不仅将消息插入队列,,而且还会调用native static void nativeWake(long).

nativePollOnce 这里就涉及到Linux pipe/epoll机制,该系统调用可以监视文件描述符中的 IO 事件. handler机制就是使用pipe来实现的。当主线程的MessageQueue没有消息时,就会阻塞在loop的queue.next()中的nativePollOnce()方法里,因为nativePollOnce() 在某个文件描述符上调用 epoll_wait,会让主线程在没有消息处理时就会阻塞在管道的读端。此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,nativeWake()会通过binder线程往主线程消息队列里添加消息,就会通过往pipe管道写端写入一个字节数据来唤醒主线程从管道读端返回,也就是说queue.next()会调用返回。

用一句话概括:线程阻塞监听多个fd句柄,其中一个fd有写入操作,当前线程就被唤醒

看下7.0源码中native层实现的同步逻辑:

问题十二:handler消息阻塞和锁机制一样吗

 nativePollOnce 大致等同于 Object.wait(), nativeWake 等同于 Object.notify(),只不过它们的实现完全不同: nativePollOnce使用 epoll, 而 Object.wait 使用 futex Linux 调用.

这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以主线程的死循环并不是一直运行,在大多数时候都是处于休眠状态,并不会消耗大量CPU资源。因为nativePollOnce()只是表明所有消息的处理已完成,线程正在等待下一个消息。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值