深入理解 Android 消息机制(1)

当存在一个 barrier 消息的时候,会寻找队列中下一个异步任务。而不是按照顺序。 例如3个消息,1,2,3, 2 是异步消息。如果不存在barrier的时候,next的顺序就是 1,2,3 但是如果存在barrier的时候,则是 2,1,3

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;

}

这里如果 next 的 Message 不为空,就返回,并且将它移出队列 在 MessageQueue 为空的时候,会顺便去处理一下 add 过的 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);

}

if (!keep) {

synchronized (this) {

mIdleHandlers.remove(idler);

}

}

查看 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();

}

当 queueIdle() 为 false 的时候,会将它从 mIdleHandlers 中 remove,仔细思考下,我们其实可以利用IdleHandler实现不少功能, 例如

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {

@Override

public boolean queueIdle() {

return false

}

});

我们可以在 queueIdle 中,趁着没有消息要处理,统计一下页面的渲染时间(消息发送完了说明UI已经渲染完了),或者算一下屏幕是否长时间没操作等等。

拿到 Message 对象后,会将 Message 分发到对应的 target 去

msg.target.dispatchMessage(msg);

查看源码

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

handleMessage(msg);

}

}

当 msg 的 callback 不为 null 的时候,即通过 post(Runnable) 发送信息的会执行 handlerCallback(msg) 方法。如果 mCallback 不为 null并且 handleMessage 的结果为 false,则执行 handleMessage 方法。否则会停止分发。

private static void handleCallback(Message message) {

message.callback.run();

}

查看 handlerCallback 方法源码, callback 会得到执行。到这里基本的Android消息机制就分析完了,简而言之就是,Handler 不断的将Message发送到一 根据时间进行排序的优先队列里面,而线程中的 Looper 则不停的从MQ里面取出消息,分发到相应的目标Handler执行。

为什么主线程不卡?

分析完基本的消息机制,既然 Looper 的 looper 方法是一个for(;;;)循环,那么新的问题提出来了。为什么Android会在主线程使用死循环?执行死循环的时候为什么主线程的阻塞没有导致CPU占用的暴增?�

继续分析在源码中我们没有分析的部分:

消息队列构造的时候是否调用了jni部分

nativeWake、nativePollOnce这些方法的作用是什么

先查看MQ的构造方法:

MessageQueue(boolean quitAllowed) {

mQuitAllowed = quitAllowed;

mPtr = nativeInit();

}

会发现消息队列还是和native层有关系,继续查看android/platform/frameworks/base/core/jni/android_os_MessageQueue_nativeInit.cpp中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(nativeMessageQueue);

}

这里会发现我们初始化了一个 NativeMessageQueue ,查看这个消息队列的构造函数

NativeMessageQueue::NativeMessageQueue() :

mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {

mLooper = Looper::getForThread();

if (mLooper == NULL) {

mLooper = new Looper(false);

Looper::setForThread(mLooper);

}

}

这里会发现在mq中初始化了 native 的 Looper 对象,查看android/platform/framework/native/libs/utils/Looper.cpp中 Looper 对象的构造函数

// 简化后的代码

Looper::Looper(bool allowNonCallbacks) :

mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),

mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {

int wakeFds[2];

int result = pipe(wakeFds);

mWakeReadPipeFd = wakeFds[0];

mWakeWritePipeFd = wakeFds[1];

result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);

result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);

mEpollFd = epoll_create(EPOLL_SIZE_HINT);

struct epoll_event eventItem;

memset(& eventItem, 0, sizeof(epoll_event));

eventItem.events = EPOLLIN;

eventItem.data.fd = mWakeReadPipeFd;

result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);

}

这里我们会发现,在 native 层创建了一个epoll,并且对 epoll 的 event 事件进行了监听。

什么是epoll

在继续分析源码之前,我们先分析一下,什么是epoll

epoll是Linux中的一种IO多路复用方式,也叫做event-driver-IO。

Linux的select 多路复用IO通过一个select()调用来监视文件描述符的数组,然后轮询这个数组。如果有IO事件,就进行处理。

select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。

epoll在select的基础上(实际是在poll的基础上)做了改进,epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可。

另一个本质的改进在于epoll采用基于事件的就绪通知方式(设置回调)。在select中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知

关于epoll和select,可以举一个例子来表达意思。select的情况和班长告诉全班同学交作业类似,会挨个去询问作业是否完成,如果没有完成,班长会继续询问。

而epoll的情况则是班长询问的时候只是统计了待交作业的人数,然后告诉同学作业完成的时候告诉把作业放在某处,然后喊一下他。然后班长每次都去这个地方收作业。

大致了解了epoll之后,我们继续查看nativePollOnce方法,同理,会调用native Looper的pollOnce方法

while (mResponseIndex < mResponses.size()) {

const Response& response = mResponses.itemAt(mResponseIndex++);

int ident = response.request.ident;

if (ident >= 0) {

int fd = response.request.fd;

int events = response.events;

void* data = response.request.data;

if (outFd != NULL) *outFd = fd;

if (outEvents != NULL) *outEvents = events;

if (outData != NULL) *outData = data;

return ident;

}

}

在pollOnce中,会先处理没有callback的response(ALOOPER_POLL_CALLBACK = -2),处理完后会执行pollInner方法

// 移除了部分细节处理和日志代码

// 添加了分析源码的日志

int Looper::pollInner(int timeoutMillis) {

if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {

nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);

if (messageTimeoutMillis >= 0

&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {

timeoutMillis = messageTimeoutMillis;

}

}

// Poll.

int result = ALOOPER_POLL_WAKE;

mResponses.clear();

mResponseIndex = 0;

struct epoll_event eventItems[EPOLL_MAX_EVENTS];

// 等待事件发生或者超时

int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

// Acquire lock.

mLock.lock();

// Check for poll error.

// epoll 事件小于0, 发生错误

if (eventCount < 0) {

if (errno == EINTR) {

goto Done;

}

result = ALOOPER_POLL_ERROR;

goto Done;

}

if (eventCount == 0) {

// epoll事件为0,超时,直接跳转到Done

result = ALOOPER_POLL_TIMEOUT;

goto Done;

}

//循环遍历,处理所有的事件

for (int i = 0; i < eventCount; i++) {

int fd = eventItems[i].data.fd;

uint32_t epollEvents = eventItems[i].events;

if (fd == mWakeReadPipeFd) {

if (epollEvents & EPOLLIN) {

awoken(); //唤醒,读取管道里面的事件

} else {

}

} else {

ssize_t requestIndex = mRequests.indexOfKey(fd);

if (requestIndex >= 0) {

int events = 0;

// 处理request,生成response对象,push到相应的Vector

pushResponse(events, mRequests.valueAt(requestIndex));

} else {

}

}

}

Done: ;

// Invoke pending message callbacks.

// 发生超时的逻辑处理

mNextMessageUptime = LLONG_MAX;

while (mMessageEnvelopes.size() != 0) {

// 处理Native端的Message

nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);

if (messageEnvelope.uptime <= now) {

// Remove the envelope from the list.

// We keep a strong reference to the handler until the call to handleMessage

// finishes. Then we drop it so that the handler can be deleted before

// we reacquire our lock.

{ // obtain handler

sp handler = messageEnvelope.handler;

Message message = messageEnvelope.message;

mMessageEnvelopes.removeAt(0);

mSendingMessage = true;

mLock.unlock();

handler->handleMessage(message); // 处理消息事件

} // release handler

mLock.lock();

mSendingMessage = false;

result = ALOOPER_POLL_CALLBACK; // 设置回调

} else {

// The last message left at the head of the queue determines the next wakeup time.

mNextMessageUptime = messageEnvelope.uptime;

break;

}

}

// Release lock.

mLock.unlock();

// Invoke all response callbacks.

// 执行回调

for (size_t i = 0; i < mResponses.size(); i++) {

Response& response = mResponses.editItemAt(i);

if (response.request.ident == ALOOPER_POLL_CALLBACK) {

int fd = response.request.fd;

int events = response.events;

void* data = response.request.data;

int callbackResult = response.request.callback->handleEvent(fd, events, data);

if (callbackResult == 0) {

removeFd(fd); //移除fd

}

// Clear the callback reference in the response structure promptly because we

// will not clear the response vector itself until the next poll.

response.request.callback.clear(); // 清除reponse引用的回调方法

result = ALOOPER_POLL_CALLBACK; // 发生回调

}

}

return result;

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

【附】相关架构及资料

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

…(img-dgRRXgu8-1712015751922)]

[外链图片转存中…(img-EXJno4Mh-1712015751922)]

[外链图片转存中…(img-jgY1lKHd-1712015751922)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

【附】相关架构及资料

[外链图片转存中…(img-BugestRc-1712015751922)]

[外链图片转存中…(img-5A2Et9om-1712015751923)]

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值