handler原理解析以及导致的内存泄漏及解决方法
所有的线程间通信,比如Rxjava,eventBus底层都是通过handelr来实现的。
用法举例
val handler = Handler(Looper.getMainLooper()) // 在子线程中 Thread { // 执行一些耗时任务 val message = handler.obtainMessage() message.obj = "更新UI的数据" handler.sendMessage(message) }.start() // 在主线程处理消息 handler.handleMessage = { msg -> val data = msg.obj as String // 更新UI }
原理:
1:所有的sendMessage方法或者post方法,最终都是调用sendMesssageAtTime,里面调用enqueueMessage,即把消息放入MessageQueue里面。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
MessageQueue本质是一个按时间(Message的when)顺序排列的链表,
因为加上了按时间排序的规则,并且从头部开始按顺序取队列,所以可以叫做优先级队列。
这里的时间就是:当我们sendmessage的时候,默认delay时间为0。每一个消息都会有一个执行时间。
public final boolean sendMessage(@NonNull Message msg) { return sendMessageDelayed(msg, 0); }
2:Looper.loop
loop()通过for循环,从MessageQueue里面取消息。
最终调用到handler的handlerMessage,我们上层应用就可以通过重写该函数实现消息的处理
3:AMS是围绕ActivityThread的H的handleMessage来管理的,所以handler很重要。
问题:
1:一个线程几个handler和Looper
可以new多个handler,但是只有一个Looper,一个Looper对应一个MessageQueue
ThreadLocal:是一个<key, value>键值对,可以保证一个线程Looper只有一个。
Looper的构造函数是一个私有函数,所以通过Looper.prepar来初始化。
这段代码就说明了可以通过ThreadLocal来保证一个线程只能有一个Looper。
ThreadLocal的key是线程,value是Looper
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
2:为什么主线程可以直接new Handler,子线程需要做什么?
应用启动的时候,启动ActivityThread的main函数,里面自动调用Looper.prepare,new出一个Looper,然后loop,启动loop循环。
子应用需要自己调用Looper.prepare.和loop().
3:子线程中维护的Looper,消息队列无消息的时候的处理方案是什么,有什么用:
当消息为空的时候,会调用Message的next(),调用nativePollOnce
nativePollOnce:native函数,调用Linux的epoll机制。即如果没有message,epoll机制会无限等待,线程挂起。
4:一个线程-》一个Looper-》一个MessageQueue
一个线程里面,多个Handler往MessageQueue里面添加数据(发消息是各个Handler可能处于不同线程),内部如何确保线程安全:
加锁:synchronized(this)
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } synchronized (this) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); }
5:Message是如何创建的:
享元设计模式,删除的时候,并没有直接删除,而是把内存卡移到另外一个链表,需要的是再取过来用。
这样不用频繁的new内存和回收内存,否则会导致内存抖动。
内存泄漏以及解决方法:
1:非静态内部类默认持有外部类的引用,只有这样才能直接使用外部类的属性和方法,静态内部类不持有外部类的引用;
2: Message的target就是handler:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
handler如果sendDelayMessage,比如延迟30S执行消息,这时候持有的外部类Activity如果被销毁,但是主线程里的MessageQueue里面消息还未被执行,message的target会持有handler,而handler又持有Activity的引用,导致Activity无法被回收。
持有链:MessageQueue->Message->handler->Activity
3:解决方法:把handler设置为静态内部类,但是有时候又需要引用到Activity,比如更新UI等需要调用外部类的属性和方法,但是静态内部类默认不持有外部类的引用,这时候可以用弱引用,使handler弱引用Activity。这样在GC的时候,就可以回收Activity。
4:强引用,软引用,弱引用和虚引用的区别:
1)普通的引用就是强引用,GC无法回收有强引用的对象
2)软引用是GC的时候,系统判断资源不足回收,
3)弱引用是GC的时候肯定会回收。
4)虚引用:对对象的回收没有影响,而且无法通过该引用找到该对象(这是和弱引用区别的),只是为了追踪该对象何时被回收,当一个对象只定义了虚引用,
GC之后如果该被回收,就会吧该对象添加到RefereneceQueue里面,后续我们可以通过ReferencQueue里面是否有改对象来判断是否存在内存泄漏。
5:因为sendDelayMessage未处理的消息还存在主线程的MessageQueue,如果此时的Activity被销毁,显然消息是不需要的,需要在外部类Activity的onDestroy对消息进行移除,removeMessage
例子:这个test如果不在UI线程,我们就需要通过Handler(Looper.getMainLooper())来制定Handler和主线程绑定,myHandler.postDelayed( { onTimeout() }, timeoutInMillis )这里是的onTimeOut是一个Runnable,意思是
固定延时时间之后,自动执行onTimeout这个函数,这时候可以不需要handler.handleMessage().
private class test( val onTimeout: () -> Unit ) { private val myHandler: Handler = Handler(Looper.getMainLooper()) var timeoutInMillis: Long = 0 // This runnable for count down timer fun start() { cancel() if (timeoutInMillis > 0) { myHandler.postDelayed( { onTimeout() }, timeoutInMillis ) } } fun cancel() { myHandler.removeCallbacksAndMessages(null) } }