首页 > 其他分享 >Android Handler 知识点

Android Handler 知识点

时间:2023-04-17 09:34:40浏览次数:57  
标签:知识点 队列 Handler 线程 Looper Android Message 消息

Android面试必问的 Handler 知识点

于 2020-12-03 12:03:10 发布547 收藏 9   版权

前言

  • 在 Android 中,Handler 是贯穿于整个应用的消息机制,在面试中出现的概率为:100%
  • 在这篇文章里,我将带你梳理 Handler 的使用攻略 & 设计原理。追求简单易懂又不失深度,如果能帮上忙,请务必点赞加关注!

延伸文章


目录

1. 概述

在 Android 中,很多地方是通过消息机制驱动的,例如线程间通信、四大组件的启动等等。消息机制中主要涉及 的类有:Handler & Looper & MessageQueue & Message,其中 Handler 可以说是消息机制提供给 Java 层的上层接口。

1.1 概念模型

问:Handler 怎么进行线程通信,原理是什么?

消息机制其实并不是 Android 系统独有的设计,在很多系统设计中都可以看到消息机制的身影,例如 IOS 的 runLoop、Web 的 Ajax 和 Spring 的消息队列等。在所有系统设计的消息机制里,都会有生产者与消费者的概念,如以下模型:

消息机制概念模型

其中消息缓冲区的具体实现可以是栈 & 队列,因为队列(特别是优先级队列)是最常见的,所以很多情况都会直接将消息缓冲区称为消息队列。

1.2 架构图

【类图】

  • Looper 里组合了 MessageQueue 消息队列,创建 Looper 的同时也创建了 MessageQueue
  • MessageQueue 里组合了待处理的 Message 链表
  • Message 持有用于处理消息的 Handler(target)
  • Handler 被创建时需要聚合 Looper 与 MessageQueue,默认使用的是当前线程的 Looper

1.3 消息的享元模式

消息机制里需要频繁创建消息对象(Message),因此消息对象需要使用享元模式来缓存,以避免重复分配 & 回收内存。具体来说,Message 使用的是有容量限制的、无头节点的单链表的对象池:

Message 的数据结构


2. Handler 核心源码分析

这一节我们来分析 Handler 的核心源码:

2.1 启动消息循环

  • 问:Looper 如何在子线程中创建?(字节、小米)

要在哪个线程启动消息循环,就需要在该线程执行Looper.prepare() & Looper.loop()。只有调用Looper.loop()之后,消息循环才算真正运转起来了。具体来说,启动消息循环的分为两种情况:主线程消息循环 & 子线程消息循环,前者由 Framework 启动,而后者需要我们自己启动:

  • 主线程消息循环

可以看到,在应用启动时,Framework 已经为主线程开启了消息循环,后续我们熟悉的startActivity & startService都是通过主线程消息循环来驱动的。

  • 子线程消息循环

在子线程开启消息循环,我们需要自己调用Looper.prepare() & Looper.loop();。可以直接创建线程,或者使用 HandlerThread,后者主要考虑的多线程中获取 Looper 的同步问题,见 第 5.1 节。

小结一下:创建 Handler 的代码需要放在Looper.prepare(); & Looper.loop();中间执行,这是因为创建 Handler 对象时需要聚合 Looper 对象(默认使用的是当前线程的 Looper),而只有执行Looper.prepare();之后,才会创建该线程私有的 Looper 对象,否则创建 Handler 会抛异常。

2.2 Looper 线程唯一性

  • 问:说一下 Looper、handler、线程间的关系。例如一个线程可以对应几个 Looper、几个Handler?

  • 问:ThreadLocal 的原理,以及在 Looper 是如何应用的?

每个线程只允许调用一次Looper.prepare(),否则会抛异常。这样设计是因为一个 Looper 对应了一个消息循环,而一个线程进行多个消息循环是没有意义的(一个线程不可能同时进行两个死循环)。那么,Handler 是如何保证 Looper 线程唯一的呢?

答:首先,Handler 主要利用了 ThreadLocal 在每个线程单独存储副本的特性,保证了一个ThreadLocal<Looper>在不同线程存取的Looper对象相互独立;其次,ThreadLocal 是 Looper 的一个static final变量,这样就保证了整个进程中 sThreadLocal对象不可变;第三,Looper.prepare()判断在一个线程里重复调用,则会抛出异常。

关于 ThreadLocal 的原理分析,在这篇文章里,我们详细讨论:《Java | ThreadLocal 用法解析》,请关注!

2.3 消息发送

  • 问:Handler#post(Runnable) 是如何执行的?(字节、小米)

  • 问:Handler#sendMessage() 和 Handler#postDelay() 的区别?(字节)

  • 问:多个 Handler 发消息时,消息队列如何保证线程安全?

  • 问:为什么 MessageQueue 不设置消息上限?

消息发送的 API 非常多,最终它们都会调用到Handler#sendMessageAtTime(Message msg, long uptimeMillis),内部会交给MessageQueue#enqueueMessage(Message msg, long when)处理,梳理如下:

消息发送调用链

 

消息入队关键源码

小结一下:

  • 每个消息的处理时间(when)不一样(SystemClock.uptimeMillis() + delayMill)
  • 消息入队时,根据消息的处理时间(when)做插入排序,队头的消息就是最需要执行的消息
  • 当消息队列为空时(无消息时线程会阻塞),消息入队需要唤醒线程
  • 当消息队列不为空时(一般不需要唤醒),只有当开启同步屏障后第一个异步消息需要唤醒(开启同步屏障时会在队首插入一个占位消息,此时消息队列不为空,但是线程可能是阻塞的),关于同步屏障的内容见第 3 节

2.4 消息获取

  • 问:消息队列无消息会怎么样?为什么 block 不会 ANR?

  • 问:Looper 死循环为什么不会 ANR?(B站)

  • 问:Looper 死循环为什么不阻塞主线程?

  • 问:Handler内存泄漏的原因?

上一节我们说到,消息入队后 Looper 所在线程就会被唤醒(如果被阻塞),以继续消息循环。在消息循环中,Looper.loop()会死循环从 MessageQueue 获取队首的消息,因为消息已经按照处理时间(when)排序,所以每次获取的都是when最小的消息:

【图】 loop next

至于 Looper 死循环为什么不会 ANR?

 

  1.   消息队列中无消息怎么处理 block
  2.   nativePollOnce值为-1表示无限等待,让出cpu时间片给其线程,本线程等待
  3.   0表示无须等待直接返回
  4.   nativePollOnce -> epoll(linux) ->linux层的messagequeue

 

  1.   msg -> 5s -> ANRmsg
  2.    
  3.   ANR:
  4.   5秒内没有响应输入事件,比如按键、屏幕触摸
  5.   10秒内没有处理广播
  6.   本质:消息队列中其他消息耗时,按键或广播消息没有及时处理
  7.    
  8.   根本原因不是线程在睡眠,而是消息队列被其他耗时消息阻塞,导致按键或广播消息没有及时处理
  9.    

 

  1.   Handler内存泄漏的原因
  2.   MessageQueue持有Message,Message持有activity
  3.   delay多久,message就会持有activity多久
  4.   方法:静态内部类、弱引用

取到一个消息时,如果when还不到,则有限等待(nextPollTimeoutMills)nativePoll()
如果消息队列没有消息,则无限等待nativePoll(-1,),而消息入队时,会执行nativeWake()

quit也会nativeWake,唤醒Looper所在线程 => messagequeue返回null => Looper退出

2.5 消息分发

  • 问:Message.callback 与 Handler.callback 哪个优先?

  • 问:Handler.callback 和 handlemessage() 都存在,但 callback 返回 true,handleMessage() 还会执行么?(字节、小米)

获取需要执行的消息之后,将调用msg.target.dispatchMessage(msg);处理消息,具体如下:

【图】

 

  1.   public void dispatchMessage(Message msg) {
  2.   if (msg.callback != null) {
  3.   // 1. 设置了Message.Callback(Runnable)
  4.   handleCallback(msg);
  5.   } else {
  6.   if (mCallback != null) {
  7.   // 2. 设置了 Handler.Callback(Callback )
  8.   if (mCallback.handleMessage(msg)) {
  9.   return;
  10.   }
  11.   }
  12.   // 3. 未设置 Handler.Callback 或 返回 false
  13.   handleMessage(msg);
  14.   }
  15.   }
  16.   public interface Callback {
  17.   public boolean handleMessage(Message msg);
  18.   }

可以看到,除了在Handler#handleMessage(...)中处理消息外,Handler 机制还提供了两个 Callback 来增加消息处理的灵活性。具体来说,若设置了Message.Callback则优先执行,否则判断Handler.Callback的返回结果,如果返回false,则最后分发到Handler.handleMessage(...)

2.6 终止消息循环

 

  1.   quit:
  2.   mQuitting = true
  3.   removeAllMessage()
  4.   nativeWake() 唤醒,程序从nativePollOnce(-1)开始执行
  5.    
  6.   主线程Looper不允许退出 quit() 抛异常 mQuitAllowed = false
  7.   ActivityThead#main looper.loop() 之后抛异常
  8.   原因:是handler驱动的机制,所有的事件都需要Handler处理,例如LAUNCH_ACTIVITY等

3. Handler 同步屏障机制

同步屏障(SyncBarrier)是 Handler 用来筛选高低优先级消息的机制,即:当开启同步屏障时,高优先级的异步消息优先处理。

3.1 开启同步屏障

3.2 关闭同步屏障

3.3 同步屏障下的消息循环

了解更多同步屏障: https://blog.csdn.net/jdsjlzx/article/details/110563162


4. IdleHandler 机制

  • 问:IdleHandler 是什么?怎么使用,能解决什么问题?

详见:https://blog.csdn.net/jdsjlzx/article/details/110532500


5. Handler 应用场景

4.1 HandlerThread

Handler 都是在 Looper 所在线程创建的,但是有时候我们需要在其他线程中创建 Looper 所在线程的 Handler,就需要考虑同步问题,使用 HandlerThread 可以简化这种同步处理:

既然涉及多个线程的通信,会有同步的问题,Android为了简化Handler的创建过程,提供了HandlerThread类

wait - notifyAll - 避免prepare之前调用getLooper()

【重点 锁的机制】

4.2 IntentService

处理完 service 自动停止 内存释放

4.3 Fragment 生命周期管理

attach -> commit
Glide生命周期管理 RequestManagerFragment 双重检查(避免连续两次with()重复创建Fragment,因为commit会发到Handle消息队列的)

Handler是贯穿于Android的消息管理机制

所有的代码都是在Handler上运行的(loop()死循环)

标签:知识点,队列,Handler,线程,Looper,Android,Message,消息
From: https://www.cnblogs.com/ioriwellings/p/16333326.html

相关文章

  • 《花雕学AI》20:ChatGPT使用之体验评测AI EDU的网页版+桌面端+Android+App store组合
    最近准备出门,要去新疆哈密参加活动,一直在寻找手机上可用的AI移动端。昨天在网上偶然找到了AIEDU(这个不是MSRA创立的人工智能开源社区),其链接是:https://ai.aigcfun.com,今天就尝试做个相关体验与学习的记录。打开首页如下:  引言:人工智能聊天机器人ChatGPT是一种基于GPT-......
  • Android studio 中fragment 的简单应用
    在AndroidStudio中,Fragment是一种可重用的UI组件,它代表了Activity中的一部分界面。它类似于Activity,但是可以被添加、删除和替换,同时可以与其他Fragment组合在一起形成更复杂的UI界面。通常情况下,Activity由多个Fragment组成,每个Fragment都有自己的布局和功能,可......
  • Android深入学习之LayoutInflater类和ViewBinding
    在build.gradle(Module)中添加viewBinding元素后,Android会自动给模块中的每个XML布局文件生成一个相应的Binding类,该Binding类名称为XML布局文件驼峰式大写+Binding后缀。以如下所示的activity_welcome.xml文件为例,对应的ActivityWelcomeBinding.java的源代码如下所示。<?xmlv......
  • 项目管理PRINCE2核心知识点整理
    前言PRINCE2,即PRojectINControlledEnvironment(受控环境中的项目)是一种结构化的项目管理方法论,由英国政府内阁商务部(OGC)推出,是英国项目管理标准。PRINCE2作为一种开放的方法论,是一套结构化的项目管理流程,描述了如何以一种逻辑性的、有组织的方法,按照明确的步骤且基于商业论......
  • @RestControllerAdvice注解 @ExceptionHandler注解
    RestControllerAdvice+ExceptionHandler这两个注解的组合,被用作项目的全局异常处理。一旦项目中发生了异常,就会进入使用了RestControllerAdvice注解类中使用了ExceptionHandler注解的方法。下面是一些项目全局异常的处理@ControllerAdvice(annotations={RestController.class,......
  • Android入门教程_废弃
    没意思,不想写了... 目录一,Android介绍Android概述什么是AndroidAndroid开发优势Android的特性可以开发什么appAndroid手机安装包apkAndroid架构https://www.runoob.com/android/android-architecture.html学习安卓需要具备哪些知识(PS+UI(优秀软件:墨刀-万兴科技......
  • Android Studio调用高德地图api
    一.搜索高德开放平台,进行注册并登录,进入到自己的控制台,打开应用管理下的我的应用,点击创建新应用。1.关于获取发布版安全码SHA1的过程如下:    打开AndroidStudio下方的Terminal,并自行找到.android在电脑中的位置,找到了之后,按照下图所示:   之后执行keytool-lis......
  • 前端小知识点扫盲笔记记录8
    前言我是歌谣放弃很容易但是坚持一定很酷微信公众号关注前端小歌谣带你进入前端巅峰交流群今天继续对前端知识的小结命令模式宏命令<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge">......
  • Android开发,使用的是OkHttp和Reftrofit,用的是Kotlin协程,用Kotlin写一个网络拦截器,模拟
    首先,我们需要定义一个网络拦截器类,继承自OkHttp的Interceptor接口:classLoginInterceptor:Interceptor{overridefunintercept(chain:Interceptor.Chain):Response{//模拟登录请求,这里可以根据具体情况进行修改valrequest=chain.request().ne......
  • Android MediaCodec 解码 mp4
    上篇博文:AndroidMediaCodec功能讲解本文示例源代码:MediaCodec解码播放mp4文件上篇博文中,我们讲解了MediaCodec的基础知识,本篇文章我们通过使用MediaCodec解码并播放mp4文件,来讲下MediaCodec的使用。解码并播放mp4文件主要涉及到了以下5大方面的功能:解码视频......