本来在看WMS,但是中途发现Android消息机制有点不明白,于是记录下学习过程。本文主要讲解java层的消息机制,native层的还未设计,后期也会尝试深入学习下,坑先挖起来,后面有能力就埋,没能力就先等着。
Android消息机制分为三个部分:1、Looper;2、Message;3、Handler
1、 Looper:
Looper顾名思义就是一个循环,其实所有的UI线程都是在一个死循环中一直在等待事件,并通过事件来触发操作。事件可以简单的理解为触摸、键盘、中断等等;这里的入口,就选择在ActivityThread.java的main函数,
public static void main(String[] args) {
Looper.prepareMainLooper();//(1)创建Looper
………………………………
//实例化ActivityThread
ActivityThread thread = new ActivityThread();
//初始化并且通知AMS,startSeq为启动序列号,AMS会为每一个
//待启动的进程生成一个唯一标志
thread.attach(false, startSeq);
//初始化主线程的Handler,其实主线程就是一个一直在等待消息的循环体,
//所以必须初始化Handler,保证其收到消息后执行该做的任务
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Looper.loop();
//主线程理论上不会出上面的循环,执行到这里肯定是出错了
throw new RuntimeException("Main thread loop unexpectedly exited");
}
a)、创建Looper
首先看(1)处,如何创建一个Looper,从代码中可以看到,prepare Looper的时候会先调用sThreadLocal.get()去获取Looper,如果能获取到就直接抛出异常。这里就可以看出,一个线程其实只能有一个Looper,如果没获取到,则会创建一个新Looper并放入sThreadLoacl中。
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) {//(2)
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));(4)
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
b)、ThreadLocal类及其原理(这块不看也不是很影响消息机制的理解,可直接跳到c模块)
这块内容比较多,先从(2)处的sThreadLoacal说起,看定义可知这是一个ThreadLocal类型的变量,即一个泛型为Looper的ThreadLocal变量,查找ThreadLocal.java文件,查看定义。
static final ThreadLocal sThreadLocal = new ThreadLocal();
在文件中发现get()方法的定义如下:
public T get() {
Thread t = Thread.currentThread();//获取当前线程
//通过当前线程获取一个ThreadLocalMap类型变量map
ThreadLocalMap map = getMap(t); //(3)
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
于是又发现了新的类,ThreadLocalMap,这里真的是一环套一环,东西特别多,我也不一定能说清楚,大家能看懂更好,我尽力说一下我的理解吧。 首先ThreadLocalMap底层实现其实是通过一个名为table的数组实现的(初始大小为16),table数组中的每个元素是Entry类型。这里差插一个图和代码以便理解:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
//通过hash取得新添加的Entry的序号
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
其中Entry类型的定义如下所示:Entry相当于map中的一对数据,key-value一对的,如下定义也可看到,Entry中的key就是ThreadLocal类型变量,value是后面说的,通过ThreadLocal.set()方法设置的一个数据类型,这边直接用Object类,所以他可以是任何类型。不过这里Entry的实现可不仅仅是简单的key-value模式,这里深挖代码还会关联到引用相关,这里先不细说。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
所以回到上面的(3)处,getMap()方法获取到的什么呢?
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null;
通过定义看到返回的是t.threadLocals,再去Thread.java中查看threadLocals的定义,发现就是ThreadLocalMap类型,这里也可以看出,每个Thread都会有一个ThreadLocalMap类型的属性。于是(3)处的逻辑就清晰了。就是返回当前线程t的ThreadLocalMap属性。然后再于ThreadLocalMap中查找key为sThreadLocal的Entry,这里因为泛型为Looper,所以value的类型就是Looper,返回的result就是Looper类型变量。
从这也可以看出,ThreadLocal为变量在每个线程中创建了一个副本,不同的线程之间threadLocal变量是不共享的。
回到(2)处,即如果当前线程已经有Looper就会抛出异常,若无就进入到(4)处,新建Looper,并将其放入ThreadLocalMap中。这里再看下set方法,毕竟有get必定会有set,不让我们获取什么呢?
public void set(T value) {
//下面两行不再赘述,获取当前线程的ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
查询到ThreadLocalMap后,将当前的sThreadLocal和Looper作为Entry放入其中。
c)、Looper的创建:
Looper的创建很简单,首先新建一个MessageQueue,然后把Looper和当前的线程进行关联。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
所以这里我们就可以看到Message和Looper的关联了,MessageQueue简单点说就是维护了一个Message类型的队列,而Looper就是一直在循环检查MessageQueue中是否有新消息,一旦有新消息就把他拿出来进行处理。
d)、Looper在干嘛:
public static void loop() {
//获取当前线程的ThreadLocalMap中以sThreadLocal为key的Looper对象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
me.mInLoop = true;
……………………………………………………………
for (;;) {//这里可以看出来是个死循环,只有当if里为true才会退出
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;//退出的一种情况
}
try {
msg.target.dispatchMessage(msg);//根据消息的目标分发消息
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
}
}
msg.recycleUnchecked();//消息放回消息池中
return true;
}
如上,loop方法的核心功能就是向消息的target分发消息,这里先不看如何分发,因为handler中会讲到。
所以综上,其实主线程就是一个死循环,因为他会一直阻塞在Looper.loop方法中,除非碰到异常才会退出,不过也会有小伙伴会问,那既然主线程一直在死循环,那怎么响应点击事件呢? 这个问题就涉及到epoll机制了,我暂时还没看,有机会再说吧。总之epoll机制就是能有一种监听的功能,当有事件发生的时候,就会去处理事件,没事件发生时就会一直在监听我们添加到监听列表里的各种事件,就比如是触摸事件,这个功能好比一个保安,当有坏人、可疑人员、发生什么袭击事件,他就会报警或者关上大门保护公司里的安全。
2、 Message:
a)、消息的基础属性和方法:
在Looper中已经讲到了Looper和Message之间的关系,再看看Message有啥。
首先 从属性开始看起,最常见的几个属性:
public int what; //一般根据what判断handler执行的功能
public int arg1;//参数之一不知道啥用处
public Object obj; //可以传入很多东西,因为类型是object
public long when; //消息的处理时间
Handler target; //消息的目标handler
Runnable callback; //消息可以是一个回调函数
Message next;//消息链表中的下一个消息
Message sPool; //消息池
下面再看下消息的创建,这是默认创建函数,是从消息池中获取一个消息,用消息池的好处就是避免了消息的频繁创建与销毁,将消息池的顶部Message放回,并将容量减1;若无消息池,则直接新建一个消息,这里的构建函数各种各样,传参的构建函数就会将参数中的一些值赋值给我们新创建的消息了。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
其中recycleUnchecked()方法就是将消息的信息都清空,然后返回消息池中。
这里要说的一个属性就是Target,Target属性是消息的目标,理解的含义就是消息需要分发给谁,后面可以看到,target属性会在sendMessage的时候赋值。
b)、同步消息和异步消息:
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
可通过setAsynchronous()方法给消息设置消息是否是同步消息。而将消息设置为同步消息或者异步消息。 这个名字也很直接,消息分为两类,同步消息和异步消息,同步消息和异步消息一般情况下没有区别,只有特定的场景才会有差别。
当开启了同步屏障后就会让异步消息优先被处理,同步消息则只能在消息队列中继续等着,直到移除同步屏障才能继续得到处理。
消息其实分为三类:同步消息、异步消息、屏障消息。前两者的区别已经说了,屏障消息的不同之处就是消息的Target为null,下面看下发送同步屏障的方法postSyncBarrier(long when)是怎么实现的吧:
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
//屏障不是只有一个,会有好多个,那怎么区分呢?就靠这个唯一的标志token
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
//MessageQueue的顺序是按照时间来的,这里就按照时间把屏障消息插入
//MessageQueue当中。
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
//返回屏障消息的唯一标志tokne,用于后面关闭屏障消息
return token;
}
}
//下面就是移除屏障消息了,可以看懂这里传入的参数token就是
//前面创建的时候返回的唯一标志
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
//往下寻找对应token的屏障消息
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
//移除屏障消息
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
上面的屏障消息的入队出队流程,其实和正常消息的流程也差不多。都是根据消息的when属性进行排序,然后出队的话就是根据消息的一些标志去找到相应的消息,然后移出MessageQueue。
c)、消息队列的next方法:
我们在处理完Message后,是不是任务就完成了?当然不是啦。处理完Message后还需要去看看这个消息是不是还有下一个消息,那怎么去访问下一个消息呢?
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
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;
//这里还记得不,Target为null不就是屏障消息嘛对吧
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 { 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;
}
//这里都是在等有消息可以执行
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;
}
//下面是关于mPendingIdleHandlers的处理,我不是很了解,暂时没看明白
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
所以从上面的介绍看出,1、MessageQueue是根据Message.when进行排序的,并且mMessages总是指向这个链表的头部; 2、Looper会一直遍历MessageQueue,有消息就会取出,没消息就会阻塞; 3、MessageQueue当中会有三种消息:同步消息、异步消息、屏障消息,通过屏障消息可以使异步消息优先执行。
3、 Handler:
a)、构造方法:
先看到Handler的构建函数,其中可以看到的几个点:1、handler中会持有一个Looper属性,也就是说一个handler对象只会关联一个looper对象,反过来就是一个looper对象可以对应多个handler对象; 2、Handler还会持有对应Looper的MessageQueue对象。
public Handler(@Nullable 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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
b)、dispatchMessage:
再看到Handler的dispatchMessage方法,那就话不多说直接看到这个方法吧,不过看到后发现,其实在处理消息的时候还是有个先后的顺序的,先会检查消息的callback,再检查Handler的callback,最后再执行到Handler的handleMessage方法,而这个handleMessage还是一个空方法,需要我们在创建Handler的时候进行重写。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里消息的callback其实就是把callback当做消息直接发送,这里也可以直接看到Handler类中post(Runnable r)方法,就是直接将runnable直接作为参数发送出去的方法,这里后面就会通过调用该方法的handler和参数r创建一个消息。
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
c)、sendMessage:
发送消息就是post和sendMessage方法,不过这一大堆方法最后都会走到下面这个方法中:
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) {
//将消息的target设置为当前Handler
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
//设置异步消息
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//将消息放入当前Handler关联的MessageQueue,等待Looper处理
return queue.enqueueMessage(msg, uptimeMillis);
}
上述sendMessageAtTime()方法,最后就是将消息放入了当前Handler相关联的Looper的MessageQueue当中,最后等待该Looper去处理该消息。
4、总结一下
综上,举个例子,再线程A中希望发送一个消息给到线程B,那需要做什么呢?
1、 创建一个Handler,关联到线程B的Looper,这里有两个方案:直接再Handler构造函数中传入B的looper、 再线程B中创建Looper再从线程A中获取到该Handler B;
2、 生成需要的Message消息;
3、 通过和线程B的Looper关联的Handler B,将消息发送出去;
4、 等待消息被B线程的Looper执行,这里执行的方法也是通过callback或者重写Handler B中的handleMessage方法去实现的。
再说一下三者的关系:1、一个线程最多只有一个Looper;2、一个Looper有且仅有一个MessageQueue;3、一个Looper可以对应多个Handler,但是一个Handler只能对应1个Looper(MessageQueue)。