Android开发教程——重新认识一下Handler
这篇文章 不是带着大家去了解Handler 工作原理等这些老生常谈的问题,是主要向大家介绍Handler 的阻塞原理和消息屏障机制,这里做个提示 可以让大家按需阅读。
Handler 可以说是App的心脏,推动着整个App所有事件的执行。接下来就一起探究下Handler的阻塞和消息平屏障。
阻塞机制
先理解下什么叫做阻塞?
比如我们定了一个外卖,我们不用一直问骑手外卖有没有送到,我们可以先继续做其他事情,骑手到了之后会给我们打电话。这个过程就是阻塞。
在MessageQueue中取出消息后,通过时间比较来计算出 需要阻塞的时间,如果没有时间,需要阻塞时间就赋值为-1
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 {
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
然后通过nativePollOnce 来阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
这个 nativePollOnce 是个native方法,我们继续往下分析:
在这里我就不一一列出 贴出调用的代码了,就直接将调用流程 贴出来
nativePollOnce
-> android_os_MessageQueue_nativePollOnce (android_os_MessageQueue.cpp)
-> NativeMeaageQueue::pollOnce
->Looper::pollOnce (Looper.cpp)
->Looper::pollInner->epoll_wait
在调用过程中,可以看到 阻塞时间这个参数 最后传到的epoll_wait这个函数里面
Looper.cpp
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
那就说明,阻塞是由epoll_wait完成的。那么什么是epoll ? 接下来,一点点的讲解。
在这里再向大家声明一个概念:
非阻塞忙轮询:
还是我们点了一个外卖,但是我们么现在特别饿,我们就需要骑手的手机号,然后一分钟给骑手打一个电话,这就是非阻塞忙轮询。
我们请求网络,从数据库读数据,只要处理数据都会涉及I/O,现在我们有多个I/O事件,那么我们该如何处理多个流呢?
多个线程 去处理多个IO流,效率会特别低,这是由CPU设计决定的,所有都会用一个线程去处理I/O
while (true){
for ( i -> stream[]) {
if (i has data){
read data until unavailable
}
}
}
我们可以像这样,用非阻塞忙轮询去处理多个IO,一直遍历每个流,当读到流中有数据的时候,就会处理数据,处理完之后 然后继续轮询。但是当所有流中都没数据的时候,cpu 就会在空转,白白浪费资源。此时应该让出cpu资源,所以有了select 机制。
while (true){
select(stream[])
for ( i -> stream[]) {
if (i has data){
read data until unavailable
}
}
}
从select 可以知道,有IO事件发生了,然后再轮询流,来处理数据。没数据的时候,则在select处阻塞着。
虽然select可以知道有IO事件发生了,但是不知道是哪几个流,所以只能无差别的轮询所有流。这种无差别轮询显然也是一种资源浪费。
在linux2.6之后,出现了epoll机制
首先看一下epoll的定义
epoll 是linux 内核中的一种可拓展IO事件 处理机制,大量应用程序请时,能够获得较好的性能。
while (true){
activite_stream[] = epoll_wait();
for ( i -> activity_stream[]) {
read data until unavailable
}
}
epoll 会将 有哪几个流发生了事件告诉我们,我们就可以对流处理数据,这样操作的每个流都是有数据的。直接将负责度由O(N)降低到了O(1)。
epoll 的用法只有三个方法:
int epoll_create(it size)
创建一个epoll 句柄,size用来告诉内核需要监听的数目一共有多大
int epoll_ctl(int epfd,int op,int fd, struct epoll_event *event);
这是epoll 的时间注册函数
int epoll_wait(int epfd,struct epoll_event * event ,int maxevents,int timeout);
这就是上面提到的阻塞了,参数 events 用来从内核得到事件的集合,maxevents 用来告诉内核 这个events有多大,这个maxevents 的值不能大于创建 epoll_create()时的size,参数timeout是超时事件(毫秒,0 会立即返回,-1将一直阻塞)
epoll 的创建和注册是在一个函数中
void Looper::rebuildEpollLocked() {
....
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
....
}
这个函数是在native 的 Looper的构造方法中调用的。
既然有阻塞,就得有唤醒
MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
在向队列塞消息的时候,可以看到 在这里有个唤醒操作,这个操作最后是调用到Looper.cpp 的wake方法中
void Looper::wake() {
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
mWakeEventFd.get(), nWrite, strerror(errno));
}
}
}
唤醒的方法就是通过 linux pipe机制 向 监听的fd中写入数据,就会唤醒了。
总的来说就是,handler 在没有任务执行的时候,就会通过epoll机制阻塞,让出cpu资源,当向队列中发送消息的时候,就会唤醒向监听的文件描述符中写入数据,唤醒epoll , 来继续处理消息。
消息屏障
说完了阻塞机制,我们再说说消息屏障。
消息屏障就是屏障不重要的消息,优先执行重要的消息。
什么才是重要的消息呢? 例如 UI 的绘制,点击事件等。
handler 发送的消息分为三种:
- 普通消息
- 屏障消息
- 异步消息
从MessageQueue中取消息的时候,可以看到这样一种情况:
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());
}
我们都知道 message的target是用来分发数据的handler ,那tartget 怎么能为空呢?那是因为 target == null 就代表此消息为屏障消息。
屏障消息 就是 开始处理异步消息的标志,handler在收到屏障消息后,就开始处理异步消息,普通消息暂缓处理。
MessageQueue.java
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) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
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;
}
return token;
}
}
这就是发送一个屏障消息,也是通过时间进行排序,和其他消息唯一不同的就是没有target。
这个方法是在ViewRootImpl中调用的:
void
() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//发送一个屏障消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//执行一个UI刷新的任务
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
然后再看mChoreographer 任务的执行
最后会调用到这里
Choreographer.java
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
.......
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
//将消息设置为异步消息
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
这是发送一个异步消息,然后再看处理:
MessageQueue.java -> next 方法
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 {
.....
return msg;
}
在这里 当 target == null 的时候,也就是消息屏障开启的时候,取出 异步消息,然后返回,进行分发处理。
有开启屏障也就有关闭屏障:
private void removeCallbacksInternal(int callbackType, Object action, Object token) {
synchronized (mLock) {
mCallbackQueues[callbackType].removeCallbacksLocked(action, token);
if (action != null && token == null) {
mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action);
}
}
}
将屏障消息移除,就可以正常处理普通消息了。
我们连贯一下, ViewRootImpl在处理UI 事件之前,先发送一个屏障消息,告诉handler优先处理异步消息,然后Choreographer发送异步消息(msg.setAsynchronous(true)),异步消息处理完之后,然后再发送一个移除屏障的消息。
Handler就是通过这种机制保障我们UI界面流畅刷新。
总结
至此,这篇文章就水完了,主要是讲了 handler的epoll阻塞机制 和handler的消息屏障。主要是让大家有更深层次的了解,不在局限于表面执行原理。
您的点赞收藏就是对我最大的鼓励! 欢迎关注我,分享Android干货,交流Android技术。 对文章有何见解,或者有何技术问题,欢迎在评论区一起留言讨论!最后给大家分享一些Android相关的视频教程,感兴趣的朋友可以去看看。