Handler原理,内存泄漏原因

131 阅读5分钟

carsonho.blog.csdn.net/article/det…

www.jianshu.com/p/027af7e6b…

www.jianshu.com/p/027af7e6b…

一、Handler原理介绍

Handler:Android 消息传递机制;作用: 在多线程场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现异步消息处理(是什么)

Android为了UI操作是线程安全的,规定只允许UI主线程更新Activity里的UI组件,但是实际开发中,存在多个线程并发操作UI组件的情况(为什么)

工作流程:四个步骤(怎么做的)

  1. 异步通信准备
  2. 消息发送
  3. 消息循环
  4. 消息处理

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个关键条件:

  1. 存在 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系
  2. 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资源。

截图

image.png

image.png

image.png

image.png