carsonho.blog.csdn.net/article/det…
一、Handler原理介绍
Handler:Android
消息传递机制;作用:
在多线程场景中,将工作线程中需更新UI
的操作信息 传递到 UI
主线程,从而实现异步消息处理(是什么)
Android为了UI操作是线程安全的,规定只允许UI主线程更新Activity里的UI组件,但是实际开发中,存在多个线程并发操作UI组件的情况(为什么)
工作流程:四个步骤(怎么做的)
- 异步通信准备
- 消息发送
- 消息循环
- 消息处理
1、异步通信准备:在主线程中创建:Handler,自动绑定了主线程的Looper,Message Queue,主线程创建Message Queue后,Looper自动进入消息循环
2、消息发送:工作线程通过Handler发送消息到消息队列中
3、消息循环:消息出队,Looper循环取出消息队列中的消息;消息分发,Looper将取出的消息发送给创建该消息的处理者Handler
4、消息处理:Handler接收到Looper发过来的消息,根据消息进行UI操作
注意
线程(Thread)、循环器(Looper)、处理者(Handler)之间的对应关系如下:
1个线程(Thread)只能绑定 1个循环器(Looper),但可以有多个处理者(Handler)
1个循环器(Looper) 可绑定多个处理者(Handler)
1个处理者(Handler) 只能绑定1个1个循环器(Looper)
二、内存泄漏原因
在Handler
消息队列中的Message
持有Handler
实例的引用,
Handler
的一般用法 = 新建Handler
子类(内部类) 、匿名Handler
内部类,Java中
非静态内部类 & 匿名内部类都默认持有外部类的引用,所以存在这样的引用链 Message ——》Handler——》Activity,直到Handler
消息队列中的所有消息被处理完毕,在Handler
消息队列 还有未处理的消息 / 正在处理消息时,此时若需销毁外部类Activity
,但由于上述引用关系,垃圾回收器(GC)
无法回收Activity
,从而造成内存泄漏。
解决方案 造成内存泄露的原因有2个关键条件:
- 存在 “未被处理 / 正处理的消息 ->
Handler
实例 -> 外部类” 的引用关系 Handler
的生命周期 > 外部类的生命周期
- 解决方案1:静态内部类
将
Handler
的子类设置成静态内部类。使用WeakReference弱引用持有外部类 - 解决方案2:当外部类结束生命周期时,清空Handler内消息队列
使得
Handler
的生命周期(即 消息存在的时期) 与 外部类的生命周期 同步,onDestroy()时调用removeCallbacksAndMessages(null)
为了保证Handler
中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即 静态内部类 + 弱引用的方式
子线程是否可以使用Handler?
可以使用,要先创建 Looper
,并开启 Looper
循环
,非主线程默认没有创建Looper对象,也没有没有开启消息循环。
Looper.prepare(); Looper.loop();
三、什么是 IdleHandler?有什么用?怎么用 ?
IdleHandler: MessageQueue 的静态内部接口,就是 Handler 机制提供,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许执行任务的一种机制。不要做耗时操作
定义时需要实现其 queueIdle()
方法。queueIdle()
方法如果返回 false,那么这个 IdleHandler
只会执行一次,执行完后会从队列中删除,如果返回 true,那么执行完后不会被删除,只要执行 MessageQueue.next
时消息队列中没有可执行的消息,即为空闲时间,那么 IdleHandler
队列中的 IdleHandler
还会继续被执行。
//getMainLooper().myQueue()或者Looper.myQueue()
Looper.myQueue().addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
//你要处理的事情
return false;
}
});
ActivityThread中GcIdler
在ActivityThread中的H收到GC_WHEN_IDLE消息后,会执行scheduleGcIdler,将GcIdler添加到MessageQueue中的空闲任务集合中,在queueIdle方法被回调时,会做强行GC的操作(即调用BinderInternal的forceGc方法),但强行GC的前提是,与上一次强行GC至少相隔5秒以上。
peterxiaosa.github.io/2020/07/14/…
四、Looper 死循环为什么不会导致应用卡死,会消耗大量资源吗?
对于线程就是一段可执行的代码,当可执行代码执行完成后,线程生命周期便终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,死循环便能保证不会被退出,就是可执行代码是能一直执行下去的
当然并非简单地死循环,无消息时会休眠。既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法
onCreate/onStart/onResume
等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。
主线程的死循环一直运行是不是特别消耗CPU资源呢?
这里就涉及到
Linux pipe/epoll
机制,简单说就是在主线程的MessageQueue
没有消息时,便阻塞在loop的queue.next()
中的nativePollOnce()
方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。