Android消息机制主要是指Handler的运行机制。在开始之前先问下,考虑以下几个问题:
- 为什么需要handler?
Android不建议在UI主线程做耗时操作,因为这样的话,可能会造成ANR, 那么,如果需要做耗时操作,需要开启线程做耗时操作,但是如果我们想更新Ui 呢,此时Handler就诞生了,系统之所以提供handler,主要是为了解决子线程中无法访问UI线程的矛盾。那么问题而就来了。 - 系统为什么不允许子线程更新UI呢?
因为Android的UI控件不是线程安全的,官方给的解释是,如果同时操作并发操作Ui线程那么将造成不可预期的状态。UI控件都是单线程模式的,那么问题又来了: - 既然线程不安全,那么给UI控件添加锁机制可以吗?
首先添加锁机制会让ui访问的逻辑变得更复杂,其次锁机制会降低ui的访问效率,因为锁机制会阻塞某些线程。最简单高效的方法就是采用单线程模型来处理ui操作。同时也可以想一下,Android系统刚推出的时候,不论是内存还是本地磁盘都不成熟,Google可能初衷是UI显示,以用户感知为基础,不必要的资源占用都会成为不使用的因素。
Message
1 : 什么是Message?
通俗讲就是,两个线程之间通信的传递的介质就是Message。
2:Message的成员变量:
public int what; //用于标记传递执行什么操作,通常用于switch判断。
public int arg1; //如果传递的数据是整数类型就可以使用arg1或者arg2,不用再创建bundle
public int arg2;
public Object obj; //在进程间通信时,传递数据,通常使用setData传递数据,需要实现Android方式的序列化
public Messenger replyTo; //在进程间通信时,可以通过replyTo给服务端传递一个客户端的Messenger的对象,此时服务端就可以使用Messenger发送消息给客户端
public int sendingUid = -1;
/*package*/ int flags; //标记位,标记Message是否正在被使用。
/*package*/ long when; //消息的什么是有被执行。
/*package*/ Bundle data; //传递数据,封装成Bundle对象
/*package*/ Handler target; //新建此消息的handler对象
/*package*/ Runnable callback; // handler.postMessage的runnable对象
/*package*/ Message next;
private static final Object sPoolSync = new Object();
private static Message sPool; //回收消息池
private static int sPoolSize = 0; //回收消息池此时的长度
private static final int MAX_POOL_SIZE = 50; //回收消息池的最大长度
private static boolean gCheckRecycle = true; //消息是否能够被回收。
从上可以看出,如果不考虑进程间通信,我们关心的就是 when, target,flags 以及消息回收池。flags很重要,这是个标记位,用于标记Message此刻的状态,如果消息处于0状态,表明Message可以被使用,但是消息处于FLAG_IN_USE状态,也就是说消息此刻正在被使用或者二次入队列,此此时将抛出异常:“This message is already in use.”
其实sPool本质是一个单向链表,内部存储着回收的消息,那么问题来了为什么采用链表存储回收的消息呢?这个问题看了消息的创建源码会找到答案。
3:Message的创建
对于很多开发者都是通过new Message()新建消息的。但是官方建议我们通过以下两种方法创建消息:
1 : Message.obtain();
2 : Handler.obtainMessage();
再看下obtain()和obtainMessage的源码:
public static Message obtain() {
synchronized (sPoolSync) { //加锁
if (sPool != null) { //消息回收池不是null
Message m = sPool; //获取链表头节点
sPool = m.next; //获取第一个Message
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--; //消息回收池长度-1
return m; //返回此消息
}
}
return new Message(); //如果是空链表,就新建Message。
}
从上述代码可以看出,obtain先判断回收池是否是null,如果是null,就new Message,如果不是null,就从回收池获取一个旧的Message,然后将消息的flags置为0,返回消息。整体思想就是重复利用创建过的Message,这也就是为什么官方建议我们这样做,肯定是为了节约资源啊。
接下来我们再看下Handler的obtainMessgae()源码:
public final Message obtainMessage()
{
return Message.obtain(this);
}
从上述代码可以看出,this指的就是Handler的引用,也就是Message的target变量。最终调用的还是Message内部的obtain(Handler target)。源码如下:
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
上述两种方法的唯一区别就是,是否是初始化了Message中的target变量。
4:Message的回收
既然有创建对象,那么同理也就有回收对象,Message的回收源码如下:
public void recycle() {
if (isInUse()) { //是否正在被使用
if (gCheckRecycle) { //是否能够回收消息
//抛出异常,消息正在被消费,不能够被回收。
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
//消息已经被消费过了,可以被回收了
recycleUnchecked();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
//从上述的英文文档可以看出,刚方法就是将Message回复原始状态,也就是说以前是怎么样的,现在就是怎么样的,
//但是唯一不同时flags对象置为FLAG_IN_USE对象,不能重复入队。
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
//将消息添加到回收池中。
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
总结:Message就是介质,相当于我们从一个Activity到另一个Activity要传递的数据,只不过这类型是固定的:Message。
注意点:
- flags的值
- 回收池
- target对象
MessageQueue
1:什么是MessageQueue?
MessageQueue中文翻译过来就是:消息队列。通常理解是:MessageQueue是存储消息的,是一个队列。但是MessageQueue是怎样工作的呢?MessageQueue真的是一个队列吗?
2:MessageQueue工作原理
我们先看下MessageQueue的创建,MessageQueue的构造方法如下:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed; //队列是否能够退出
mPtr = nativeInit(); //底层使用的Code
}
那么MessageQueue在哪里实例化呢?是在Looper的构造方法里:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看到,这个是在Looper实例化的,通过上述代码可以了解到,创建一个Looper就会创建一个MessgaeQueue。
接下来看下MessageQueue的成员变量:
// True if the message queue can be quit.
private final boolean mQuitAllowed; //是否能够退出
@SuppressWarnings("unused")
private long mPtr; // used by native code
Message mMessages; //链表的头节点(思考:队列里怎么会有链表呢?)
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked; //是否阻塞?
// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken;
通过上述代码,可以思考一个问题:成员变量里怎么会有链表Message?
3:Message入队
既然是队列,就肯定有消息入队嘛,代码如下:
//思考:我怎么知道这个是消息入队呢?
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) { //判断消息是否持有发送消息的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) { //链表是null
// 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;
}
从上述代码可以看书,消息入队之前会判断三个条件:
- 消息是否持有发送该消息的handler
- 消息是否正在被使用
- 消息队列是否退出了,如果退出了,那么回收消息,等等,回收消息?回收?Message的回收方法在这里调用一次。
判断完了之后,就是消息入队了,但是,这代码好像是链表的插入操作啊?别好像,本来就是,所以我们可以基本断定MessageQueue实际上操作的是消息链表,上一个问题:成员变量里怎么会有链表Message?是不是有答案了呢?
那么又有个问题了,这个方法什么时候调用呢?
4:Message出队
既然消息入队了,那么得有出队啊,源码如下:
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 (;;) { //死循环
//如果有消息过段时间再处理,就执行如下方法。Binder管道连接,等待到手通知消费消息
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;
//遍历消息链表,从链表中取出一个msg。
if (msg != null && msg.target == null) { //判断消息是否为null并且是否拥有发送消息的handler
// 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) { //消息不为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(); //调用的底层C代码,处理还未处理的消息,准备退出队列了,此方法是安全的退出方式。默认是安全退出方式
return null; //返回null, Looper退出的判断条件
}
// 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 = mIdleHandlers.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;
}
}
从上述代码可以看出出队的基本步骤:
- 消息队列是否已经退出
- 如果没有退出,进入死循环,遍历链表,从链表中取出一个消息,如果消息是null,就表明没有消息可以处理了。
- 如果Msg不是null, 判断消息是否是可以立即执行,如果不可以,那么设置可时间点,在将来的某个时间点执行。
- 如果消息可以立即执行,那么就是链表的删除元素操作了。
通过入队和出队,可以总结出,其实MessageQueue本质就是:内部维护了一个消息链表,操作都是链表的插入和删除操作。
思考:入队方法什么时候调用呢?
5:MessageQueue退出
关于MessageQueue的退出方法代码如下:
void quit(boolean safe) {
if (!mQuitAllowed) { //队列是否允许退出,这个值是在MessageQueue构造的时候传入的。
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);
}
}
可以看出就是遍历链表,然后回收所有的消息。再看下安全的退出removeAllFutureMessagesLocked()方法源码:
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) { //如果消息链表是null,返回
return;
}
if (n.when > now) { //找出链表中的第一个延迟消息
break;
}
p = n;
}
p.next = null;
do { //循环删除第一个延迟消息的所有的消息
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
结论:
不安全的退出:删除所有的消息
安全的退出:删除第一个延迟消息之后的所有消息(包括第一个延迟消息)。
对于这个结论,至少源码是这样告诉我们的,但是还有没有其他的区别呢?看完Looper应该会有惊喜~
思考:退出什么时候调用呢?
Looper
刚开始接触Android的时候,就知道一点,looper中有个Loop方法,能够从MessageQueue中去消息。那么具体怎么去消息呢?
1:Looper的成员变量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //存储线程不同线程的不同数据
private static Looper sMainLooper; //Ui 主线程的 sMainLooper
final MessageQueue mQueue; //关联的消息队列
final Thread mThread; //该Looper所在的线程
private Printer mLogging;
private long mTraceTag;
/* If set, the looper will show a warning log if a message dispatch takes longer than time. */
private long mSlowDispatchThresholdMs;
从成员变量可以看出,Looper内关联的有MessageQueue对象,创建了当前线程对象,自带Ui主线程的Looper。这三个都好说,那么sThreadLocal又是什么呢?从它的泛型可以看出是Looper的ThreadLocal类,使用场景:以线程为作用域并且不同线程有不同的数据副本。
2:创建Looper对象
通常的创建Looper的方法是:
public static void prepare() {
prepare(true); //是否能够退出队列
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) { //是否已经存储了Looper对象,也就是说该线程下,是否已经创建过Looper了,
//如果Looper已经存在了,就异常显示
throw new RuntimeException("Only one Looper may be created per thread");
}
//新建Looper对象,并且将Looper添加到ThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
//新建MessageQueue
mQueue = new MessageQueue(quitAllowed);
//新建Looper所在的线程对象
mThread = Thread.currentThread();
}
从上述代码可以看出,创建Looper做了三件事:
- 将Looper添加到ThreadLooper对象中;
- 创建MessageQueue对象;
- 创建当前Looper的线程对象;
其中从sThreadLocal.get()的判null操作,我们可以得出结论:一个线程只能存在一个Looper!
等等,这个MessageQueue的参数也就是quitAllowed嘛,也就是是否允许退出队列。
3:Looper的loop()方法
在开始loop()方法之前,有必要先看个方法myLooper():
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
//从翻译来看:获取与当前线程相关联的Looper对象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
从上述方法可以猜测ThreadLocal存储的是:Looper以及Looper关联的线程
接下来是重头戏loop()方法:
public static void loop() {
final Looper me = myLooper(); //返回当前线程关联的Looper对象
if (me == null) { //如果Looper为null,表明当前线程不存在Looper,也就是没有实例化Looper对象
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //获取当前线程中Looper对象对应的MessageQueue对象
// 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(); // 从MessageQueue中取出消息。有消息就取出,没消息就阻塞
if (msg == null) { //消息是否为null,这个应该在MessageQueue的next方法中,消息队列退出之后会返回null
// No message indicates that the message queue is quitting.
return;
}
...
msg.target.dispatchMessage(msg); //回调给发送消息的handler处理
...
msg.recycleUnchecked(); //处理完毕,消息回收
}
}
从上出代码可以看出,loop方法内部实际上是个死循环,调用MessageQueue的next()方法获取消息,等等,那之前的问题(队列中的next()方法在哪里调用)是不是解决了?有消息就处理,没消息阻塞。
那么怎么消费这个消息了呢? msg.target.dispatchMessage(msg);直接给出了答案,回调给了发送消息的target对象,让handler处理,从这里可以看你出,其实loop并不是真正的消费消息,只是起到一个消息调度的作用,真正消费消息的还是发送该消息的handler。
等等,loop死循环,那么怎么跳出死循环呢? 从源码来看只有满足一个条件,那就是返回的消息为null, 这样就退出死循环了。从MessageQueue的next()方法中可以看到,
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
也就说mQuitting为true的时候返回null, 那么怎么mQuitting怎么为true呢?从上文中的MessageQueue的quit代码中可以找到给mQuitting赋值的踪迹:mQuitting=true; 那这个MessageQueue的quit(boolean safe)什么时候调用呢?
4:Looper的退出
对于Looper的退出方式,Google给出了两种方式:
/**
* Quits the looper.
* <p>
* Causes the {@link #loop} method to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @see #quitSafely
*/
//注释最后一句的翻译是:考虑使用{@ Link quitSafely},以确保所有未决工作都有秩序地完成。
public void quit() {
mQueue.quit(false); //安全退出
}
/**
* Quits the looper safely.
* <p>
* Causes the {@link #loop} method to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* However pending delayed messages with due times in the future will not be
* delivered before the loop terminates.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p>
*/
//中间几句话翻译一下:在已交付的消息队列中已被处理。然而,延迟的消息与未来的到期时间将不会在循环结束之前处理
//换句话说:在退出之前,可以立即处理的消息,可以就都发送给handler处理,但是延迟的消息将不会得到处理。
public void quitSafely() {
mQueue.quit(true); //不安全退出
}