Handler是一个线程间通信的机制,很多消息都会从子线程发送至主线程,而主线程只有一个Looper,发送的消息都被放置在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) {
......
}
return true;
}
很清楚的看到这个方法里面有锁(synchronized),既然入队列里有锁,那再看看取消息是不是也有锁?
Message next() {
......
synchronized (this) {
......
}
......
}
是的,也是存在锁的。
所以,它是通过synchronized来保证了线程的安全性。
Handler所发送的Delayed消息时间准确吗?
实际上,这个问题与线程安全性为同一个问题,多线程中线程一旦安全,时间就不能准确;时间一旦准确,线程就一定不安全。
所以,Handler所发送的Delayed消息时间基本准确,但不完全准确。
因为多个线程去访问这个队列的时候,在放入对列和取出消息的时候都会加锁,当第一个线程还没有访问完成的时候,第二个线程就无法使用,所以他实际的时间会被延迟。
我们在使用Message的时候应该怎样创建它?
由于Message创建非常频繁,如果不断以new的方式去创建它,它的内存抖动是很恐怖的。
所以在Android的Message机制里面,对Message的管理采用了享元设计模式。
先来查看Message.obtain()都有哪些操作?
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
obtain()维持了一个Message的pool(池子)。
private static Message sPool;
我们在构建一个消息的时候,一般的步骤是先obtain一个消息,然后对它的各个字段进行设置,像target、data、when、flags…
当MessageQueuez去释放消息的时候(quit),它只是把消息的内容置空了,然后再把这条处理的消息放到池子里面来,让池子不断变大。
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
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++;
}
}
}
在这个池子里面,最多放置50个消息。
private static final int MAX_POOL_SIZE = 50;
如果消息超过了50个消息,这个池子也不要了,然后mMessage也为空,则它也会被及时的回收。
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
使用Handler的postDelay后消息队列将会有怎样的变化?
我们从postDelay的方法来开始追:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
这时候就是给消息队列添加一个消息时刻,如果这个消息队列为空,这个消息就不会被执行,只有一个消息,这个消息就不会被发送,而是计算等待的时间。
在添加消息的时候,在MessageQueue的时候,他有一个计算,MessageQueue里面有一个enqueueMessage()。在这个enqueueMessage中,一旦添加了消息之后,他就执行nativeWake()唤醒消息队列,这个消息队列就醒来。
if (needWake) {
nativeWake(mPtr);
}
这个消息队列醒来之后,在MessageQueue里的next()函数就会触发关于要等待多长时间的计算。
//开机到现在的毫秒数如果小于msg.when则代表还未到发送消息的时间
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
// 虽然有消息,但是还没有到运行的时候
//计算还有等待多久,并赋值给nextPollTimeoutMillis
//设置下一次查询消息需要等待的时长
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
计算完这个等待时间之后,这个for循环结束,结束完之后回过头来,就再跑回这里再次睡眠。
//native的方法,在没有消息的时候回阻塞管道读取端,只有nativePollOnce返回之后才能往下执行
//阻塞操作,等待nextPollTimeoutMillis时长
nativePollOnce(ptr, nextPollTimeoutMillis);
所以说,他会先计算需要等待的时间,计算完需要等待的时间之后,就会进行对应的操作,然后重新让这个消息进行wait。