Handler消息机制和ThreadLocal原理

Posted 冬天的毛毛雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler消息机制和ThreadLocal原理相关的知识,希望对你有一定的参考价值。

一、简介

Handler作为android线程间通信的常用方式,主要由Handler、Looper、MessageQueue、message四个部分组成 ,

  • Handler负责线程间消息通讯的发送消息和接收处理消息
  • Looper用于从MessageQueue中轮询获得Message
  • MessageQueue则是一个单线链表的用于存储Message的数据结构
  • Message则是线程间通信的载体

总体流程图如下

简单使用,如下子线程发送message,主线程更新UI

private Handler handler=new Handler(new Handler.Callback() 
    @Override
    public boolean handleMessage(@NonNull Message message) 
        //主线程更新UI
        return true;
    
);

new Thread(new Runnable() 
    @Override
    public void run() 
        //子线程发送消息
        handler.sendEmptyMessage(1);
        //发送Runnable本质也是发送message,Runnable被赋值给Message.callback
        handler.post(runnable);
    
).start();

二、源码分析

2.1、Handler

先从Handler构造函数开始

public Handler(@Nullable Callback callback, boolean async) 
    ...
    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;
    mCallback = callback;
    mAsynchronous = async;


public static @Nullable Looper myLooper() 
    return sThreadLocal.get();

先是通过Looper.myLooper()获得Looper对象,Looper对象是在Looper.prepare()时创建的,但是这里我们并没有调用prepare(),是因为一开始在ActivityThreadmain方法中调用了Looper.prepareMainLooper()已经初始化了Looper

public static void main(String[] args) 
    ...
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) 
        sMainThreadHandler = thread.getHandler();
    
    if (false) 
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");


public static void prepareMainLooper() 
    prepare(false);
    synchronized (Looper.class) 
        if (sMainLooper != null) 
            throw new IllegalStateException("The main Looper has already been prepared.");
        
        sMainLooper = myLooper();
    


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


private Looper(boolean quitAllowed) 
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();

prepare方法中创建了Looper,Looper在构造方法中创建了MessageQueue,并且把Looper和当前Thread关联了起来,然后set到sThreadLocal里面,sThreadLocal是泛型类型为Looper的ThreadLocal对象,sThreadLocal.set(new Looper(quitAllowed))实际是把Entry赋值给了Thread的ThreadLocalMap成员变量中,而Entry是ThreadLocal为Key,Looper作为Value的Map对象,关于ThreadLocal后续第三点再做说明

总的来说就是,当前主线程创建了Looper被赋值到了主线程的ThreadLocalMap成员变量中,然后在创建Handler的时候在构造方法中获取到的Looper实际上是从主线程的ThreadLocalMap获取的

如果我们在子线程中创建Handler是需要先执行Looper.propare(),否则直接创建Handler时子线程的ThreadLocalMap并没有保存Looper对象就会抛出如下异常

throw new RuntimeException(
    "Can't create handler inside thread " + Thread.currentThread()
            + " that has not called Looper.prepare()");

2.2、Looper

先看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));

如果ThreadLocal不为空,表示当前线程已经创建过了Looper,重复创建会抛出运行时异常,也说明了,一个线程只有一个Looper对象,在面试的时候经常会问到一个线程有多少个Looper,MessageQueue,Message类似的问题,根据上述分析

答案是:一个线程只会有一个Looper,而MessageQueue则是在Looper构造方法里创建的,所以也只会有一个MessageQueue,至于Message那肯定是有多个的

再来看Looper.loop()方法,loop()方法的作用是用一个死循环不断从MessageQueue中获取Message,然后把获取到的Message通过Message.target.dispatchMessage()回调到给Handler(Message.target就是Handler)

public static void loop() 
    final Looper me = myLooper();
    me.mInLoop = true;
    final MessageQueue queue = me.mQueue;

    boolean slowDeliveryDetected = false;
    //开启死循环
    for (;;) 
        //不断获取下一个MessageQueue中可用的Message
        Message msg = queue.next(); // might block
        if (msg == null) 
            return;
        

        final Printer logging = me.mLogging;
           ...
        try 
            //target是Handler类型的成员变量,这里就把message分发给Handler.dispatchMessage(msg)方法处理了
            msg.target.dispatchMessage(msg);
            if (observer != null) 
                observer.messageDispatched(token, msg);
            
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
         catch (Exception exception) 
            if (observer != null) 
                observer.dispatchingThrewException(token, msg, exception);
            
            throw exception;
         finally 
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) 
                Trace.traceEnd(traceTag);
            
        
        ...
        msg.recycleUnchecked();
    

在来看Handler.dispatchMessage(msg)

public void dispatchMessage(@NonNull Message msg) 
    if (msg.callback != null) 
        handleCallback(msg);
     else 
        if (mCallback != null) 
            if (mCallback.handleMessage(msg)) 
                return;
            
        
        handleMessage(msg);
    

msg.callbackRunnable类型的,如通过handler.post(runnable)或者handler.postDelay(runnable,delayMillis)发送的Runnable会被赋值给Message.callback,这种方式本质还是发送的Message

mCallback则是Handler构造方法传入的,例如如下Callback通过匿名内部类的方式在构造方法时传入,并且默认实现handlerMessage这个带布尔值的方法,如果布尔值返回true则不会执行复写的handleMessage方法,如果返回false则会执行

private Handler handler=new Handler(new Handler.Callback() 
    @Override
    public boolean handleMessage(@NonNull Message message) 
        //入职这个返回的布尔值
        return true;
    
);

2.3、发送消息

在子线程发送消息,比如调用sendMessage,sendMessageDelayed,sendMessageAtTime等方法,最终都会走sendMessageAtTime,然后调用enqueueMessage(queue, msg, uptimeMillis),把消息压入MessageQueue

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) 
    MessageQueue queue = mQueue;
    if (queue == null) 
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    
    return enqueueMessage(queue, msg, uptimeMillis);


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

三、ThreadLocal原理

3.1、ThreadLocal原理

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有的ThreadLocal对象及其所对应的值

由于每一条线程均含有各自的私有的ThreadLocalMap容器,这些容器相互独立不熟影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多线程访问容器的互斥性

public class Thread implements Runnable 
    ...
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMapThreadLocal的静态内部类,是由一个个Entry组成的类似HashMap的数据结构

EntryThreadLocal对象和Object构成,ThreadLocal作为key,Object作为Value

Entry继承WeakRefresh<ThreadLocal<?>>且Entry的key指向ThreadLocal是弱引用的关系,当ThreadLocal没强引用的时候可以被回收,回收后key也就指向了一个空对象了

public class ThreadLocal<T> 
    static class ThreadLocalMap 

        static class Entry extends WeakReference<ThreadLocal<?>> 
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) 
                super(k);
                value = v;
            

            private static final int INITIAL_CAPACITY = 16;

            private Entry[] table;

            private int size = 0;

            private int threshold; // Default to 0

            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) 
                table = new Entry[INITIAL_CAPACITY];
                //取模运算,确定数组下标
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                setThreshold(INITIAL_CAPACITY);
            
        
    

ThreadLocalMap是一个类似HashMap的数据结构,但是在ThreadLocal中,并没实现Map接口,它是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。并且Entry中除了key-value之外并没有next,所以它并不存在链表的结构

当执行ThreadLocal.set(T)时,Thread首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前对象为key,将值存储进ThreadLocalMap对象中

public void set(T value) 
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);

然后再看当ThreadLocalMap不为空时map.set(this, value)方法

private void set(ThreadLocal<?> key, Object value) 
    Entry[] tab = table;
    int len = tab.length;
    //根据key的hashCode确定在数组中的下标位置i
    int i = key.threadLocalHashCode & (len-1);
    //从位置i开始循环下一个位置
    for (Entry e = tab[i];
         e != null;
         //i下一个位置的Entry
         e = tab[i = nextIndex(i, len)]) 
        ThreadLocal<?> k = e.get();
        //如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value
        if (k == key) 
            e.value = value;
            return;
        
        //如果这个key等于null(比如被GC了),那么重新为他赋值
        if (k == null) 
            replaceStaleEntry(key, value, i);
            return;
        
        //如果这个位置下的Entry的key和要增加的key没啥关系,那就继续找下一个位置
    
    //如果数组下标i此时为空,则直接放到数组中
    tab[i] = new Entry(key, value);
    //长度+1
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();

在插入过程中,根据ThreadLocal对象的hash值,确定在table中的位置i,过程如下:

  1. 如果当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上;
  2. 如果位置i已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value;
  3. 如果位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;

ThreadLocal.get()方法和set方法类似,ThreadLocal首先会获取当前线程对象,然后获取当前线程的TheadLocalMap对象,再以当前ThreadLocal对象作为key,获取对应的value值

public T get() 
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) 
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) 
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        
    
    return setInitialValue();

3.2、ThreadLocal使用场景

  • 线程间数据隔离
  • 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束
  • 进行事务操作,用于存储线程事务信息

例如在Handler中为什么要用到ThreadLocal呢?

ThreadLocal的特性适用于同样的数据类型,不同的线程有不同的备份情况,每个线程都有一个对象,但是不同线程的Looper是不一样的,这个时候就特别适合使用ThreadLocal来存储数据,这也是为什么Handler要使用ThreadLocal存储Looper的原因

3.3、ThreadLocal内泄漏的处理

先看一下ThreadLocal引用链和Entry的实现

static class Entry extends WeakReference<ThreadLocal<?>> 
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) 
        super(k);
        value = v;
    

如上图,Entry中的key通过弱应用指向ThreadLocal对象,value指向传入的Object对象。随着栈中ThreadLocal出栈或者置空时,栈里ThreadLocal断开和堆内存中的ThreadLocal对象的引用关系,ThreadLocal对象变成GC不可达,遇到GC被回收后ThreadLocal置空,置空后key引用的地址是一个空对象那么key也就置空了,但是此时value并未回收

针对value未回收这个问题,如果未主动调用remove(ThreadLocal<?> key)方法置空对应value也不一定会内存泄漏,ThreadLocal提供了保底方案,就是在每次调用get()set()时都会去遍历ThreadLocalMap下key为null的Entry,如果key为null就会对其value进行置空

之所以还会存在内存泄漏,是因为在最坏的情况,也就是没有主动执行remove方法,并且线程没有被回收且长时间存活同时之后也没有再调用get和set方法,那么value指向的Object就会一直存在内存中造成内存泄漏,比如这个线程是线程池中的线程时,所以最好的方案是使用完后主动remove掉不需要了的ThreadLocal对象

四、Handler内存泄漏处理

将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息

private static class MyHandler extends Handler 

    private WeakReference<MainActivity> ref;

    public MyHandler(MainActivity activity) 
        this.ref = new WeakReference(activity);
    

    @Override
    public void handleMessage(final Message msg) 
        MainActivity activity = ref.get();
        if (activity != null) 
            activity.handleMessage(msg);
        
    


@Override
protected void onDestroy() 
    safeHandler.removeCallbacksAndMessages(null);
    super.onDestroy();

onDestroy方法不一定执行,如果IdleHandler比较繁忙的话,那就回在下一次GC的时候回收Handler

以上是关于Handler消息机制和ThreadLocal原理的主要内容,如果未能解决你的问题,请参考以下文章

Android的消息机制之ThreadLocal的工作原理

Android进阶知识——Android的消息机制

Android进阶知识——Android的消息机制

Android进阶知识——Android的消息机制

Android进阶知识——Android的消息机制

深入理解Android Handler机制(深入至native层)