文章目录
- 回顾
- 线程消息队列时怎样实现的
- 消息是怎么传递的?
- Handle 的延迟消息是怎么处理的?
- IdleHandler 的原理
- 主线程进入了 Looper 循环为什么没有 ANR?
- 消息屏障是什么?
回顾
之前学习过Handler相关的基础知识,今天再学习一下 Handler 更深层次的知识。
线程消息队列时怎样实现的
- 可以在子线程创建 Handler 么?
- 主线程的 Looper 和 子线程的 Looper 有什么区别?
- Handler Looper 和 MessageQueue 有什么关系?
- MessageQueue 是怎么创建的?
- 可以在子线程创建 Handler 么?
在子线程创建 Handler 时会报一个 RuntimeException 让你调用 Looper.prepare()
public Handler(Callback callback, boolean async) {
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;
}
Looper.myLooper() 是从 ThreadLocal 中读取 Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
- Looper.prepare()
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));
}
Looper.prepare() 呢,先从 sThreadLocal.get() 如果已经存在则抛出异常。如果没有 Looper 则创建一个 Looper 传入了 quitAllowed 参数,默认是 true ,quitAllowed 是什么意思的?quitAllowed 代表 Looper 是否可以退出,工作完成以后可以调用 Looper.quit() 退出,主线程创建的 Looper 可以看一下,创建的时候传入的是 false ,是不可以退出的,并且创建完会将 Looper 保存到一个静态变量里面,就可以随时获得主线程的 Looper。
- 可以在子线程创建 Handler 么?
- 可以,需要先创建 Looper
- 主线程的 Looper 和 子线程的 Looper 有什么区别?
- 上面讲的子线程创建 Looper quitAllowed 是true 主线程是 false;就是子线程的 Looper 可以退出,主线程不可以。
- 那么 Looper 创建的时候做了哪些事
// Looper 的构造函数
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper 在创建的时候 new MessageQueue(quitAllowed); 创建了消息队列
- new MessageQueue(quitAllowed);
MessageQueue(boolean quitAllowed) {
// 记录了是否可以退出
mQuitAllowed = quitAllowed;
// 调用了 nativeInit() 在 native 层去初始化
mPtr = nativeInit();
}
- Handler Looper 和 MessageQueue 有什么关系?
- 线程在创建 Handler 时需要创建 Looper ,创建 Looper 时创建了 MessageQueue (Handler 创建可以传入 Looper)多个 Handler 可以往同一个 Looper 发送 msg ,MessageQueue 发送消息时会根据 target 往对应的 Handler 中回调数据。
- 接下来看看 MessageQueue() 在 native 层做了哪些处理
frameworks/base/core/jni/android_os_MessageQueue.cpp -> android_os_MessageQueue_nativeInit
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
其主要创建了 NativeMessageQueue() 对象
- NativeMessageQueue
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
// 先从当前线程的缓存中获取 Looper
mLooper = Looper::getForThread();
// 如果获取不到 则 new 一个 Looper
if (mLooper == NULL) {
mLooper = new Looper(false);
// 然后设置到局部缓存中
Looper::setForThread(mLooper);
}
}
- Looper::getForThread(); 线程的局部缓存是什么呢?
sp<Looper> Looper::getForThread() {
int result = pthread_once(& gTLSOnce, initTLSKey);
LOG_ALWAYS_FATAL_IF(result != 0, "pthread_once failed");
Looper* looper = (Looper*)pthread_getspecific(gTLSKey);
return sp<Looper>::fromExisting(looper);
}
其实就是获取线程的 TLS ,叫 Thread Local Storage ,就是对线程内全部开放,其他线程无法访问。我记得之前好像学习 JVM 的内存分配的时候有涉及到。
- native 的 Looper 的创建
Looper::Looper(bool allowNonCallbacks) :... {
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
AutoMutex _l(mLock);
rebuildEpollLocked();
}
首先根据 eventfd 创建了 mWakeEventFd ,这里是Android9 早起版本不是 mWakeEventFd 是管道,因为使用 mWakeEventFd 计数器比用管道的性能要好。管道需要写再读需要拷贝数据。(在android后期又对mWakeEventFd 的使用做了优化。)然后调用了 rebuildEpollLocked();
- rebuildEpollLocked();
void Looper::rebuildEpollLocked() {
// Allocate the new epoll instance and register the WakeEventFd.
// epoll_create1 创建了 epoll
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
// 创建了 createEpollEvent 事件设置了 mWakeEventFd ,监听可读事件
epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
// ...
}
Looper 创建的时候会创建 mWakeEventFd ,并且去监听它的事件,那么数据是什么写到里面的呢?另外一个线程往当前线程消息队列中插入一条消息,然后会调用 wake() 函数 :调用完了 wake() 函数就会往 mWakeEventFd 里面写东西。
wake() 函数调用:可以看看java 层调用 Handler sendMessage 时,加入到 MessageQueue 后,会调用到下面代码,mPtr 是上面创建 MessageQueue时调用的 mPtr = nativeInit(); 返回的。
if (needWake) {
nativeWake(mPtr);
}
private native static void nativeWake(long ptr);
- 那么什么时候去读数据的呢?
在 Looper.loop() 里会从 Message 中获取 next() 的 message ,在调用 next() 函数时调用了 nativePollOnce(ptr, nextPollTimeoutMillis); 函数。调用到了 native 层 Looper::pollOnce
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
//....
result = pollInner(timeoutMillis);
}
}
- 调用了 pollInner() 函数
int Looper::pollInner(int timeoutMillis) {
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// epoll_wait 等待有没有事件触发
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
//...
// 如果有事件了则在 for 循环中处理事件
for (int i = 0; i < eventCount; i++) {
const SequenceNumber seq = eventItems[i].data.u64;
uint32_t epollEvents = eventItems[i].events;
if (seq == WAKE_EVENT_FD_SEQ) {
if (epollEvents & EPOLLIN) {
// 调用了 awoken(); 函数 awoken 就是把事件读出来 消化掉
awoken();
}
} else {
//...
}
}
}
- 架构图
Handler 架构分为 java 层和 native 层,java 层开始,一个线程创建了 Looper 对应的创建了一个 MessageQueue , java 层的 MessageQueue 创建时对应创建了一个 native 层的 NativeMessageQueue 并且创建了一个 Looper ,也就是说 java 层的 Looper 和 MessageQueue 在 native 层也对应的有 Looper 和 MessageQueue。
- Handler Looper 和 MessageQueue 有什么关系?
- 一一对应的关系
- MessageQueue 是怎么创建的?
- java 层 Looper 创建的时候创建的 MessageQueue ,java 层MessageQueue 创建的时候会创建一个 native 层的 NativeMessageQueue,NativeMessageQueue 创建的时候会创建 Naive 层的 Looper ,Native 层的 Looper 创建的时候会创建一个可读的 epoll 。
消息是怎么传递的?
上面一段讲了 Handler 在 java 层和 native 层的架构,这回梳理一下消息是怎么传递呢。
- 消息循环过程是怎么样的?
- 消息是怎么发送的?
- 消息是怎么处理的?
- 从 java 层的 Looper.loop() 循环开始
public static void loop() {
// 拿到 looper
final Looper me = myLooper();
// 拿到 MessageQueue
final MessageQueue queue = me.mQueue;
for (;;) {
// 取下一条消息
Message msg = queue.next(); // might block
if (msg == null) {
// 没有消息直接返回
return;
}
// 调用消息的 target.dispatchMessage(msg);
// target 就是对应的 Handler
msg.target.dispatchMessage(msg);
// 回收消息
msg.recycleUnchecked();
}
loop() 中重点是 Message msg = queue.next(); 如何获取下一个消息和 msg.target.dispatchMessage(msg); 如何分发消息。
- msg.target.dispatchMessage(msg); 分发 msg 比较简单
public void dispatchMessage(Message msg) {
// 先以 msg 中的 callback 优先回调回去
if (msg.callback != null) {
handleCallback(msg);
} else {
// 然后再检查全局 mCallback
if (mCallback != null) {
// mCallback.handleMessage(msg) 返回 true 则不往下分发了。
// 一些 hook 点就是通过反射 设置 mCallback 偷偷的更换 msg 然后返回 false
if (mCallback.handleMessage(msg)) {
return;
}
}
// 最后才调用 handleMessage
handleMessage(msg);
}
}
- Message msg = queue.next(); 怎么取消息
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
// 目的是阻塞线程,当其他线程发送一些特殊消息的时候会唤起阻塞
// 第一次 nextPollTimeoutMillis = 0 所以第一次一定不会阻塞
// 如果第一次下去之后没有消息了 nextPollTimeoutMillis = -1 了就需要一直等待了
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
Message prevMsg = null;
// 取一条消息
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
msg.next = null;
// 标记成使用中
msg.markInUse();
// 然后返回消息
return msg;
// ...
// No more messages.
nextPollTimeoutMillis = -1;
}
}
}
next() 方法这里主要看 nativePollOnce(ptr, nextPollTimeoutMillis); 方法,首次超时时间 nextPollTimeoutMillis = 0,所以一定不会阻塞,会去从队列中取消息,如果没有消息则把 nextPollTimeoutMillis 设置成 -1 ,下次 for() 循环会一直阻塞住。接下来看一下 nativePollOnce() 函数。
- nativePollOnce()
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
android_os_MessageQueue_nativePollOnce() 调用了 NativeMessageQueue 的 pollOnce(env, obj, timeoutMillis);
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
// ...
}
NativeMessageQueue::pollOnce() 函数调用了 Looper 的 pollOnce() 函数,并且带了一个超时时间。
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
//...
if (result != 0) {
if (outFd != nullptr) *outFd = 0;
if (outEvents != nullptr) *outEvents = 0;
if (outData != nullptr) *outData = nullptr;
return result;
}
result = pollInner(timeoutMillis);
}
}
Looper::pollOnce() 首次的时候 result = 0 ,所以会调用 pollInner(timeoutMillis); 函数。
int Looper::pollInner(int timeoutMillis) {
// ...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// 调用了 epoll_wait() 函数,这个函数是用来阻塞的,它返回只有几种情况
// 第一种出错了,eventCount<0,第二种超时了 eventCount=0,第三种有事件传递进来 eventCount 就是事件个数
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// ...
// 有事件返回以后则通过 for 循环处理事件
for (int i = 0; i < eventCount; i++) {
const SequenceNumber seq = eventItems[i].data.u64;
uint32_t epollEvents = eventItems[i].events;
if (seq == WAKE_EVENT_FD_SEQ) {
if (epollEvents & EPOLLIN) {
// 如果事件满足条件,则调用 awoken() 来消费事件
awoken();
}
} else {
// ...
}
}
//...
return result;
}
当 Looper::pollInner() 返回了,就可以继续执行最上面的 next() 函数了,一直循环拿到下一个msg,就是不停的调用 nativePollOnce() 一直监听其他线程是否有发送事件进来,如果有事件,nativePollOnce() 就可以顺利执行下去,就可以拿下一个信息了。
- 那么怎么往消息队列里面发送消息呢?
一般使用的时候都是调用 Handler 的 sendMessage()
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
最后都会走到下面这个方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// Handler 设置给 target
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 调用 MessageQueue 的 enqueueMessage 并传入 uptimeMillis
return queue.enqueueMessage(msg, uptimeMillis);
}
- queue.enqueueMessage(msg, uptimeMillis); 重点代码如下
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// 先将消息插入到消息队列中
//...
if (needWake) {
// 然后调用 nativeWake(mPtr); 去将唤醒消息队列所在的线程
nativeWake(mPtr);
}
}
return true;
}
enqueueMessage() 首先将消息插入到消息队列,然后调用 nativeWake(mPtr); 唤醒消息队列所在的线程,这里重点看是如何唤醒的。
- nativeWake(mPtr);
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
android_os_MessageQueue_nativeWake() 函数调用到了 NativeMessageQueue 的 wake() 函数。
void NativeMessageQueue::wake() {
mLooper->wake();
}
最后又调用到了 Looper 的 mLooper->wake();
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
}
Looper::wake() 往 mWakeEventFd 里面的计数器写数,这样 epllo_wait() 的循环就可以收到可读事件了。
Handle 的延迟消息是怎么处理的?
- 发送延时消息一般从 Handler 发送消息开始,传入延迟的毫秒数。
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
// 调用 sendMessageAtTime() 用当前时间 + 延迟时间 就是发送的时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
- sendMessageAtTime()
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);
}
调用了 MessageQueue 的 enqueueMessage() 传入了 msg 和 时间
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
//...
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 消息队列是空 或者 when = 0 (调用 sendMessageAtFrontOfQueue 的时候 when = 0)
// 或者比第一个时间还早
// 满足上面几个条件之一则插入到第一个节点
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
// 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;
}
enqueueMessage() 就是按照时间为 Message 单链表做了个排序,所以延迟的意思就是先加入队列,到时间再处理消息。然后还是调用了 nativeWake(mPtr); 函数,上面代码将到了 调用完了 nativeWake(mPtr); 会写入事件,唤醒 native 层的 Looper 循环返回数据。先看一下 java 层的 loop
public static void loop() {
final Looper me = myLooper();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
}
需要注意的是无消息或者消息未到时间的阻塞是在 queue.next(); 函数中 那么如果 msg == null 返回的 return 是出现异常了 loop() 停止了, 这是两个概念。 接下来看一下 queue.next();
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
// 目的是阻塞线程,当其他线程发送一些特殊消息的时候会唤起阻塞
// 第一次 nextPollTimeoutMillis = 0 所以第一次一定不会阻塞
// 如果第一次下去之后没有消息了 nextPollTimeoutMillis = -1 了就需要一直等待了
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) {
// 如果消息还没到时间,则 nextPollTimeoutMillis 等待时间设置成还差多少时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 如果时间到了则返回 msg
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
}
上面讲的时候说过,没有消息时 nativePollOnce() 会阻塞住,当 nativeWake() 发送后,会使得 nativePollOnce() 通过,会走下面的代码。首先 nativePollOnce(ptr, nextPollTimeoutMillis); 的 nextPollTimeoutMillis 参数是指睡眠多长时间,如果是 -1 则一直睡眠等待 wake() ,上面拿到 message 后如果到了时间则直接返回 msg ,如果还未到则计算一下还剩下多少时间赋值给 nextPollTimeoutMillis ,然后调用 nativePollOnce(ptr, nextPollTimeoutMillis); 睡眠等待时间到达。 下一次唤醒会把 msg 返回回去。
- 总结
延迟操作就是首先按照时间顺序插入消息队列中,然后通过 epoll_wait() 进行延迟阻塞,到时间了再返回消息。只不过延迟精度不一定很精确。而且如果处理消息太耗时,可能会让下一个消息延迟了。
IdleHandler 的原理
了解 IdleHandler 的作用以及调用方式
了解 IdleHandler 有哪些使用场景
熟悉 IdleHandler 的实现原理
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
从上面注释来看,boolean queueIdle(); 回调的时机第一种是消息队列中没有了消息。第二种可能是消息队列中有消息,但是时间还未到执行它的时候。
- IdleHanlder 的用法
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// 如果 return true; 就可以一直收到回调,如果 return false;就只能收到一次回调
return true;
}
});
- MessageQueue 中 addIdleHandler() 函数
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
addIdleHandler() 函数就是往 mIdleHandlers 数组中添加一个 handler ,那么 mIdleHandlers 的列表是什么时候调用的?
- mIdleHandlers 的列表是什么时候调用的?
在 Looper 的 loop() 函数中,上面讲过 loop() 会从 MessageQueue 中获取Message,然后去执行分发,然乎回收消息。那么MessageQueue是如何返回消息的?
- MessageQueue: next() 函数
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
// 阻塞用 有消息 或者超时 或者异常了 会往下走
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// ... 获取 Message 逻辑省略
// 如果没有获取到普通Message消息会往下获取 mIdleHandlers 中的数据
// 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();
}
// 如果没有 IdleHandler 则直接跳过此次循环
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)];
}
// 将 mIdleHandlers 转换为数组
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
// 从 数组中获取 IdleHandler 数据
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);
}
// 如果 queueIdle() 返回的 false 则执行完了从列表中删除,也就是只执行一次
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
所以 nativePollOnce() 返回之后没有消息需要分发了,就开始处理 IdleHandler 中的数据了。
- framework 中用到了 IdleHandler 的地方
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
// 返回。false 只执行一次
return false;
}
}
在 ActivityThread 中添加进去了一个 mGcIdler 执行的时候会调用GC操作。
需要注意的是,如果 MessageQueue 中没有消息了,addIdleHandler 之后并不会触发 Idle 事件的执行,有时候需要往 MessageQueue 中 send 一条普通消息才可以。下面那条例子也是其中之一
- 之前的 Idle 都是异步的,下面这种情况是处理同步 Idle 的情况。
frameworks/base/core/java/android/app/Instrumentation.java
public void waitForIdleSync() {
validateNotAppThread();
Idler idler = new Idler(null);
mMessageQueue.addIdleHandler(idler);
mThread.getHandler().post(new EmptyRunnable());
idler.waitForIdle();
}
waitForIdleSync() 等待 Idle 执行返回,最后调用了idler.waitForIdle(); 等待
public void waitForIdle() {
synchronized (this) {
while (!mIdle) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
}
调用了 wait(); 同步等待线程 mIdle 完成。直到 IdleHandler 的 queueIdle() 方法执行
public final boolean queueIdle() {
if (mCallback != null) {
mCallback.run();
}
synchronized (this) {
mIdle = true;
notifyAll();
}
return false;
}
将mIdle = true; 再调用 notifyAll(); ,这样上面 wait() 的代码就可以执行下去了。我们自己开发的时候也可以使用这种方式。
- IdleHandler 适用场景
- 之前研究性能优化中的启动优化时,一些不必要立刻启动的项目可以放到 IdleHandler 中执行,或者 Activity onCreate() 以后一些可以在 UI 绘制等以后执行的,可以放在 IdleHandler 执行。
- 批量任务:任务密集,只关注最终结果(比如打开 App 收到一堆通知要刷新UI ,可以先汇总,等待UI绘制结束再统一刷新一次页面。)
主线程进入了 Looper 循环为什么没有 ANR?
了解 ANR 触发原理
了解应用大致启动流程
了解消息循环机制
了解系统和应用通信流程
- ANR 是什么?
ANR 实际上是 AMS 在系统进程弹出来的一个 dialog
AMS 在发生 ANR 时会调用
mHandler.post(new Runnable() {
@Override
public void run() {
mAppErrors.appNotResponding(proc, activity, parent, aboveSystem, annotation);
}
});
- mAppErrors.appNotResponding(proc, activity, parent, aboveSystem, annotation);
// Bring up the infamous App Not Responding dialog
Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);
mService.mUiHandler.sendMessage(msg);
上面发送 mUiHandler 不是在 SystemServer 的主线程,其实是在子线程。(所以 UI 不一定是在主线程刷新,之前讲 UI 线程的时候提到过)
在 frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 中会接收到 handleMessage 消息
final class UiHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_NOT_RESPONDING_UI_MSG: {
mAppErrors.handleShowAnrUi(msg);
ensureBootCompleted();
} break;
在 mAppErrors.handleShowAnrUi(msg); 中会创建 diaog
dialogToShow = new AppNotRespondingDialog(mService, mContext, data);
proc.anrDialog = dialogToShow;
dialogToShow.show();
-
发生 ANR 的场景有哪些
Service Timeout BroadcastQueue Timeout ContentProvider Timeout InputDispatching Timeout (包括 Activity 输入等处理超时)
-
那么 ANR 是怎么触发的呢?系统如何知道 ANR 了。
下面以 Service 为例
之前的文章 Android 深入理解 Service 的启动和绑定 有讲到过启动 service 的过程要经过下面的方法。
- ActiveService : realStartServiceLocked()
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
bumpServiceExecutingLocked(r, execInFg, "create");
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
}
在调用 app.thread.scheduleCreateService() 之前,先调用了 bumpServiceExecutingLocked()
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
boolean timeoutNeeded = true;
long now = SystemClock.uptimeMillis();
if (r.executeNesting == 0) {
r.executeFg = fg;
ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now);
}
if (r.app != null) {
r.app.executingServices.add(r);
r.app.execServicesFg |= fg;
if (timeoutNeeded && r.app.executingServices.size() == 1) {
scheduleServiceTimeoutLocked(r.app);
}
}
} else if (r.app != null && fg && !r.app.execServicesFg) {
r.app.execServicesFg = true;
if (timeoutNeeded) {
scheduleServiceTimeoutLocked(r.app);
}
}
//...
}
其内部调用了 scheduleServiceTimeoutLocked(r.app);
// static final int SERVICE_TIMEOUT_MSG = 12;
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;
// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
其实内部就是为 ActivityManagerService 的 handler 发送了一个延迟消息延迟时间就是 service 超时时间。发送的what = static final int SERVICE_TIMEOUT_MSG = 12; 超时以后 AMS 接收到消息就会调用到 frameworks/base/services/core/java/com/android/server/am/ActiveServices.java ,再调用到了 mAm.mAnrHelper.appNotResponding(proc, anrMessage); 然后就弹出弹窗了。
-
那么如果 Service 正常启动了以后为什么没有弹窗呢?
之前文章讲过,Service 启动会回调到 ActivityThread 的 handleCreateService() -
handleCreateService()
private void handleCreateService(CreateServiceData data) {
// 这里面就是说的 IdleHanlder 的用处之一
unscheduleGcIdler();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
}
try {
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
service.onCreate();
mServices.put(data.token, service);
try {
// 调用完了 service.onCreate(); 之后调用到了 AMS 的serviceDoneExecuting()
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
-
ActivityManager.getService().serviceDoneExecuting(data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
然后调用到了 serviceDoneExecutingLocked() -
重点:serviceDoneExecutingLocked 内部调用了下面方法
static final int SERVICE_TIMEOUT_MSG = 12;
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
将同一个 what 属性的 ActivityManagerService.SERVICE_TIMEOUT_MSG 从 Handler 移除掉,这样 ANR 的弹窗就不会弹出来了。
- 主线程的消息循环
public static void main(String[] args) {
Looper.prepareMainLooper();
// ...
Looper.loop();
}
原理上面已经讲过,loop() 循环 从 MeesageQueue 中读取数据,等等…,那么有几种情况会发送消息到主线程的 Hanlder 呢
1. 应用主线程发送消息
2. 应用子线程发送消息
3. binder 线程往主线程发送消息
- 比如启动 AMS Service 都是通过 binder 线程发送到主线程去处理的
-
总结
为什么没有ANR:ANR是没有在规定时间内没有完成AMS的任务,和 loop() 循环没有啥必然联系 AMS 的请求都是丢到应用端的 binder 线程去处理,然后再丢到发送消息去唤醒主线程处理。 ANR 不是因为 for(;;) 而是主线程有耗时任务导致的 AMS 任务延迟导致的。比如上面启动 Service 的情况 是先走的 service.onCreate() 然后去移除的 Handler 消息,所以 service onCreate() 不能有太耗时的操作。
消息屏障是什么?
- 正常的消息队列分为几种消息,平时大多只用了普通消息,还有两种 一种是屏障消息,一种是异步消息。
- 屏障消息不是为了分发的,是为了阻塞普通消息的分发的
- 异步消息和普通消息的本质区别就是有一个异步的标志位,导致会有不同的处理。
- 如何发布一个屏障?
- frameworks/base/core/java/android/os/MessageQueue.java 中有一个函数 postSyncBarrier()
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
// 插入消息链表
return token;
}
}
因为屏障消息不需要分发,所以不需要 target 也就是 Handler,后面会根据 target 是不是空来判断是不是屏障消息。并且它也会按照时间排序,不过它只会影响后面的消息。返回的 token 是用来后面撤销屏障用的。我们自己发送的消息 target 必须是有值的。
- 移除屏障的方法,需要通过 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) {
// 移除消息 ...
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
移除消息通过 token,移除后调用 nativeWake(mPtr); 函数唤醒 native_wait() 。唤醒以后会继续处理加入的普通消息。
- 屏障用在哪里了?
loop 获取消息是从 MessageQueue 中的 next() 函数,屏障消息也是如此
Message next() {
final long ptr = mPtr;
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;
// msg.target == null 就是屏障消息
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) {
// 处理返回消息
return msg;
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
nextPollTimeoutMillis = 0;
}
}
next() 函数在获取 message 时,判断它是不是屏障消息,也就是 target == null ,如果是屏障消息则进行 do while() 循环,查找是否有异步消息要处理,如果有异步消息则返回异步消息,如果没异步消息,然后睡眠等待屏障的移除(需要其他线程唤醒,也就是上面的移除唤醒)。
- 插入消息也可能会唤醒线程
boolean enqueueMessage(Message msg, long when) {
// 消息插入到了队列的头 如果休眠状态,需要唤醒
// 如果普通消息,并且在屏障后面,则没有必要唤醒
// 如果插入了最早的一条异步消息则需要唤醒
if (needWake) {
nativeWake(mPtr);
}
}
- Android framework 哪里用到了屏障
主要是屏幕绘制的时候 ViewRootImpl 的 scheduleTraversals() 开始绘制之前发送了一个 postSyncBarrier()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 插入屏障 这样普通消息就会 block住。
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 然后等待 mTraversalRunnable 执行(下一个 vsync 信号到来)
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
}
}
doTraversal() 的时候移除消息,然后开始绘制了。目的是为了防止开始绘制因为普通消息延迟。