Android面试——Handler

Handler:处理异步消息

Handler是Android中用于进行线程间通信的一种机制。其主要作用是将消息发送到与创建Handler时关联线程的消息队列中,从而实现异步操作。

消息队列(Message Queue): 用于存储待处理的消息。

消息(Message): 包含了要执行的任务和相关信息。

处理消息的Looper: 一个线程中只能有一个Looper,用于不断地从消息队列中取出消息并交由Handler处理。

在Android中,Handler常用于UI线程和后台线程之间的通信。通过Handler,我们可以将任务发送到消息队列,然后由Looper负责按顺序执行这些任务,实现异步操作。

Handler工作流程

概述:Handler将Message发送到Looper的消息队列中,即MessageQueue,等待Looper循环读取Message,处理Message,然后调用Message的target,即附属Handler的dispatchMessage方法,将该消息回调到handleMessage方法中,然后完成UI更新。

Handler主要函数

MessageQueue主要函数

链表,优先级队列,时间排序

1、一个线程有几个handler?

答:多个,只要内存够,就可以new多个handler

2、一个线程有几个looper?如何保证?——一个looper,threadlocal保证

答:一个线程对应一个hashmap<key, value>,存在多个entry数组,但是key对应threadlocal(final的),value对应looper,在looper.prepare时进行threadlocal和looper绑定,map中key是唯一的,value可以是多个,在prepare时通过threadlocal的get判断是否looper已经创建,保证looper的唯一性,即value的唯一性

3. Handler内存泄漏原因?为什么其他的内部类没有说过有这个问题?

答:持有activity,内部类:持有外部类的对象引用

因为非静态内部类会隐式持有外部类实例的引用,当非静态内部类的引用的声明周期长于Activity的生命周期时,会导致Activity无法被GC正常回收掉

发送消息和处理消息的handler是一个,为什么子线程的handler发送的消息,主线程还可以用同一个handler处理消息

MessageQueue-->Message-->包含Handler-->持有activity

delay:Message:1min

1min后messagequeue才去处理message,即1min后message才解除Handler关系,和activity持有关系,msg没有被及时处理,activity就会被一直持有,即使activity调用ondestroy,但是由于activity被handler持有,所以得不到释放,造成内存泄漏

解决:handler静态内部类,弱引用方式

4. 为何主线程可以new Handler?如果想要在子线程中new Handler要做些什么准备?

启动app:点击桌面icon-->launcher-->zygote-->application(art,为每个app创建一个虚拟机)-->启动ActivityThread.main()函数,进行主线程looper初始化,所以我们可以在主线程new handler

 而子线程中并没有创建lopper对象,所以需要先调用Looper.prepare(),之后再创建Handler对象,最后调用Looper.loop(),不断的读出消息。

5. 子线程中维护的looper,消息队列无消息的时候处理方案是什么?有什么用?

queue.next()

nativePollOnce(long ptr,时间)// 睡眠、等待

时间:如果为-1,则表示无限等待(sleep,线程挂起),直到有事件发生为止。如果值为0,则无需等待立即返回

looper.quit(boolean)、nativeWake(long ptr)// 唤醒、recycleUncheck()// 将消息中变量置空

handler睡眠和唤醒机制

主线程需要释放么?——绝对不能,quit函数中会有主线程相关判断,报异常

6. 既然可以存在多个handler往msgqueue中添加数据(发消息时各个handler可能处于不同线程),那它内部是如何确保线程安全的?

一个线程对应一个looper,一个looper对应一个msgqueue,一个线程对应一个msgqueue

加锁(内置锁):synchronized(this),这里的this代表msgqueue,(synchronized可以修饰函数,静态函数,代码块)

7. 我们使用msg时应该如何创建它?

obtain方法,享元设计模式(bitmap中在使用),消息处理完只清空此块内存里面的变量值,不释放此块内存,下次使用直接改变此块内存中变量值即可,防止了一直new--destroy

8. 不同的Handler如何与他的msg绑定的?

由于一个线程中可能有多个handler,为了区分这些不同的hanlder所需要处理的消息,每个Message对象都维护有一个hanlder实例即target,Message通过target属性标识handler。在loop方法中通过调用msg.target.dispathMessage(msg)方法将消息分发到与Message绑定handler.handleMessage()方法中。 所以一个handler发送的消息只会被自己接收

Looper会存在线程的哪里?

Looper会存在线程的ThreadLocal对象里,该对象是线程的缓存区

8. looper死循环为什么不会导致应用卡死

卡死是ANR,而looper没有消息需要处理时,就睡眠,主线程处于等待,cpu给其他线程使用 

应用卡死,也就是ANR所产生的原因?
① 5秒钟之内没有响应输入的事件,比如按键、屏幕触摸等。
② 广播接收器在10秒内没有执行完毕

AMS管理机制
每一个应用都存在于自己的虚拟机中,也就是说每一个应用都有自己的一个main函数。
启动流程:launcher->zygote->art application->activityThread->main()
所有应用所有生命周期的函数(包括Activity、Service所有生命周期)都运行在这个Looper里面
而且,它们都是以消息的方式存在的

既然Handler的消息全都是loop来的,为什么我们没有ANR问题?
之前不是说5秒钟不响应就会出现阻塞问题吗,为什么休眠好长时间也并不会被ANR呢?
唤醒线程的方法:

1) looper中添加message. 通过nativeWait()->loop运作
2) 输入事件
产生ANR的问题不是因为主线程睡眠了,而是因为输入事件没有响应,输入事件没有响应他就没有办法唤醒这个Looper,才加了这个5秒的限制

ANR:消息没有及时处理,一旦超过5s,则产生ANR,导致消息队列阻塞 

9. looper.loop为什么不会阻塞主线程

10. msg的数据结构是什么样子

11. 使用handler的postDelay后消息队列会有什么变化

Handler主要场景是子线程完成耗时操作的过程中,通过 Handler向主线程发送消息Message,用来刷新UI界面
了解Handler的发送消息和处理消息的源码实现
Handler源码的一个切入点就是它的默认构造器

从New Handler()开始

这个 Looper 是什么?何时被初始化?

Looper介绍

启动一个Java程序的入口函数是main方法,当main函数执行完毕之后此程序停止运行,也就是进程会自动终止。但是当打开一个Activity之后,只要不按下返回键Activity会一直显示在屏幕上,也就是Activity所在进程会一直处于运行状态
Looper 内部维护一个无限循环,保证 App进程持续进行

Looper初始化

if 这行代码的目的是确保在一个线程中Looper.prepare()方法只能被调用1次  

下面截图写法会报错:不是说调用2次prepare才会抛异常吗?为什么MainActivity中只调用了1遍
就导致程序崩溃?

是因为在MainActivity所在进程被创建时,Looper的prepare方法已经在main方法中调用了1遍
这会直接导致一个非常重要的结果:
prepare()方法在一个线程中只能被调用1次
Looper 的构造方法在一个线程中只能被调用1次
最终导致 MessageQueue在一个线程中只会被初始化1次 

Looper负责做什么事情

Looper做的事情是:不断从 MessageQueue中取出Message,然后处理Message中指定的任务 

 

Handler的 sendMessage 方法

Handler的 enqueueMessage 方法

Looper.loop()为什么不会阻塞主线程
Looper中的 loop方法实际上是一个死循环,但是我们的UI线程却并没有被阻塞,反而还能够进行各种手势操作,这是为什么呢???

nativePollOnce方法是一个native方法,当调用此native方法时,主线程会释放CPU资源进入休眠状态,直到下条消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作

Handler的sendMessageDelayed或者postDelayed 是如何实现的?
在向MessageQueue队列中插入Message时,会根据Message的执行时间排序
而消息的延时处理的核心实现是在获取Message 的阶段。如下所示:

如果当前系统时间大于或等于Message.when,那么会返回Message给Looper.loop()
但是这个逻辑只能保证在when之前消息不被处理,不能够保证一定在when时被处理

总结

1. 应用启动是从ActivityThread的main 开始的
先是执行了Looper.prepare(),该方法先是new了一个Looper对象,在私有的构造方法中又创建了MessageQueue作为此Looper对象的成员变量,Looper对象通过ThreadLocal绑定MainThread 中

2. 当创建Handler子类对象时,在构造方法中通过ThreadLocal获取绑定的Looper对象,并获取此 Looper对象的成员变量MessageQueue作为该Handler对象的成员变量 

3. 在子线程中调用上一步创建的Handler子类对象的sendMesage(msg)方法时,在该方法中将msg的target属性设置为自己本身,及handler本身,同时调用成员变量MessageQueue对象的enqueueMessage()方法将msg放入MessageQueue中

4. 主线程创建好之后,会执行Looper.loop()方法,该方法中获取与线程绑定的Looper对象,继而获取该Looper对象的成员变量MessageQueue对象,并开启一个会阻塞(不占用资源)的死循环,只要MessageQueue中有msg,就会获取该msg,并执行msg.target.dispatchMessage(msg)方法,(msg.target即上一步引用的handler对象),此方法中调用了第二步创建handler子类对象时复写的handleMessage()方法

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值