Handler源码分析

115 阅读3分钟

一图胜千言

image.png

read the fuck source code

分别看一下三个核心类Handler,MessageQueue,Looper都做了什么

首先从Handler的sendMessage()方法开始

image.png

经过一系列的调用,最后通过MessageQueue的enqueueMessage方法,将Message插入消息队列.

这里的Message其实是一个链表结构.

image.png

看一下MessageQueue是怎么处理添加进来的Message的

image.png

第一个if里面,如果当前列表是空的,直接插入.

否则,遍历当前的链表直到找到时间大于当前message的时间的message, 或者当next为null,遍历到了尾节点,插入进去.

这里注意:MessageQueue里面的消息,是根据时间排好序的,所以Looper中依次轮询即可.

那么什么时候取消息呢?

当我们的应用进程启动的时候ActivityThread的main方法就开启了MainLooper的轮询,这方面的知识可以看下Acitviy启动流程这篇文章.

直接看Looper.loop方法

image.png

就是开启for(;;)循环,循环调用MessageQueue.next方法获取Message对象

如果返回为空代表应用退出了,调用Looper.quit()方法.

// Return here if the message loop has already quit and been disposed.

// This can happen if the application tries to restart a looper after quit

当子线程的Looper获取不到Message的时候,Looper就退出循环.

如果消息不为空调用msg.target.dispatchMessage(msg)分发给Handler执行,这里的msg.target就是发送Message的Handler.

接着看一下MessageQueue.next方法的实现

image.png

这里面有一个很重要的方法

nativePollOnce(ptr, nextPollTimeoutMillis);

//休眠到一定时间自动唤醒

//或者等到新消息到来主动唤醒nativeWake(mPtr);

其他相关问题

Handler Looper MessageQueue Message的关系

一个线程对应一个Looper,一个Looper对应一个MessageQueue

为什么建议使用Message.obtain方法创建Message

使用Message.obtain方法创建对象的好处是可以利用对象缓存池sPool

Message.obtain源码:


    synchronized (*sPoolSync*) {

        if (*sPool* != null) {

            Message m = *sPool*;

            *sPool* = m.next;

            m.next = null;

            m.flags = 0; // clear in-use flag

            *sPoolSize*--;

            return m;

        }

    }

    return new Message();

}

Looper.prepare()源码

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

prepare就是将Looper对象放到TheadLocal中

ThreadLocal

当创建一个ThreadLocal变量时,访问这个变量的每个线程都有这个变量的一个本地副本。 当多个线程操作这个变量时,实际上就是操作自己本地内存里面的变量,从而避免了线程安全问题。 这个变量副本存放在ThreadLocalMap中。对应关系如下

image.png

ThreadLocalMap存放的就是以ThreadLocal为key,以ThreadLocal的initialValue为Value的合集. initialValue的值,默认为null

举个例子:

public class ThreadLocaDemo {
 
    private static ThreadLocal<String> localVar = new ThreadLocal<String>();
 
    static void print(String str) {
        //打印当前线程中本地内存中本地变量的值
        System.out.println(str + " :" + localVar.get());
        //清除本地内存中的本地变量
        localVar.remove();
    }
    public static void main(String[] args) throws InterruptedException {
 
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_A");
                print("A");
                //打印本地变量
                System.out.println("after remove : " + localVar.get());
               
            }
        },"A").start();
 
        Thread.sleep(1000);
 
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_B");
                print("B");
                System.out.println("after remove : " + localVar.get());
              
            }
        },"B").start();
    }
}
 
A :local_A
after remove : null
B :local_B
after remove : null
 

Looper的prepare方法就是将Looper自己set到ThreadLocal中去,这样就保证在当前线程中只有一个Looper对象

image.png

看一下set方法做了什么

image.png

Linux的epoll机制,阻塞式休眠

原理IO多路复用

epoll_create:建立一个epoll对象

epoll_ctl:添加socekt

epoll_wait

nativeWake(mPtr);

面试题:

1.如果发送同一个消息会怎样

源码中会抛出异常

if (msg.target == null) {

    throw new IllegalArgumentException("Message must have a target.");

}

if (msg.isInUse()) {

    throw new IllegalStateException(msg + " This message is already in use.");

}

2.如果频繁发送Message会怎样

频繁的创建Message会造成内存抖动,甚至OOM

造成卡顿

3.如何防止Handler内存泄漏

handler.removeCallbacksAndMessage(null)

静态内部类+弱引用

4.内存屏障是什么

当MessageQueue轮询到内存屏障类型的消息,会优先处理所有的异步任务,ui绘制的时候就是通过添加消息屏障,保证绘制流程.