Handler的用处
我们都知道Android是禁止在非主线程更新UI的,其主要原因是UI控件并不是线程安全的,如果多线程访问UI,将会出现非常多的问题。你可能会想到使用sychronized给UI控件加锁,但是加锁会带来两个问题,一方面影响运行效率,另一方面会使得UI访问逻辑变得很复杂。为了避免这些问题,所以才采用了单线程更新UI的方式。那么当我们正处于子线程时,如何能够更新UI呢?这时就可以使用Handler来进行线程的切换了,这也是我们最常用的方法。
与Handler相关的主要成员功能
- ThreadLocal 用于存储不同线程中的Looper,是每一个线程,都已唯一与之对应的Looper。
- Looper 主要用于从MessageQueue中取出消息,并交给handler处理消息。
- MessageQueue 用于以队列的方式存储取出消息,实质是链表的形式。
- Handler 用于发送信息以及接受信息,并对信息进行处理。
下面我将会结合源码对以上四个方面进行讲述
ThreadLocal
TreadLocal是一个数据存储类,数据存储后,只有在指定的线程中才能取出存储的数据,对于其它线程则无法获得数据。ThreadLocal使得每一个线程中都能与当前线程相对应的数据,且每个线程对应的数据互不干扰。
我们首先来看一下它的set方法。
//ThreadLocal
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看到set方法首先会尝试通过thread获得TreadLocalMap类,这个类是TreadLocal的静态内部类
static class ThreadLocalMap {
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
可以看到里面是有一个Entry数组,TreadLocal中的数据就是存在这里的,顺便一提这里的WeakReference指的是弱引用方式,这种引用的特殊之处在于,当存储的数据没有强引用时,如果GC执行回收时,就会自动销毁数据,从而防止了内存泄漏,这里就不多说了。
然后在每一个Thread中都有着一个TreadLocalMap的成员变量,getMap方法就是获得所给Thread中的TreadLocalMap变量。接着是判断返回对象map是否为null对象,如果不是,就初始化Tread中的TreadLocalMap对象,如果是,就用自身作为key以及value值来创建Entry,存入相应的Thread中,具体存入过程如下
//ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
可以看到大概逻辑就是简单的更新数据。这里就不多说了。
接着我们再来看下ThreadLocal中的get方法
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();
}
首先根据所在线程取出当前的map,然后查找map中存入的数据并返回,由于之前存的时候存入了TreadLocal对象的值,在取的时候就可直接把TreadLocal对象作为key进行查找。getEntry的代码如下
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
整体是很简单的,通过key值在Thread中的TreadLocalMap对象中查找数据。
最后总结一下过程,ThreadLocal通过当前的线程来获取相应线程中存储数据的TreadLocalMap,TreadLocalMap就是真正的存储数据的对象,通过对他进行修改,获取,就实现了TreadLocal在不同线程中拥有不同的数据的功能。
MessageQueue
MessageQueue用于以队列的形式存储消息,即先进先出,但实质上它是以链表的形式实现的。其中比较重要的方法有enqueueMessage、next,分别用于消息的添加和取出。
首先我们来看一下enqueueMessage方法:
boolean enqueueMessage(Message msg, long when) {
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.");
}
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;
}
总体逻辑就是根据所给时间when将消息添加到链表的适当位置,添加链表的逻辑相信大多数人都是会的,就不多说了,重点是next方法。
Message next() {
// 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
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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 {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
......
}
这里省略了一些不太重要的代码,可以看出当MessageQueue中没有消息即代码中的msg为null时并且mQuitting为false时,这个方法将无限循环下去,不断的读取是否有消息的加入,阻塞线程。当消息队列中有消息时,就会读取消息返回,并且删除该消息。
Looper
Looper在流程中类似于一个中间商,不断的从message中获取消息,然后传递给handler进行处理。每一个线程只会有一个Looper,我们很容易想到它是通过TreadLocal实现的
在它的内部存在着ThreadLocal的静态对象
//looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
通过ThreadLocal我们就可以使的每一个线程中的Looper的值独立,不会相互干扰。我们在子线程中使用Looper时,必须先调用prepare方法创建当前线程的Looper
//looper
public static void prepare() {
prepare(true);
}
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,否则会报错。在MainThread中即我们更新UI的线程中,在创建线程时系统便会自动调用prepareMainLooper()为主线程创建Looper
//looper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
在Looper创建的同时, 会创建MessageQueue
//looper
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
也就是说一个Looper对应一个MessageQueue。创建完looper后Looper还并不能开始从消息队列读取消息。只有调用了Loop函数才行:
//looper
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
}
}
同样这里我们只列出了比较关键的代码。在Loop中,会调用MessageQueue的next方法来获取消息,同时调用handler的dispatchMessage方法处理消息,只有当msg==null时才会结束循环,否则如果消息队列中没有信息时,next()方法会堵塞,无法返回值,这就使得Loop也被堵塞在那里。如果需要结束Looper可以调用Looper中的quit和quitSafety方法,
这两个方法的区别是,quit将直接结束读取消息,而quitSafety会先将消息队列中的消息处理完毕,在结束读取消息。这两个方法都会调用MessageQueue的quit方法:
//MessageQueue
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);
}
}
这里会改变mQuitting的值为true。这个值我们在MessageQueue的next就见过:
//MessageQueue中的next()
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
返回null之后就能使Looper结束循环,使得方法执行完毕。
Handler
Handler用于发送消息并且处理消息。发送消息可以通过post和send的一系列方法实现,比如:
//handler
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
很容易看出发送信息的过程,实质上就是设置消息的target, 再把消息加入到消息队列中。此后Looper就会通过MessageQueue的next()读取消息,并且读取 成功后调用handler的dispatchMessage方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里面msg.callback是一个Runnable对象,可以在生成信息时设置。mCallback则是Handler中的一个成员变量,具体如下:
public interface Callback {
public boolean handleMessage(Message msg);
}
紧接着handleeMessage是Handler中的一个空方法。通过源码我们很容易知道,我们设置handler如何处理消息时有三种方法:
- 在消息中设置callback参数
- 在Handler的构造函数中传入Callback的实现
- 重写Handler中的handleMessage方法
以上三种方式的优先级依次减弱。
码字不易,谢谢拜读。