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
}

 

 

 
Handler的重点是:通过threadLocal,将thread和Looper绑定起来。许多地方直接调用这个,就可以得到Looper,进而得到一些列的
MessageQueue,handler等。一个线程只能有一个Looper,当然也只能有一个MessageQueue,但是可以有很多的Handler
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
 
ActivityThread: main->
Looper.prepareMainLooper();
 
这个作用是:new Looper对象,Looper持有MessageQueue和currentThread
 
MessageQueue里面有Message,Message持有handler
 
 
Handler里面持有MessageQueue,当应用层new Hanlder的时候,构造函数会通过
mLooper = Looper.myLooper();
if (mLooper == null) {
    throw new RuntimeException(
        "Can't create handler inside thread " + Thread.currentThread()
                + " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
 
获取到当前thread的Looper的MessageQueue
 
所以当handler sendMessage的时候,会把message放入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);
}
 
最后通过Looper.loop,循环获取Messagequeue里面的message
for (;;) {
    if (!loopOnce(me, ident, thresholdOverride)) {
        return;
    }
}
 
获取到message后就要进行分发:Looper.java:
msg.target.dispatchMessage(msg);
 
这里的target就是handler,然后就到了应用层的回调处理函数
handler:
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

原理:

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)
    }
}

 

posted @ 2022-03-23 09:10  蜗牛攀爬  阅读(746)  评论(0编辑  收藏  举报