前言
我们都知道由于Android开发规范的限制,我们并不能在子线程中访问UI组件,否则便会触发程序异常崩溃,因此Andorid引入了Handler机制,通过它可以轻松地将一个任务切换到Handler所在的线程中去执行,实现多线程间的消息通信功能。本篇文章旨在通过深入浅析Handler机制的源码来了解它的通信过程,并穿插解惑常见的面试题,好了,让我们开始吧!
Question
1、Handler机制的原理。
2、如何避免实例化Handler造成的内存泄漏问题?
3、MessageQueue是什么时候创建的?主线程有多个Handler时,对应创建多少个MessageQueue?
4、有延时消息处理时,MessageQueue怎么处理Message?
5、Looper的工作原理?
6、ThreadLocal的作用?
7、如何在子线程里使用Handler?
8、Looper为什么不会造成线程阻塞?
Handler
-
Constructor
我们要使用Handler首先要得到一个Handler对象,那么我们就从最简单的Constructor作为入口,来分析它的源码。
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) { // 潜在内存泄漏的检查,默认为false。
//匿名类、内部类或本地类都必须申明为static,否则会警告可能出现内存泄露。
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
// 使用 Looper 的静态方法 myLooper() 来获取当前线程的 Looper
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; //消息队列,来自Looper对象
mCallback = callback; //回调方法
mAsynchronous = async; //设置消息是否为异步处理方式
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
OK,从上面这一小段源码中我们可以发现两个问题。
1、声明Handler
非静态的匿名类、内部类和本地类时,可能会造成内存泄;
在Java中非静态内部类或者匿名内部类都是默认持有外部类的引用的,因此非静态匿名类Handler会默认持有Activity的引用,这样当Activity
销毁时,如果Handler
有任务正在处理未及时释放Activity的引用便会造成内存泄漏。现在我们知道造成内存泄漏的原因,那么我们将Handler
声明为静态内部类并配合SoftReference
或WeakReference
就可以避免内存泄漏,这也解答了Question-2。
private static class NoLeakHandler extends Handler{
private WeakReference<Activity> mActivity;
public NoLeakHandler(Activity activity){
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
2、当前线程没有Looper
或者没有调用Looper.prepare()
时,会触发运行时异常;
为什么在子线程中声明Handler
会触发程序异常,而主线程不会?下面是ActivityThread
的部分源码。
//创建一个消息循环
public static void main(String[] args) {
......
Looper.prepareMainLooper(); //主线程的Looper(),也是app应用主要的消息机制的消息管理者
......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到ActivityThread
调用了Looper.prepareMainLooper()
为我们创建了Looper
,而子线程默认没有创建Looper
,这也是我们在子线程创建Handler
会报错的原因,所以需要我们手动调用Looper.prepare()
创建Looper
,避免触发程序异常。
子线程中创建Handler
的第一种方式:
private Handler mHandler;
//创建子线程handler
private void createHandler(){
new Thread(new Runnable() {
@Override public void run() {
Looper.prepare();
mHandler = new Handler(Looper.myLooper());//传入子线程Looper
Looper.loop();
}
}).start();
}
第二种方式是使用HandlerThread
:
private Handler mHandler;
//创建子线程handler
private void createHandler(){
HandlerThread handlerThread = new HandlerThread("myHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
handlerThread.start();//必须开启
mHandler = new Handler(handlerThread.getLooper());
}
private void postRun(){
mHandler.post(new Runnable() {
@Override public void run() {
SystemClock.sleep(5000);
MainActivity.this.runOnUiThread(new Runnable() {
@Override public void run() {
txt_result.setText("完成操作");
}
});
}
});
}
HandlerThread
内部创建消息队列,外部通过handler
通知HandlerThread
执行,HandlerThread
源码如下:
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
······
}
HandlerThread
其实就是对方式一的封装,并内置了Handler
,使用起来更简单。其中HandlerThread
构造方法的第二个参数表示优先级(范围-20~19,越小优先级越高)
- dispatchMessage()
public void dispatchMessage(Message msg) {
if (msg.callback != null) { // Message的 callback 是一个 Runable 对象
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public interface Callback {
public boolean handleMessage(Message msg);
}
dispatchMessage()
首先检查了Message
的callback
是否为null,不为null就通过handleCallback
来处理消息,其次检查mCallback
是否为null,不为null就调用mCallback
的handleMessage(msg)
处理消息,最后调用Handler
的handleMessage()
来处理消息。
Looper
来看看Looper
的prepare()
和prepareMainLooper()
。
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//每个线程只允许执行一次该方法,第二次执行时线程的TLS已有数据,则会抛出异常。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建Looper对象,并保存到当前线程的TLS区域
sThreadLocal.set(new Looper(quitAllowed));
}
//这个方法是主线程Looper初始化的代码,入口在ActivityThread中,即App初始化时就会被调用
public static void prepareMainLooper() {
prepare(false); //设置不允许退出的Looper
synchronized (Looper.class) {
//将当前的Looper保存为主Looper,每个线程只允许执行一次。
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
可以看到在prepare()
中实例化了Looper
并将其保存在ThreadLocal
中,且每个线程只允许执行一次ThreadLocal
的get()
,也就是说一个Thread只能有一个Looper。而MessageQueue对象是在实例化Looper时创建的的,由此我们可以得出一个结论:一个Thread
只能有一个Looper
和一个MessageQueue
,所以在主线程中不管存在多少个Handler,都只共用一个MessageQueue(这也正好解答了Question-3)。
再来看主线程代码,ActivityThread
不仅调用了prepareMainLooper()
,还调用到了loop()
,那loop()
是干什么的?
public static void loop() {
final Looper me = myLooper();
if (me == null) {
hrow new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue
for (;;) {
Message msg = queue.next(); // 可能阻塞
if (msg == null) { // 没有消息指示消息队列正在退出。
return;
}
msg.target.dispatchMessage(msg); // 调用 Handler 的 dispatchMessage 方法
msg.recycleUnchecked();
}
}
可以看到Looper
相当于是一个消息轮询器,它通过loop()
不断地调用了MessageQueue
的 next
方法来获取新消息,只有next()
返回null,才能终止循环。而 next()
是一个阻塞操作,当没有消息时,next()
会一直阻塞在那里,这也导致了loop()
一直阻塞。如果next()
返回了新消息,Looper 就会调用msg.target.dispatchMessage(msg)
处理这条消息;而msg.target
指的是发送这条消息的Handler
对象,这样 Handler 发送的消息最终又交给了它的 dispatchMessage
方法来处理。
ThreadLocal
上面提到Looper
都保存在了ThreadLocal
中,那ThreadLocal
到底是什么呢?
1.定义
Implements a thread-local storage, that is, a variable for which each thread
has its own value. All threads share the same {@code ThreadLocal} object,
but each sees a different value when accessing it, and changes made by one
thread do not affect the other threads. The implementation supports
{@code null} values.
大致意思是实现了与一个线程相关的存储,即每个线程都有自己独立的变量。所有的线程都共享一个ThreadLocal
对象,并且当一个线程的值发生改变之后,不会影响其他的线程的值。
2.实现
ThreadLocal
的类定义使用了泛型ThreadLocal<T>
,其中T
指的是在线程中存取值的类型。(对应Android中使用的ThreadLocal
, T
为Looper
)
- set方法
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 getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //以当前线程为 key 得到 map 中的 Entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue(); // null;
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); //如果当前线程的 map 已经存在,set 值为 null
else
createMap(t, value);
return value;
}
- ThreadLocalMap
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;
}
}
// map 初始容量 16,必须为 2 的幂
private static final int INITIAL_CAPACITY = 16
private Entry[] table; // Entry表,大小必须为2的幂
private int size = 0;
private int threshold; //下次需要扩容的阈值,默认 0
private void setThreshold(int len) {
threshold = len * 2 / 3; // 2/3 的负载因子
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化 table,大小 16
table = new Entry[INITIAL_CAPACITY];
//用第一个健的哈希值对初始大小取模得到索引,和 HashMap 的位运算代替取模原理一样
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue); //初始化 Entry
size = 1; //第一个值进入 table,table 大小置 1
setThreshold(INITIAL_CAPACITY); //设置阈值
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1); //拿到 table 索引
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e); //线性探测继续寻找
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;]
if (k == null)
expungeStaleEntry(i); //如果拿到一个 null 的 key 说明已经被回收了,需要清理
else
i = nextIndex(i, len); //否则的话说明需要继续寻找
e = tab[i];
}
return null;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
tab[staleSlot].value = null; // 将 table[i] 这个引用和 value 置 null
tab[staleSlot] = null;
size--; //table 大小减一
Entry e;
int i;
// 遍历清理
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//遇到还可以清理的话顺便清理掉
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//遇到还没被回收的,rehash 找到新的为空的索引位置
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null; // 将原位置置 null
while (tab[h] != null) // 找到新位置
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
}
3.ThreadLocal的工作原理
ThreadLocal
通过获取当前线程中的ThreadLocalMap
,从而实现每个单独线程的信息绑定。Android的消息机制中,Looper
便是采用ThreadLocal
作为存储结构,所以Looper
对象的存储只会在当前线程中,子线程若是使用消息机制的话,必须调用Looper.prepare()
来在线程中新建一个Looper
对象(Question-6)。
消息分发
- 消息发送
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public final boolean postAtTime(Runnable r, long uptimeMillis){
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public boolean sendMessageAtTime(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(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到Message
最终会被发送到MessageQueue
的enqueueMessage()
中去,并指定msg.target
为当前发送Message的Handler
,由此可知MessageQueue
是用来存放Message
的消息队列。
- 消息回收
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
}
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
实质上都是调用的MessageQueue
的方法来达到目的的
MessageQueue
MessageQueue
是消息机制的Java层和C++层的连接纽带,大部分核心方法都交给native层来处理,其中比较重要两个方法。
private native void nativePollOnce(long ptr, int timeoutMillis); //阻塞loop循环
private native static void nativeWake(long ptr); //唤醒loop循环
- Constructor
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
接收一个参数quitAllowed
,决定当前队列是否允许被终止。同时调用 一个native方法,初始化了一个long类型的变量mPtr
。
- enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
f (msg.target == null) { // msg.target 为 Handler
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
从enqueueMessage
的实现来看,尽管Messagequeue
叫做消息队列,但是它的内部实现确实通过一个单链表的数据结构来维护消息列表的,这是因为单链表在插入和删除上比较有优势。
解惑Question-4:
我们再来看下新消息需要放到队头的情况:p == null || when == 0 || when < p.when
。即队列为空,或者新消息需要立即处理,或者新消息处理的事件比队头消息更早被处理。这时只要让新消息的next指向当前队头,让mMessages指向新消息即可完成插入操作。也就是说MessageQueue的先进先出原则指的并不是插入的顺序,而是Message执行时间的顺序。
-
next()
相信大家一定没有忘了next()
是在Looper.loop()
中调用的吧,现在我们来看一下它的源码。
Message next() {
·····
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
·····
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
·····
}
·····
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
可以发现next()
是一个无限循环的方法,如果消息队列中没有消息,那么next()
会一直阻塞在这里。当有新消息来临时,next()
会返回这条消息并将其从单链表中移除。
大致思路如下:先获取第一个同步的message
。如果它的when
不晚与当前时间,就返回这个message
;否则计算当前时间到它的when
还有多久并保存到nextPollTimeMills
中,然后调用nativePollOnce()
来延时唤醒,唤醒之后再照上面那样取message
,如此循环。代码中大部分都是对链表的指针操作,其他的逻辑很清楚,就不一句句分析了。
-
removeMessages()
该方法有2个重载,这里以void removeMessages(Handler h, int what, Object object){}
方法为例。
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h && p.what == what
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
首先是Handler
的非空判断,然后便是同步代码块,里面只有两个while循环。为什么有两个呢?这是因为在数据结构中链表分两种:带头结点和不带头结点。而这两种链表的遍历方式有所不同:不带头结点的链表中,第一个元素需要单独处理,然后才能将后续部分当做带头结点的链表来使用while循环遍历。可以看出MessageQueue
是不带头结点的链表,而且遍历过程中有需要删除节点,因此要特殊处理的不只是第一个元素,而是第一组符合删除条件的元素。
- quit()
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked(); //清空延迟消息
} else {
removeAllMessagesLocked(); //清空所有消息
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
可以看到quit()
最终会调用到removeAllFutureMessagesLocked()
或者removeAllMessagesLocked()
两个方法,我们再来看下这两个方法的实现。
- removeAllFutureMessagesLocked() & removeAllMessagesLocked()
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) { //如果队头时间比当前时间大,则说明所有时间都是延迟的,那么全部删除
removeAllMessagesLocked();
} else {
Message n;
for (;;) { //找出大于当前时间的那个节点
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
//删除后部分节点
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
这段代码比较清晰,如果队头时延时消息时则调用removeAllMessagesLocked
清空所有的Message
,如果没有则继续遍历下一个节点,直到找到执行时间大于当前时间的那个节点,再将含这个节点之后的所有节点删除。
Question:Looper.loop()
为什么不阻塞主线程?
还记得下面这两个方法吗?
private native void nativePollOnce(long ptr, int timeoutMillis); //阻塞loop循环
private native static void nativeWake(long ptr); //唤醒loop循环
Looper
通过调用MessageQueue.next()
取消息时,如果已经没有消息了,此时该线程便会阻塞在next()
的nativePollOnce()
中,主线程便会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生时,才通过调用nativeWake()
往pipe管道写端写入数据来唤醒主线程工作。这里涉及到的是Linux
的pipe/epoll
机制,epoll
机制是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。
参考资料 Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
Handler机制的原理
Andriod提供了Handler
和Looper
来满足线程间的通信。Looper
类用来管理特定线程内对象之间的消息交换。
1、Looper
: 一个线程可以产生一个Looper
对象,由它来管理此线程里的Message Queue
。
2、Handler
: 可以构造Handler
对象来与Looper
沟通,以便push
新消息到MessageQueue
里;或者接收Looper
从Message Queue
取出来的消息。
3、MessageQueue
:用来存放Message
,按照Message
执行时间的顺序对Message
进行排列。
4、线程:UI thread
通常就是ActivityThread
,而Android启动程序时会替它建立一个MessageQueue
。
网友评论