消息机制
你了解安卓中的消息机制吗;
1、一个线程可以存在多个消息队列吗
2、延迟消息是怎么处理的
3、 View.post(Runnable action) ,runOnUiThread(Runnable action)和Handler.post(Runnable action)区别
4、 为什么主线程不会因为Looper.loop()方法造成阻塞
5、 为什么主线程会ANR而子线程不会呢
6、 HandlerThread原理
7、 IntentService原理
消息机制
我们从常用的Handler.post(Runnable action)去一步步解析消息机制
- 不管是post(),postDelayed(),sendMessage()等方法最后会调sendMessageDelayed();
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
- 把runnable或者what都封装成Message
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
- 消息发送。注意SystemClock.uptimeMillis() + delayMillis SystemClock.uptimeMillis() 是本地方法获取当前时间
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
- 加入消息队列。enqueueMessage(queue, msg, uptimeMillis)。注意这里msg.target = this。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
- MessageQueue 消息队列怎么来的呢
我们从Handle构造方法开始找
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
//这里的有没有遇到过,我们平常new的Thread是没有mLooper的。在子线程里直接new Handle就会抛出这个
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;
}
-消息队列是从Looper里来拿的,那Looper又是怎么来的呢
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
再看看 sThreadLocal.get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
通过set(T value)方法再看看looper是怎么加到ThreadLocalMap里的
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- Looper又是在哪里实例化并是怎么加到ThreadLocalMap里的呢
public static void prepare() {
prepare(true);
}
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里来拿的"然后一个Thread只有有一个Looper,所以一个Thread只能有一个Looper,对应一个消息列队。
- 前面有提到线程默认是没有Looper的,那么主线程在哪里创建了Looper呢
//ActivityThread的main方法
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
- 加到消息队列
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
msg.markInUse();
msg.when = when;
//这个mMessages是链表的头
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排序好的。在插入消息前会记录消息要发送的时间。这样就可以处理延时消息了
万事俱备,就差牛逼的消息获取以及分发了
- 死循环一直从MessageQueue中遍历消息
我们都知道一个线程做完一件事情就没了,但主线程就算没事做也不能结束掉呀。很简单,一个死循环。
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();
boolean slowDeliveryDetected = false;
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) {
//有没有人用过BlockCanary,原理很简单
//在事件分发前后做记录,就能分析出在dispatchMessage中执行了耗时操作
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
try {
msg.target.dispatchMessage(msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
msg.recycleUnchecked();
}
}
关键是两个点
- 获取消息: Message msg = queue.next(); // might block
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 = 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;
}
}
1、拿到消息,判断消息when
1.1、立即返回message去分发
1.2、延时消息计算要分发的时间nextPollTimeoutMillis,会调用本地的方法nativePollOnce(ptr, nextPollTimeoutMillis);
没有消息nextPollTimeoutMillis = -1 会一直阻塞操作,等待新消息进来
延时消息nextPollTimeoutMillis > 0 会一直阻塞操作,到点后或者新消息进来唤醒
1.3、nativePollOnce()方法里本质同步I/O,即读写是阻塞的,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。
具体nativePollOnce()的Native层实现,想深入研究可查看资料: Android消息机制2-Handler(Native层)。
- 消息分发:msg.target.dispatchMessage(msg);前面有提到要注意Message加入列表前msg.target = this。所以消息分发就是调的Handle的dispatchMessage(msg)
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
- runOnUiThread(Runnable action)就很简单了,看一下源码
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
- View.post(Runnable action)复杂一点,我们平时有尝试过拿View的宽高,在onCreate(Bundle savedInstanceState)甚至在onResume() 里都是拿到宽高为0。然后通过View.post(Runnable action)方法在run()里却拿到真正的宽高。
首先我们看一下post()代码
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
代码以及注释很明显,如果AttachInfo 不为空就走的mHandler.post(action);否则推迟这个Runnable直到知道要在哪个线程去运行。那这个getRunQueue().post(action)应该就是暂时缓存起来了。
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
1、我们先看AttachInfo。mAttachInfo是在View第一次attach到Window时,ViewRoot传给自己的子View的。这个AttachInfo之后,会顺着布局体系一直传递到最底层的View。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}
void dispatchDetachedFromWindow(){
...
mAttachInfo = null;
}
- HandlerThread 其实HandlerThread 继承Thread,但是它实现了消息队列。特点就是开启一个线程按消息发送顺序串行地处理事务,可以复用,不用重新new Thread。
- IntentService 继承 Service。内部用了HandlerThread去起一个子线程做耗时操作,最后stopSelf()。