我是靠谱客的博主 时尚啤酒,最近开发中收集的这篇文章主要介绍Android消息机制、Handler工作原理和十万个为什么之为什么postDelayed可以延时,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Android消息机制概述

Android的消息机制主要是指Handler的运行机制以及支撑Handler工作的MessageQueue和Looper的工作过程。Handler主要作用是将一个任务切换到某个指定的线程中执行。由于在Android3.0之后规定访问UI只能在主线程中进行,所以Handler在此有了用武之地。为什么不再允许在子线程中访问UI呢?因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能导致UI控件处于不可预期的状态,为什么不对UI控件的访问加上锁机制呢?缺点有两个:首先加锁机制会让UI访问的逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻某些线程的执行,有可能导致程序ANR。

MessageQueue就是消息队列,它的内部存储的是一组消息,采用单链表的数据结构来存储消息列表,对外提供插入和删除工作。Looper是消息循环,由于MessageQueue只是一个消息的存储单元,它不能处理消息,Looper就是用来填补这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有就处理,没有就一直等待。在使用Handler的时候必须为线程创建Looper,否则就会报错,但是在UI线程中使用Handler不需要创建Looper,这是因为UI线程被创建的时候就已经初始化了Looper,因此在子线程中使用Handler必须要创建Looper。

Handler创建完毕后,此时其内部的Looper以及MessageQueue就可以和Handler一起协同工作,然后通过Handler的post方法将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法发送一个消息,,这个消息同样会在Looper中处理,post方法最终也是通过send方法完成的。当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就会被调用,注意:Looper运行在创建Handler所在的线程中,这样一来Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了。过程如下图:

Android消息机制分析

Android消息机制分析将围绕ThreadLocal、Handler、MessageQueue和Looper的工作原理来讨论。

ThreadLocal

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后只有在制定线程中可以获取到存储的数据,对其他线程来说则无法获取到数据。这个东西有点像打游戏的分区,比如打王者荣耀,我在11区有一个武则天,在14区有一个芈月,但是在11区我无法使用14区的芈月,在这里玩家就像是ThreadLocal,不同的区就是不同的线程。

ThreadLocal的使用场景:

第一:当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal,比如对Handler来说,它需要获取当前线程的Looper,Looper的作用于就是线程并且在不同线程具有不同的Looper,这时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。

第二:在复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这时候就可以采用ThreadLocal。

下面用一个例子来有演示下ThreadLocal:

 private ThreadLocal<Boolean> mBooleanThreadLocal=new ThreadLocal<>();
mBooleanThreadLocal.set(true);
Log.v(TAG,"[Thread#main]mBooleanThreadLocal="+mBooleanThreadLocal.get());
new Thread("Thread#1"){
@Override
public void run() {
super.run();
mBooleanThreadLocal.set(false);
Log.v(TAG,"[Thread#1]mBooleanThreadLocal="+mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread#2"){
@Override
public void run() {
super.run();
//
Looper.prepare();
//
Handler handler=new Handler();
//
Looper.loop();
Log.v(TAG,"[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.get());
}
}.start();

最终运行结果:

从上面就可以看出在主线程、线程1、线程2中ThreadLocal的get方法分别是不同的值。

ThreadLocal工作原理

首先看Thread的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);
}

 

在Android24之前Thread的set方法如下:


public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}

其实实现原理基本一致。

在Android24之前会通过values方法来获取当前线程中的ThreadLocal数据,如何获取呢?其实获取的方式也是很简单的,在Thread类的内部有一个成员专门用于存储线程的ThreadLocal的数据:ThreadLocal.Values localValues ,因此获取当前线程的ThreadLocal数据就变得异常简单了。如果localValues的值为null,那么就需要对其进行初始化,初始化后再将ThreadLocal的值进行存储。”下面看一下ThreadLocal的值到底是如何在localValues 中进行存储的。在localValues 内部有一个数组:private Object[] table ,ThreadL ocal的值就存在在这个table数组中。下 面看一下localValues是如何使用put 方法将ThreadLocal的值存储到table数组中的,如下所示。


void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}

通过上述代码,我们可以带出一个存储规则, 那就是ThreadLocal的值在table 数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下-一个位置,比如ThreadLocal的reference 对象在table数组中的索引为index, 那么ThreadLocal 的值在table 数组中的索引就是index+1。 最终ThreadLocal的值将会被存储在table数组中: table[index + 1]= value。

在Android24之后,是通过ThreadLocalMap来存储线程以及线程对应的值,ThreadLocalMap是ThreadLocal的一个内部类,实现如下:


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
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
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);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
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 void set(ThreadLocal<?> key, Object value) {
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();
}
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
……
}

存储方式有点类似Map,其核心也是通过线程以及存储的值对应来存放数据。因此在不同线程中访问同一个ThreadLocal的set和get方法,它们对ThreadLocal 所做的读/写操作仅限于各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不千扰地存储和修改数据。

MessageQueue

MessageQueue主要包含两个操作:插入和读取,读取本身会伴随删除操作,插入读取对应的方法分别是:enqueueMessage和next。MessageQueue内部不是队列而是通过一个单链表的数据结构来维护消息列表。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 {
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;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

next代码如下:


Message next() {
……
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;
}
……
}

next方法是一个无限循环的方法,如果消息队列中没有消息,那么next就会一直阻塞在这里,当有新消息来时,next方法会返回这条消息并将其从单链表中移除。

Looper

Looper在Android的消息机制中扮演者消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就立即处理,否则就一直阻塞在那里。通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环,如下所示:


new Thread("Thread#2"){
@Override
public void run() {
super.run();
Looper.prepare();
Handler handler=new Handler();
Looper.loop();
Log.v(TAG,"[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.get());
}
}.start();

loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null。 当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。也就是说,Looper必须退出,否则loop方法就会无限循环下去。loop 方法会调用MessageQueue的next方法来获取新消息,而next是一个阻塞操作,当没有消息时,next 方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。但是这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。

Handler工作原理

Handler的工作主要包含消息的发送和接收过程,消息发送可以通过post的一些列方法以及send的一系列方法来山西爱你,post的一系列方法最终是通过send的一系列方法来实现的:


public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, 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);
}

可以发现,Handler发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper, Looper 收到消息后就开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段。dispatchMessage 的实现如下所示:


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

最后用一张图来说明Handler处理消息的过程:

补充:

曾经有个同学去去面试,被问到MessageQueue遵循FIFO(先入先出),为什么postDelayed可以延时?

下面从源码的角度来分析下这问题。

首先我们做一个测试,看这个延时的消息是否被添加到MessageQueue里面去:


private void testPostDelayed(){
boolean result=new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.v(TAG,"延迟1秒执行");
}
}, 1000);
Log.v(TAG,"result="+result);
}

运行后结果如下:

结果证明两个问题:1.延时消息第一时间被加入了消息队列,2.postDelayed方法没有BUG(手动滑稽)

我们通过源码来分析postDelayed的流程:

经过N个方法最终还是到了MessageQueue中的enqueueMessage,上面几个方法都没什么好说的,主要看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是我们从postDelayed一路带下来的,那么延时相关的核心代码是:


if (p == null || when == 0 || when < p.when) {
// 当消息队列中没有消息,或者是按时间来说是该第一个第一个触发的
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 插入到队列中间,经常我们不需要唤醒事件队列,除非队列的
// 头部有一个障碍或者消息是该队列最早的异步消息
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;
}

通过这段代码可以看出,如果通过postDelayed提交一个消息A到消息队列,由于此时会执行MessageQueue的enqueueMessage,由于此时when>0,Looper阻塞,等待下一个消息或者是等Delayed时间结束,自动唤醒;在上述前提下,如果此时Handler有发送一个消息B到消息队列,但此时消息A还在梦游,此时会把B插入到A的前面,然后调用nativeWake()方法唤醒线程,唤醒之后会会重新读取队列,此时B在A前面,因此不需要等待,于是直接返回给Looper;Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间继续阻塞,直到阻塞时间到或者下一次有Message进队。

从上面的分析我们可以清晰的知道postDelayed的工作原理,下次再有人问题来的时候也不至于答不上来。

最后说一下,那个同学就是我……

参考资料:

《Android开发艺术探索》

最后

以上就是时尚啤酒为你收集整理的Android消息机制、Handler工作原理和十万个为什么之为什么postDelayed可以延时的全部内容,希望文章能够帮你解决Android消息机制、Handler工作原理和十万个为什么之为什么postDelayed可以延时所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部