Handler消息机制和ThreadLocal原理
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler消息机制和ThreadLocal原理相关的知识,希望对你有一定的参考价值。
一、简介
Handler作为android线程间通信的常用方式,主要由Handler、Looper、MessageQueue、message四个部分组成 ,
Handler
负责线程间消息通讯的发送消息和接收处理消息Looper
用于从MessageQueue中轮询获得MessageMessageQueue
则是一个单线链表的用于存储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(),是因为一开始在ActivityThread
的main
方法中调用了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.callback
是Runnable
类型的,如通过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;
ThreadLocalMap
是ThreadLocal
的静态内部类,是由一个个Entry组成的类似HashMap的数据结构
Entry
由ThreadLocal
对象和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,过程如下:
- 如果当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上;
- 如果位置i已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value;
- 如果位置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原理的主要内容,如果未能解决你的问题,请参考以下文章