您的位置:首页 > 产品设计 > UI/UE

Android消息机制(Handler,Looper,MessageQueue)-源码分析

2017-02-23 15:48 369 查看

Android-消息机制(Handler,Looper,MessageQueue)源码分析

说到消息机制,Android开发者应该都不陌生。在Android开发过程中,需要使用消息传递的场景到处可见,如在子线程更新UI之类常见的操作都离不开消息传递。本篇文章主要从Handler 、Looper以及MessageQueue的运行机制及其源码方面对Android的消息机制进行讲解。

0

Android为了UI组件的访问效率考虑,将UI组件实现为非线程安全的,因此Android不允许在非UI线程中操纵UI组件。但是,当我们需要从网络拉取数据、执行IO操作或者执行一些耗时运算的时候,我们必须将这些任务放到子线程来完成,然而在子线程完成任务后无法操纵UI来与用户交互,给开发者带来了很多麻烦。因此,Android提供了消息机制,通过消息机制我们能很方便的切换任务的执行环境,也即很好的解决了这个问题。

下面是Handler、Looper以及MessageQueue三者之间的关系。在消息传递过程中,首先Handler将消息放到MessageQueue中,然后Looper在死循环中依次从MessageQueue读取消息,并在Looper所在的线程环境中执行相应任务。

Handler

Handler是Android消息机制的上层接口,通过Handler我们能将Message发送给Looper去处理,也即将指定的任务切换到指定的线程环境中去执行。

首先看一下Handler的两个主要构造方法:

public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
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());
}
}

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}


Handler的两个主要构造方法中一个不需要Looper对象(使用线程默认Looper,下面会详解),另一个则需要一个Looper对象,而Handler发送的消息就在该Looper中进行处理。

因此,在一个Handler创建的时候,会获取与之关联的Looper,以及该Looper内的MessageQueue。

Handler的post方法(post发送对Runnable对象会被包装到Message的callback)以及send方法最终都是通过调用sendMessageAtTime(Message msg, long uptimeMills)方法实现的,该方法源码如下:

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


可以看到,该方法只是简单的调用enqueueMessage方法,该方法如下:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}


而enqueueMessage方法里又简单的调用了MessageQueue的enqueueMessage方法,该方法将Message插队到MessageQueue的队列里(其实是单链表实现),下面会详细分析。

再看一下Handler的dispatchMessage方法,当Looper处理Message时,会将Message发送给Handler的该方法来处理:

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


该方法的处理流程为:首先看该Message是不是一个Runnable(也即通过post方法发送的,Runnable被包装到callback),如果是,则在handlerCallback方法中执行该Runnable;如果不是则看该Handler的Callback是否被设置,设置了callback的话就交给callback处理;如果callback没有成功处理,也即返回了false,或者没有设置callback,那么该Message交给Handler的handlerMessage方法(也即一般在我们创建Handler时覆盖的方法)处理。

由上可见,Handler的主要工作就是发送Message到MessageQueue,那么,消息又是怎么被Looper处理的呢?

Looper

我们知道,UI线程在初始化的时候,会创建一个Looper,该Looper负责处理外部事件,因此,我们在主线程中使用Handler时不用在创建Looper。但是,在子线程中,我们必须先使用Looper.prepare()来创建一个Looper,然后调用Looper.loop()来开启消息循环,之后才能正常的使用Handler传递消息。Looper的构造方法是私有的,整个Looper类只有三百行,下面主要看一下prepare、myLooper以及loop方法:

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


首先,看Looper的prepare方法,该方法会创建一个Looper(创建Looper的时候会创建MessageQueue),并将其放入到ThreadLocal中(一种线程内数据的存储对象,后面会详解)。如果你在主线程调用Looper.prepare()将会得到RuntimeException:“Only one Looper may be created per thread”。

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


myLooper方法会返回当前线程的Looper对象,该对象在调用prepare方法时被放在ThreadLocal中,使用Handler类中无Looper的构造方法创建Handler时会使用此方法获取线程内的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();

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;
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}

if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}

msg.recycleUnchecked();
}
}


loop方法的代码比较长,但不难看出,loop方法中开启了一个死循环,在循环中依次读取Message,然后调用
msg.target.dispatchMessage(msg)
方法,msg.target为发送该Message的Handler,也即调用了Handler的dispatchMessage方法。在介绍Handler时已经说过Handler处理Message的流程, 此处不在赘述。

值得一提的是,loop中的死循环跳出的唯一条件是queue.next()返回null,而queue.next()返回null表示该MessageQueue正在退出,也即该Looper正在退出。此外,如果MessageQueue中没有消息,则它的next方法会产生阻塞,直到有消息可读。

通过这样,不管Handler在哪个线程环境,Handler都能将Message发送给关联的Looper,存储到Looper中的MessageQueue中, 然后Looper在自己被创建的线程环境中,通过死循环方法loop来从MessageQueue中读取消息,然后使用Handler规定的处理方式来处理消息。

到这,大家应该理解了Handler的工作机制了。但是,在Handler与Looper之间,MessageQueue发挥着重要的作用,下面再看一下Message Queue。

MessageQueue

MessageQueue是Handler发送的消息的目的地,是Looper读取消息的来源。MessageQueue中的Message以单链表结构组织在一块(Message对象本身具有next数据域,正是通过此数据域来组织成单链表)。下面主要看一下enqueueMessage方法以及next方法。enqueueMessage方法将一个Message插入到单链表中,next方法返回并删除一个Message。

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


首先看一下enqueueMessage方法, 该方法比较长,但逻辑很简单。首先检查Message的完整性,然后判断MessageQueue是否正在退出,是的话就抛出异常。没问题的话,就将该Message插入到单链表中(按照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;
}

// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run.  Loop and wait some more.
mBlocked = true;
continue;
}

if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = m
c2e5
IdleHandlers.toArray(mPendingIdleHandlers);
}

// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}

if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}

// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;

// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}


该方法也比较长,但是主要逻辑很清楚,首先,使用一个native方法nativePollOnce来阻塞获取消息,然后判断消息是否是ready状态,也即
now < msg.when
是否成立,然后返回这个Message,或者执行IdlerHandler(如果有的话,可以通过MessageQueue的addIdleHandler方法添加)。

通过上面MessageQueue的两个主要方法,我们知道了MessageQueue的工作机制(核心工作是在native方法中完成,此处不再分析),也即通过enqueueMessage将Message插入到链表中,然后使用next方法依次从链表中读取并删除消息,最后消息交给Looper来处理。

ThreadLocal

分析完Handler、Looper以及MessageQueue的工作机制,大家应该对Android的消息传递有了一些了解,但是不知道大家有没有注意到一个问题,那就是,当我们使用Handler的没有Looper参数的那个构造方法构造对象时,Handler与哪个Looper关联呢?对,是与当前线程的Looper对象关联, 也即通过
Looper.myLooper()
来获取到的当前线程的Looper对象。那么问题来了,myLooper是一个静态方法,也就是说在不同的线程环境里调用myLooper方法,myLooper方法内操纵的都是同一个(或同多个)静态数据域,那么是怎么做到在不同的线程环境里调用myLooper方法返回线程相关的Looper对象呢?答案是ThreadLocal。

首先看一下在Looper中出现过的get和set 方法(在Looper的prepare方法中会使用set方法,在myLooper中会使用get方法)。

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


这是set方法,首先获取当前的线程对象啊,然后使用getMap方法获取当前线程对象中的ThreadLocalMap对象,该对象是Thread对象的局部变量,也即任何一个线程中都会有ThreadLocalMap。然后使用ThreadLocalMap的set方法来存储value(下面详解)。

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}


然后再看一下get方法,和set方法类似,get 方法也先取得当前线程对象的ThreadLocalMap对象,然后使用map.getEntry方法来获取一个Entry,Entry的Value属性即为最终结果。

现在,已经可以很清晰的知道get、set方法是如何工作了:首先获取当前线程的ThreadLocalMap,然后通过该对象存取数据。ThreadLocalMap是ThreadLocal的内部类,现在看一下ThreadLocalMap的set方法以及getEntry方法:

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


Entry[]为ThreadLocalMap中存储数据的数组,Entry存储了ThreadLocal的弱引用以及与该ThreadLocal相对应的Object。set函数通过hash映射将该key对应的value放到Entry数组中。

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


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);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}


getEntry函数同样通过hash映射来取得相应的Entry,遇到冲突则使用getEntryAfterMiss函数顺次查找。查找不到指定数据则返回null。

总的来说,ThreadLocal其实是将自身作为key,存储的对象作为value,然后将这个key-value(也即Entry)放到ThreadLocalMap中,而ThreadLocalMap内部通过对key进行hash映射来将value放到数组的指定位置。这样,由于每个Thread中都有一个自己的ThreadLocalMap,而ThreadLocal做为存储的key,因此同一个ThreadLocal对象在不同的Thread中能关联不同的value。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐