Handler 40问

1,handler 机制中,存在哪些角色?各自承担了什么功能?

1.Handler :消息辅助类& 对外的接口& 向MQ投递消息&消息的目标处理者
2.Message :消息的载体&被Handler投递&自带Handler处理&自带处理池
3.Looper :循环器&持有MQ&循环从MQ中获取消息&TLS线程唯一
4.MessageQueue:基于时间的优先级队列&链表结构&java与C++层的纽带
[图片上传失败...(image-25c28e-1616424380917)]

2.Handler机制中,Message和Runnable的区别?

1.本质上没有区别的,MQ只接受Message,Runnable 会被转换成Message入队

2.Runnable 通过getPostMessage()方法转换成Message对象

3.Runnable 转换的Message,Runnable 会被记录在MSG的callback属性上,在处理消息时,优先被处理

3.Handler分发事件优先级,是否可以拦截?拦截的优先级如何?

1.Handler中,通过dispatchMessage()处理消息,其中存在优先级策略

2.优先级1:msg.callback ,run -独占

3.优先级2:mCallback.handlerMessage(msg) -返回值决定是非法拦截该消息

mCallback 属于 Handler,是消息的统⼀拦截机制。

3.优先级3:handle.handleMessage()


image

4.Handler 在处理 Message 的时候,可以对消息统⼀拦截吗?如何做?有什么缺点?

1.可以

2.实现:使用Handler的Callback;通过Handler的构造方法传递Callback对象,并实现其handlerMessage()方法用于Message的统一处理。返回值表示是否拦截。


image

3.缺点:无法拦截Runnable消息

5.Handler 发送延迟消息,涉及哪些⽅法?原理是什么?会被开机时间影响吗?

1.涉及方法:sendMessageDelayed()和sendMessageAtTime(),最终都会调⽤到 enqueueMessage() ⼊队;


image

image
  1. 原理:延迟时间记录在 msg.when 中,参与 MessageQueue 的优先级排序,即 MQ.enqueueMessage() 插⼊消息时,会基于 when 计算消息在 MessageQueue 中的位置并插⼊;


    image

    image

    3.不受开机时间影响,因其延迟消息基于 SystemClock,此为设备开机时间,不受时钟影响 。
    1.prepare不同
    主线程:prepareMainLooper()
    子线程:prepare()
    2.是否允许退出
    主线程:不允许
    子线程:允许
    3.构造方法不同
    主线程:有ActivityThread启动时,main()方法中构造的,开发者无需关系
    子线程:开发者执行构造,prepare() --->loop()

new Thread(){
    public void run() {
        Looper.prepare();
        Toast.makeText(MainActivity.this, "Hello", 0).show();
        Looper.loop();
    };  
}.start();

7.Looper 如何保证线程唯⼀?线程内多次 prepare() 不同的 Looper 会怎样?

1.原理:基于 TLS 机制,Looper ⾸次 prepare() 时,会将 Looper 存⼊ ThreadLocal (map)中,再
次 prepare() 时检查 ThreadLocal 是否已存储;


image

2.多次调⽤ Looper prepare() 会抛出异常;

8.Looper 如何判断是否允许退出?如何设置 Looper 是否允许退出?主线程 Looper 允许退出吗?

1.判断:Looper持有的MessageQueue的mQuitAllow状态,标识了是否允许退出

image

2.设置:通过 Looper.prepare(quitAllowed) 的⼊参 quitAllowed 判断是否允许退出;
此⽅法为私有,外部⽆法直接调⽤;
3.规律:主线程 Looper 不允许退出,⼦线程 Looper 允许退出;

  1. 主线程:prepareMainLooper() → prepare(false) - 不允许退出;


    image

    2.其他线程:prepare() → prepare(true) - 允许退出;

9.Looper 的退出,涉及哪些⽅法,原理是什么?有什么限制?

1.Api:直接退出quit() & 安全退出quitSafey()


image

2.原理:都会调⽤ MessageQueue 的 quit() ⽅法,清理 MessageQueue 持有的消息,并标 记 MQ 退出,驱动其 next() ⽅法返回 null,初始 Looper 退出 loop() 循环,进⽽使得 Looper 退出;


image

image

3.限制:只有⼦线程 Looper 才允许退出,主线程 Looper 尝试退出会抛出异常;

4.Tips:MQ 退出 → () 返回 null → Looper.loop() 收到 null → 跳出while 循环 → Looper 退出;

10.Looper.loop() ⽅法中的循环内,调⽤ MessageQueue 的 next(),若 next() 返回 null 则直接 退出循环,合理吗?

1.合理
2.正常情况,MQ,next() 必然会返回待处理的消息,没有则会通过 nativePollOnce() 休眠


image

image

3.若 next() 返回 null,则说明出现异常
mPtr = 0,消息队列已被清理并退出;
mQuitting = true,消息队列已经被标记为退出;

11.如何构造子线程Looper?涉及哪些方法?

  1. 启动新线程;
  2. 调⽤ Looper.prepare() - 准备;
    3.调⽤ Looper.loop() - 进⼊循环;
new Thread(){
   public void run() {
       Looper.prepare();
       Toast.makeText(MainActivity.this, "Hello", 0).show();
       Looper.loop();
   };  
}.start();

12.主线程 Looper 何时运⾏?

1.App启动时,会调用到ActivityThread周,Looper就在其main()方法中被启动


image

2.main()中会调用Looper,prepareMainLooper()和Looper.loop()


image

3.Tips:ActivityThread 不继承⾃ Thread,它只是⼀个运⾏在主线程上的对象;
image

13.Message 消息池的结构?最⼤缓存多少个 Message 对象?

1.Message类的静态属性sPool维护
2.消息池基于链表结构,以msg.next串联
3.sPoolSize 维护消息池⼤⼩,最⼤ 50;


image

14.Message 消息池,有什么优点?

1.享元模式,避免重复构造Message
2.回收资源,回收时清理msg持有的callback和target,避免内存泄漏

15.Handler 的 Message 可以分为那 3 类?分别有什么标识?

1.同步Message:普通Message
2.异步Message:msg.setAsynchronous(true)


image

3.同步屏障:msg.target == null


image

16.同⼀个 Message 对象能否重复 send?

1.关键在于如何定义同一个Message
2.角度一:Java对象层面,可被复用
原因:Message由消息池维护,即同一个对象被回收后会被再次复用

new Message & Message.obtain() 

3.角度二:业务层面,不能复用;
原因:Message通过enqueueMessage()入队时,会通过markInUse()标记,再次入队无法通过isInUse()检查,则会抛出异常

image

17.场景:MessageQueue 是基于触发时间 when 的优先级队列,那么什么情况下,队列中靠后 的消息会优先得到执⾏?原理是什么?

1.场景:靠前的消息是同步消息,靠后的消息是异步消息,且消息队列的队头为同步屏障

  1. 原理:同步屏障会阻塞 MQ 中的同步消息,优先处理异步消息;

18.Message 的同步屏障有什么⽤?有什么意义?如何发送⼀个同步屏障?

1.用途:阻塞MQ对同步Message的分发,target == null ,无法通过 Handler ⼊队出队,需直接操作 MQ
2.意义:允许异步消息优先于同步消息执⾏;
3.同步屏障:特殊的 Message,target == null,⽆法通过 Handler ⼊队出队,需直接操作 MQ;
⼊队:postSyncBarrier():返回⼀个屏障 token;
出队:removeSyncBarrier()


image

image

image

19.什么是异步消息?如何发送?

1.意义:需配合同步屏障使⽤,否者与同步消息⽆区别

  1. 异步消息:setAsynchronous(true) → 向 flags 添加 FLAG_ASYNCHRONOUS 标记


    image

    image

    3.发送⽅式
    通过异步 Handler 发送 → 构造 Handler 时,async 传递 true
    发送消息前,主动调⽤ setAsynchronous(true)
    ---安全起⻅,Android 9.0 普通开发者⽆法使⽤异步消息,所有发送⽅式被标记为 @hide

20.同步屏障如何配合异步消息⼯作?

1.当 mMessage 是⼀个同步屏障时,会跳过所有同步消息,找到异步消息提前执⾏
2.若 MQ 队列中没有异步消息,会进⼊ nativePollOnce(mPtr, -1) ⽆限等待,直到同步屏障
被移除,或新的异步消息⼊队,才会通过 nativeWake() 唤醒 MQ;


image

21.Handler 的 IdleHandler 机制,如何理解?有什么⽤途?

1.接⼝,需实现 queueIdle() ⽅法 & 定义在 MQ 中 & 以 MQ mIdleHandlers 维护存储


image

2.⽤途:可在 MQ 即将空闲时,处理任务


image

3.逻辑点:MQ.next() 中,当前⽆待执⾏消息时,执⾏ mIdleHandlers;如上图
4.依据 queueIdle() 返回值分:持续回调(true) & ⼀次性回调(false),false 会导致执⾏ 完后,从 mIdleHandlers 中移除 。如上图

22.IdleHandler 的 queueIdle() 返回 true,为什么不会死循环?

  1. pendingIdleHandlerCount 标记控制,MQ.next() 时,初始为 -1;


    image

    2.-1 才会执⾏ mIdleHandlers,执⾏后置为 0 → 不会重复执⾏


    image

    3.每次 Looper.loop() 从 MQ.next() 取出消息后,在 Loop 循环中继续调⽤ MQ.next() 才会重 置pendingIdleHandlerCount,才会继续执⾏

23.IdleHandler 执⾏耗时会影响正常的消息分发吗?Handler 内部如何处理?

1.会
2.IdleHandler的耗时不可控
3.执⾏完后会重置 nextPollTimeoutMillis = 0,重新分发最近消息

24.Handler 在 Activity 中使⽤,什么场景下会出现内存泄露?原因是什么?如何规避?

1.现场:延迟消息 + 内部类 Handler;导致Handler 持有外部类的对象
2.主线程⽣命周期⻓于四⼤组件,msg,target 指向 Handler,⽽ Handler 作为内部类 持有外部类 Activity 的引⽤,导致 Activity 泄露


image

  1. 1, 静态 Handler + Activity 弱引⽤;
    1. 随 Activity ⽣命周期,onDestory() 会 remove 掉所有的消息

25.移除消息的 removeMessage() 为什么需要两次循环?

1.优化效率

  1. while-1:移除消息 & 找到下⼀个待处理的消息,存⼊ mMessages 中;
  2. while-2:从 mMessages 开始,移除后续符合条件的消息


    image

25.如何理解 HandlerThread?

1.继承 Thread,内部持有 Handler,并⾃维护⼦线程的 Looper
2.意义:将 Thread、Handler、Looper 封装,便于开发者使⽤


image

26.如何实现,⼦线程等待主线程处理消息结束后,再继续执⾏?原理是什么?

1.借助 Handler 的 runWithScissors()
2.原理:内部通过 BlockingRunnbale 包装,通过 synchronized + wait 进⼊等待,在 r 执⾏
完成后,调⽤ notifyAll() 唤醒等待队列,⼦线程收到后继续执⾏


image

image

image

27.Handler 的 runWithScissors() 可实现 A 线程阻塞等待 B 线程处理完消息后再继续执⾏的功 能,它为什么被标记为 hide?存在什么问题?原因是什么?

1.实现:将 Runnable 包装为 BlockingRunnable,其内通过 synchronized + wait 进⼊等
待,待 r 执⾏完后,调⽤ notifyAll() 唤醒等待队列的⼦线程,⼦线程继续执⾏(如上图)

2.问题:在⼦线程 Looper 中使⽤,可能导致 A 线程进⼊ wait 等待,⽽永远得不到被 notify唤醒

3.原因:⼦线程 Looper 允许退出,若包装的 BlockingRunnable 被执⾏前,MessageQueue
退出,则该 runnable 永远不会被执⾏,则会导致 A 线程⼀直处于 wait 等待,永远不会被notify 唤醒

28.⼦线程如何向主线程的 Handler 发送消息?为什么经过 Handler 就可以达到切线程的⽬的?

1.在 Android 中,主线程主要承担更新 UI 的⼯作,耗时操作⽆法在主线程完成

2.⼯作线程可以通过向主线程的 Handler 发⽣消息,来达到与主线程通信的⽬的

3.主线程与⼯作线程之间,是共享内存地址空间的,所以是可以互相操作的,但是需要注意处理线程同步的问题

4.⼯作线程通过主线的 Handler,向其成员变量 MessageQueue 中添加 Message。同时主
线程的 Looper ⼀直处于 loop() 状态,当检测到有新 Message 时,会将其取出,并通过
dispatchHandler() 分发处理消息


image

29.Looper.loop 中,如果没有待处理的消息,为什么不会阻塞 UI?

1.主线程在 MessageQueue 没有消息时,会阻塞在 loop 的 queue.next() ⽅法中的 nativePollOnce()⽅法⾥
2.此时主线程会释放 CPU 资源进⼊休眠状态,直到下⼀个消息到达或者有事务发⽣,通过

往 pipe 管道写端写⼊数据的⽅式,来唤醒主线程。这⾥采⽤的是 epoll 机制

3.epoll 机制是⼀种 IO 多路复⽤机制,可以同时监控多个描述符,在有事件发⽣的时候,⽴

即通知相应程序进⾏读或写操作,类似⼀种 callback 的回调机制。

4.主线程在⼤多数时候是处于休眠状态,并不会消耗⼤量的 CPU 资源。当有新的消息或事

务到达时,会⽴即唤醒主线程进⾏处理,所以对⽤户来说是⽆感知的

30.Handle MQ ⽆消息时,为什么不出现 ANR?

1.ANR 机制有独特的覆盖场景,通常原因为处理消息不及时

2.MQ ⽆消息时,会进⼊ nativePollOnce() 休眠,此时⽆消息,处于休眠状态

3.MQ 有消息时,会⽴即通过 nativeWake() 唤醒去处理消息

31.如果 Java 层 MQ 中消息很少,但是响应时间却很⻓,是什么原因?

1.MQ队列中,该Message前的Message处理较为耗时

2.Native 层消息过多,Java层MQ消息优先级最低,最后处理

32.在完整的 Handler (Java & C++)架构下,消息处理的优先级?

1.Native Message

2.Native Request

3.Java Message
Natitv的优先级最高,Java的最低

33.Native 层的 MQ,提供了哪些 JNI ⽅法?各有什么⽤途?

nativeInit():初始化
1.创建了 NativeMessageQueue 对象,增加其引⽤计数,并将 NativeMessageQueue
指针 mPtr 保存在 Java 层的 MessageQueue。

image

2.创建 Native Looper 对象(如上图)
3.调⽤ epoll 的 epoll_create()/ epoll_ctl() 来完成对 mWakeEventFd 和 mRequests 的可 读事件监听。
image

image

nativeDestory():回收资源
1. 调⽤ RefBase::decStrong() 来减少对象的引⽤计数
2.当引⽤计数为 0 时,则删除 NativeMessageQueue 对象。
nativePollOnce():利⽤ epoll 机制休眠,等待被唤醒
调⽤ Looper::pollOnce() 来完成,空闲时停留在 epoll_wait() ⽅法,⽤于等待事件发⽣或 超时
image

//1、等待事件发生或者超时(timeoutMillis),如果有事件发生,就从管道中读取事件放入事件集合(eventItems)返回,如果没有事件发生,进入休眠等待,如果timeoutMillis时间后还没有被唤醒,就会返回
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
nativeWake():向管道 mWakeEventFd 写⼊字符 1,唤醒 epoll_wait() 等待
image

image

34.如何理解 Looper 的 Painter?

  1. 在 Looper.loop() 循环消息的时候,如果 mLogging 不为 null,都会在关键点通过 Printer 输出 Log。例如 Message 开始执⾏和执⾏完毕


    image

    2.Printer 对象在 Looper 中默认为 null,可以通过 setMessageLogging() ⽅法从外部设置


    image

    3.性能检测⼯具 BlockCanary 就是利⽤ Printer 来检测主线程卡顿的问题。通过处理

Message 两次 Log 的时间差值,来判断是否存在卡顿

35.Looper 的 Printer 输出的⽇志,有什么其他⽤途?依靠的原理是什么?有什么缺点?

1.⽤途:性能监控;

2.原理:通过筛选⽇志内存,区分 Message 的开始执⾏和结束执⾏的时间点,即可判断处 理 Message 的耗时,即主线程卡顿耗时;

3.缺点:Printer 存在⼤量字符串拼接,在消息量⼤时,会导致性能受损;
实测数据:存在 Printer 时,在列表快速滑动时,平均帧率降低 5 帧;

36.如何理解 epoll?它有什么优势(Android M 开始,将 Pipe 换成了 eventFd)?

1.Linux 的 I/O 多路复⽤机制,可以同时监控多个描述符;

2.多路复⽤机制下,可以通知内核挂起进程/线程,当 1 个 或多个 IO 事件发⽣后,内核再唤 醒进程,将控制权返还,由应⽤程序⾃⼰处理事件;
3.epoll 优势:
(1)监控描述符数量不受限;
监控 fd 数量何内存相关,3G 内存 → 20w~30w
(2)IO 不会随监控 fd 的增多⽽放⼤ → 得益于事件表维护 fd 与其回调的关系;
4.epoll 适⽤:

1.适⽤⼤量空闲连接的场景 

2.没有⼤量空闲或死亡连接时,epoll 的效率不会⽐ select/poll ⾼ → 量⼩时 O(logn) 和 

O(n) 差别不⼤

5.操作

1.epoll_create - 创建 epoll 句柄; 

2.epoll_ctl() - 设置监听的 fd; 

3.epoll_wait() - 等待 fd 事件;

37.Handler 和管道(Pipe)的关系(Android M 开始,将 Pipe 换成了 eventFd)?

1.Handler 底层的休眠机制,就是利⽤管道 和 epoll 的 I/O 多路复⽤

2.线程进⼊休眠,通过 nativePollOnce() 底层调⽤ epoll_wait() 进⼊休眠

3.A 线程准备好 Message 后,放⼊消息池,向管道写⼊数据 “1”,管道有数据后,会唤醒⽬

标线程去处理消息

4.Tips:Android M 开始,将 Pipe 换成了 eventFd;


image

38.Handler 底层为什么使⽤管道,⽽不是 Binder?

1.相对于同进程内的线程通信,Binder 太重了,浪费 CPU 和内存资源(Binder 消耗CPU大)

2.内存⻆度:Binder 通信涉及⼀次内存拷⻉,⽽多个线程操作的 Handler 是在共享的内存

中,⽆需拷⻉,只需要通知线程有数据了

3.CPU ⻆度:Binder 底层驱动会维护线程池,⽐较浪费 CPU 资源

39.Handler 可以 IPC 通信(进程间的通信)吗?

1.不能

2.Handler 只能⽤于共享内存地址的 2 个线程通信,即同进程的 2 个线程通信;

40.Handler 为什么需要使⽤底层的 epoll 来休眠?

1.需要兼顾 Native 层的消息,消息可能来⾃底层硬件(分层方式当中,Native层就是本地框架)

2.如果只考虑 Java 层,notify/wait 即可实现

41.如何理解 nativePollOnce() ⽅法?

1.最终会调⽤到 Native 层的 pollInner() ⽅法。

2.在 pollInner() 中,处理流程:

a. 先调⽤ epoll_wait(),这是阻塞⽅法,⽤户等待事件发⽣或超时。 

b. 对于 epoll_wait() 返回,当且仅当⼀下三种情况出现时,才会返回。 

    POLL_ERROR,发⽣错误,直接跳转到 Done。 

    POLL_TIMEOUT,发⽣超时,直接跳转到 Done。 

    检测到管道有事件发⽣,则根据情况做响应的处理 

        如果是管道有事件发⽣,则直接读取管道的数据。 

        如果是其他事件,则处理 request,⽣成对应的 response 对象,push 到 

        response 数组。 

c. 进⼊ Done 标记为的代码段。

  先处理 Native 的 Message,调⽤ Native 的 Handler 来处理 Message。 

  再处理 Response 数组,POLL_CALLBACK 类型的事件。 

d. 返回后,Java 层继续处理


image

你可能感兴趣的:(Handler 40问)