-
2019-12-21 15:08:35
handler实现消息延迟发送的原理
handler每次发送消息,不管是否发送空消息,最终都会调用到如下方法中:
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } //时间为当前毫秒时间+需要延迟的毫秒时间 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } 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; //mAsynchronous是在handler初始化的时候就确定了的,所以同一个handler发送的消息不会出现同步与异步消息混合发送的情况 if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis);
MessageQueue中enqueueMessage方法主要负责将从handler发送过来的message根据when的大小来添加到单向链表中,when的数据越大在链表中的位置越靠后,
boolean enqueueMessage(Message msg, long when) { ...... synchronized (this) { .....处理正在退出的情况 msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // 当前链表为空,则添加为新的表头,如果当前阻塞,则唤醒队列 msg.next = p; mMessages = msg; needWake = mBlocked; } else { // 将消息插入到链表中,when越大越靠后,通常我们不需要唤醒队列,除非该消息是最早的消息 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; }
该方法是将消息加入到链表中,是关键代码块加入了同步处理,这里跟从队列中获取消息想对应,都采用同步,防止出现添加与获取之间时间差导致异常问题。
消息获取到之后会检测当前时间与msg执行时间when,如果小于when,则通过nativePollOnce设置一个阻塞延迟唤醒,如果不是,则将该异步消息返回给handler去执行消费掉
消息获取如下:Message next() { //初始化成功,ptr就不可能为0 final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //阻塞延迟,该方法最终通过linux的epoll机制实现,调用方法为epoll_wait nativePollOnce(ptr, nextPollTimeoutMillis); //同步从链表中获取异步msg synchronized (this) { //一般情况下不会一直卡这里,往往都是异步消息 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 = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 得到可执行消息,将其返回 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; } ...... } }
延时功能主要在nativePollOnce中实现,代码如下:
\frameworks\base\core\jni\android_os_MessageQueue.cppstatic void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); 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; if (mExceptionObj) { env->Throw(mExceptionObj); env->DeleteLocalRef(mExceptionObj); mExceptionObj = NULL; } }
这里调用到了looper.cpp中的方法pollOnce
\system\core\libutils\Looper.cppint Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { int result = 0; for (;;) { 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 DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - returning signalled identifier %d: " "fd=%d, events=0x%x, data=%p", this, ident, fd, events, data); #endif if (outFd != NULL) *outFd = fd; if (outEvents != NULL) *outEvents = events; if (outData != NULL) *outData = data; return ident; } } if (result != 0) { #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - returning result %d", this, result); #endif if (outFd != NULL) *outFd = 0; if (outEvents != NULL) *outEvents = 0; if (outData != NULL) *outData = NULL; return result; } result = pollInner(timeoutMillis); } } int Looper::pollInner(int timeoutMillis) { #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis); #endif // Adjust the timeout based on when the next message is due. 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; } #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - next message in %" PRId64 "ns, adjusted timeout: timeoutMillis=%d", this, mNextMessageUptime - now, timeoutMillis); #endif } // Poll. int result = POLL_WAKE; mResponses.clear(); mResponseIndex = 0; // We are about to idle. mPolling = true; struct epoll_event eventItems[EPOLL_MAX_EVENTS]; //在指定文件描述符上等待IO时间,直到timeoutMillis超时时间到 int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); // No longer idling. mPolling = false; // Acquire lock. mLock.lock(); // Rebuild epoll set if needed. if (mEpollRebuildRequired) { mEpollRebuildRequired = false; rebuildEpollLocked(); goto Done; } // Check for poll error. if (eventCount < 0) { if (errno == EINTR) { goto Done; } ALOGW("Poll failed with an unexpected error: %s", strerror(errno)); result = POLL_ERROR; goto Done; } // Check for poll timeout. if (eventCount == 0) { #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - timeout", this); #endif result = POLL_TIMEOUT; goto Done; } // Handle all events. #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount); #endif for (int i = 0; i < eventCount; i++) { int fd = eventItems[i].data.fd; uint32_t epollEvents = eventItems[i].events; if (fd == mWakeEventFd) { if (epollEvents & EPOLLIN) { awoken(); } else { ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents); } } else { ssize_t requestIndex = mRequests.indexOfKey(fd); if (requestIndex >= 0) { int events = 0; if (epollEvents & EPOLLIN) events |= EVENT_INPUT; if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT; if (epollEvents & EPOLLERR) events |= EVENT_ERROR; if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP; pushResponse(events, mRequests.valueAt(requestIndex)); } else { ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is " "no longer registered.", epollEvents, fd); } } } Done: ; // Invoke pending message callbacks. mNextMessageUptime = LLONG_MAX; while (mMessageEnvelopes.size() != 0) { 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<MessageHandler> handler = messageEnvelope.handler; Message message = messageEnvelope.message; mMessageEnvelopes.removeAt(0); mSendingMessage = true; mLock.unlock(); #if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d", this, handler.get(), message.what); #endif handler->handleMessage(message); } // release handler mLock.lock(); mSendingMessage = false; result = 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 == POLL_CALLBACK) { int fd = response.request.fd; int events = response.events; void* data = response.request.data; #if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p", this, response.request.callback.get(), fd, events, data); #endif // Invoke the callback. Note that the file descriptor may be closed by // the callback (and potentially even reused) before the function returns so // we need to be a little careful when removing the file descriptor afterwards. int callbackResult = response.request.callback->handleEvent(fd, events, data); if (callbackResult == 0) { removeFd(fd, response.request.seq); } // 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(); result = POLL_CALLBACK; } } return result; }
至此,handler发送延时消息就通过向messagequeue中按照消息执行时间when添加链表后,通过next方法不断获取出来,对比当前时间与执行时间的大小,设置超时阻塞等待时间nextPollTimeoutMillis,然后调用本地方法通过looper.cpp中使用epoll_wait实现阻塞等待超时时间到达。
但是,如果正在等待超时时间过程中,又有新的消息发送过来呢 ?如何处理,
next方法中如果在阻塞等待后,mBlocked为true,在添加消息到链表之后,如果是添加在链表前面,则会调用唤醒方法:needWake = mBlocked && p.target == null && msg.isAsynchronous(); ...... if (needWake) { nativeWake(mPtr); }
\frameworks\base\core\jni\android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->wake(); } void NativeMessageQueue::wake() { mLooper->wake(); }
\system\core\libutils\Looper.cpp
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, &inc, sizeof(uint64_t))); if (nWrite != sizeof(uint64_t)) { if (errno != EAGAIN) { LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s", mWakeEventFd, strerror(errno)); } } }
这里通过向mWakeEventFd文件描述符写入数据,将epoll_wait唤醒,停止阻塞,正常的去执行发送不需要延迟的消息。
更多相关内容 -
一切从Android的Handler讲起(五):延迟消息实现原理与消息机制的基本原理
2022-04-23 14:38:26一切从Android的Handler讲起(五):延迟消息实现原理与消息机制的基本原理 在一切从Android的Handler讲起(四):Looper消息获取中,肥柴的分析都是基于及时消息,那对于诸如postDelay的延迟消息,Handler中又...一切从Android的Handler讲起(五):延迟消息实现原理与消息机制的基本原理
在一切从Android的Handler讲起(四):Looper消息获取中,肥柴的分析都是基于及时消息,那对于诸如postDelay的延迟消息,Handler中又是如何实现其延迟出发的呢?
一、延迟消息实现原理
我们重新来回顾一下MessageQueue内的获取消息的next()方法的源码。
/** MessageQueue.class */ @UnsupportedAppUsage Message next() { 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(); } /** 注释1 Message获取 */ 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) { /** 注释2 计算Message执行时间与当前时间的差值,即延迟时间 */ 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 { /** 注释3 无需延迟,立即返回Message进行后续处理 */ // 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. /** 注释4 没有IdelHandler,重新进行循环 */ 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、首次进入next方法,nativePollOnce(long ptr, int timeoutMillis)从MessageQueue中获取表头Message;
2、获取Message的执行时间与当前时间进行判断,计算表头Message是否需要延迟,延迟时间为nextPollTimeoutMillis;
3、若当前时间小于Message执行时间,即now < msg.when,那么立即返回当前Message交由Handler处理;
4、需要延迟,则判断是否存在IdelHandler,不存在则进入下一个循环,执行nativePollOnce方法;
5、此时nativePollOnce(long ptr, int timeoutMillis)的入参nextPollTimeoutMillis即为需要延迟的时间,等待延迟时间后在触发获取Message;
从这里我们就知道:next()中如果当前链表头部的Message是延迟消息,则根据延迟时间进行消息队列阻塞,不返回给Looper Message,并设置定时唤醒,唤醒后,返回Message给Looper处理。
二、消息机制的基本原理
我们上面提到,如果是延迟消息,则通过nativePollOnce(long ptr, int timeoutMillis)设置了定时器延迟唤醒,那灵魂三连问来了。
1、nativePollOnce获取的是链表表头信息,那MessageQueue如何保证链表内获取的消息顺序从而保证执行顺序?
事实上,当调用enqueueMessage(Message msg, long when)方法,MessageQueue会根据Message的执行时间msg.when进行排序,链表头的延迟时间小,尾部延迟时间最大。
2、如果在延时唤醒的过程中,又来了一个立即执行的message又该如何呢?
依照上面的思路,立即执行的消息同样也会先入链表,然后唤醒线程获取表头message,看是否到了执行时间。由于立即执行的消息其实是一个延时为0的message,在一个延迟的链表中,必然会放入表头,而且是无延迟的,所以会立即取出返回给loop去执行了,loop处理完消息,继续来拿表头的message。
3、当整个链表都是延迟执行的message时,如果此时插入的message也是延时执行的,是否一定要唤醒呢?
如果插入的message并非插入表头,说明拿的下一个message也不是自己,完全可以让线程继续休眠,没有必要唤醒,因为此时的定时器到期唤醒后拿到的正是待返回和执行的表头message。
因此我们对于消息机制的基本原理有如下的总结:
1). 消息是通过enqueueMessage(msg)方法插入消息链表中的,并且按照message.when排序,链表头的延迟时间小,尾部延迟时间最大。
2). Looper.loop()通过MessageQueue中的next()去取消息。
3). next()中如果当前链表头部的message是延迟消息,则根据延迟时间进行消息队列会阻塞,不返回给Looper message,并设置定时唤醒,唤醒后,返回message给looper处理。
4). 如果在looper处于休眠中(要么链表为空,要么整个链表均为延时message),此时若有新的消息插入到链表头部则直接唤醒线程,looper继续判断步骤3)。
5). 最终Looper将表头message交给handler中的handleMessage处理,然后继续调用MessageQueue的next()方法,如果刚刚的延迟消息还是时间未到,则计算时间继续阻塞。
三、肥柴总结
肥柴总结一下延迟消息实现原理与消息机制基本原理的核心:只有当表头来了新消息,才会唤醒Loop来获取,Message要么立即执行,要么Loop刷新自我唤醒的定时器继续睡眠。
- - - - - 延迟消息实现原理与消息机制的基本原理篇完 - - - - -
-
深入理解Handler(三) --- Handler发送延时消息实现
2020-10-06 18:34:30我们在面试过程当中经常会被问到 Handler是如何发送延时消息的 消息延时处理关键源码分析 这里只选取部分代码,完整链路流程可以参考这篇 从源码角度来理解Android线程间消息传递机制 这里我们先看插入消息的处理 ...- 消息延时是如何实现的
- 是发送延时了,还是消息处理延时了
- 延时精准度怎么样
我们在面试过程当中经常会被问到 Handler是如何发送延时消息的
消息延时处理关键源码分析
这里只选取部分代码,完整链路流程可以参考这篇 从源码角度来理解Android线程间消息传递机制
这里我们先看插入消息的处理
MessageQueue::enqueueMessage
boolean enqueueMessage(Message msg, long when) { // 每一个普通Message必须有一个target 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) { if (mQuitting) { //正在退出时,回收msg,加入到消息池 msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支 //when什么时候会等于0,在调用sendMessageAtFrontOfQueue时 msg.next = p; mMessages = msg; needWake = mBlocked; } else { //将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非 //消息队头存在阻塞,并且同时Message是队列中最早的异步消息 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; }
MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。排在越前面的越早触发,那我们现在应该了解到了,这个所谓的延时呢,不是延时发送消息,而是延时去处理消息,我们在发消息都是马上插入到消息队列当中。
我们这里插入完消息之后,怎么又保证在我们预期的时间里处理消息呢,接下来我们看如何获取消息
MessageQueue::next()
@UnsupportedAppUsage Message next() { ... for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回 nativePollOnce(ptr, nextPollTimeoutMillis); //如果阻塞操作结束,则去获取消息 synchronized (this) { // 去获取下一条消息 final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //当消息的Handler为空时,则查询异步消息 if (msg != null && msg.target == null) { // 查找队列中的下一个异步消息 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { //当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 获取一条消息,并返回 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); //设置消息的使用状态,即flags |= FLAG_IN_USE msg.markInUse(); return msg; //成功地获取MessageQueue中的下一条即将要执行的消息 } } else { //没有消息 nextPollTimeoutMillis = -1; } ... } }
msg != null
我们看下这部分,如果当前时间小于头部时间(消息队列是按时间顺序排列的),那就更新等待时间nextPollTimeoutMillis
,等下次再做比较
如果时间到了,就取这个消息并返回。
如果没有消息,nextPollTimeoutMillis被赋为-1,这个循环又执行到nativePollOnce
继续阻塞nativePollOnce最终执行到的函数大家也可以看看
Looper.cpp:: pollInner
int Looper::pollInner(int timeoutMillis) { ... // Poll. int result = POLL_WAKE; mResponses.clear(); mResponseIndex = 0; mPolling = true;//即将处于idle状态 struct epoll_event eventItems[EPOLL_MAX_EVENTS]; int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);//等待事件发生或者超时,在nativeWake()方法,向管道写端写入字符,则该方法会返回; ... return result; }
这里我们可以看到是由
epoll_wait
来阻塞的。延时消息小结
通过上述片段我们了解到
- 消息队列是按消息触发时间排序的
- 设置
epoll_wait
的超时时间,使其在特定时间唤醒,这里我们先计算当前时间和触发时间有多长,这个差值作为epoll_wait
的超时时间,epoll_wait
超时的时候就是消息触发的时候了,就不会继续堵塞,继续往下执行,这个线程就会被唤醒,去执行消息处理
Handler发送消息的 Delay 可靠吗?
答案是不靠谱的,引起不靠谱的原因有如下
- 发送的消息太多,Looper负载越高,任务越容易积压,进而导致卡顿
- 消息队列有一些消息处理非常耗时,导致后面的消息延时处理
- 大于Handler Looper的周期时基本可靠(例如主线程>50ms)
- 对于时间精确度要求较高,不要用handler的delay作为即时的依据
如何优化保证可靠性
消息精简,从数量上处理
- 队列优化,重复消息过滤
- 互斥消息取消
- 复用消息
这几点主要确保处理的消息精简,避免资源浪费
消息空闲IdleHandler
MessageQueue.IdleHandler ideHandler =new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { return false; } }; Looper.myQueue().addIdleHandler(ideHandler);
使用完移除消息
使用独享的Looper(HandlerThread)
HandlerThread handlerThread = new HandlerThread("A-Thread"); handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper());
这里给线程设置名字,有利于我们对当前线程监控,确保我们的功能稳定
-
Android消息机制、Handler工作原理和十万个为什么之为什么postDelayed可以延时
2018-08-20 00:02:19Android的消息机制主要是指Handler的运行机制以及支撑Handler工作的MessageQueue和Looper的工作过程。Handler主要作用是将一个任务切换到某个指定的线程中执行。由于在Android3.0之后规定访问UI只能在主线程中进行,...Android消息机制概述
Android的消息机制主要是指Handler的运行机制以及支撑Handler工作的MessageQueue和Looper的工作过程。Handler主要作用是将一个任务切换到某个指定的线程中执行。由于在Android3.0之后规定访问UI只能在主线程中进行,所以Handler在此有了用武之地。为什么不再允许在子线程中访问UI呢?因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能导致UI控件处于不可预期的状态,为什么不对UI控件的访问加上锁机制呢?缺点有两个:首先加锁机制会让UI访问的逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻某些线程的执行,有可能导致程序ANR。
MessageQueue就是消息队列,它的内部存储的是一组消息,采用单链表的数据结构来存储消息列表,对外提供插入和删除工作。Looper是消息循环,由于MessageQueue只是一个消息的存储单元,它不能处理消息,Looper就是用来填补这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有就处理,没有就一直等待。在使用Handler的时候必须为线程创建Looper,否则就会报错,但是在UI线程中使用Handler不需要创建Looper,这是因为UI线程被创建的时候就已经初始化了Looper,因此在子线程中使用Handler必须要创建Looper。
Handler创建完毕后,此时其内部的Looper以及MessageQueue就可以和Handler一起协同工作,然后通过Handler的post方法将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法发送一个消息,,这个消息同样会在Looper中处理,post方法最终也是通过send方法完成的。当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就会被调用,注意:Looper运行在创建Handler所在的线程中,这样一来Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了。过程如下图:
Android消息机制分析
Android消息机制分析将围绕ThreadLocal、Handler、MessageQueue和Looper的工作原理来讨论。
ThreadLocal
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后只有在制定线程中可以获取到存储的数据,对其他线程来说则无法获取到数据。这个东西有点像打游戏的分区,比如打王者荣耀,我在11区有一个武则天,在14区有一个芈月,但是在11区我无法使用14区的芈月,在这里玩家就像是ThreadLocal,不同的区就是不同的线程。
ThreadLocal的使用场景:
第一:当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal,比如对Handler来说,它需要获取当前线程的Looper,Looper的作用于就是线程并且在不同线程具有不同的Looper,这时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。
第二:在复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这时候就可以采用ThreadLocal。
下面用一个例子来有演示下ThreadLocal:
private ThreadLocal<Boolean> mBooleanThreadLocal=new ThreadLocal<>(); mBooleanThreadLocal.set(true); Log.v(TAG,"[Thread#main]mBooleanThreadLocal="+mBooleanThreadLocal.get()); new Thread("Thread#1"){ @Override public void run() { super.run(); mBooleanThreadLocal.set(false); Log.v(TAG,"[Thread#1]mBooleanThreadLocal="+mBooleanThreadLocal.get()); } }.start(); new Thread("Thread#2"){ @Override public void run() { super.run(); // Looper.prepare(); // Handler handler=new Handler(); // Looper.loop(); Log.v(TAG,"[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.get()); } }.start();
最终运行结果:
从上面就可以看出在主线程、线程1、线程2中ThreadLocal的get方法分别是不同的值。
ThreadLocal工作原理
首先看Thread的set方法:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
在Android24之前Thread的set方法如下:
public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); }
其实实现原理基本一致。
在Android24之前会通过values方法来获取当前线程中的ThreadLocal数据,如何获取呢?其实获取的方式也是很简单的,在Thread类的内部有一个成员专门用于存储线程的ThreadLocal的数据:ThreadLocal.Values localValues ,因此获取当前线程的ThreadLocal数据就变得异常简单了。如果localValues的值为null,那么就需要对其进行初始化,初始化后再将ThreadLocal的值进行存储。”下面看一下ThreadLocal的值到底是如何在localValues 中进行存储的。在localValues 内部有一个数组:private Object[] table ,ThreadL ocal的值就存在在这个table数组中。下 面看一下localValues是如何使用put 方法将ThreadLocal的值存储到table数组中的,如下所示。
void put(ThreadLocal<?> key, Object value) { cleanUp(); // Keep track of first tombstone. That's where we want to go back // and add an entry if necessary. int firstTombstone = -1; for (int index = key.hash & mask;; index = next(index)) { Object k = table[index]; if (k == key.reference) { // Replace existing entry. table[index + 1] = value; return; } if (k == null) { if (firstTombstone == -1) { // Fill in null slot. table[index] = key.reference; table[index + 1] = value; size++; return; } // Go back and replace first tombstone. table[firstTombstone] = key.reference; table[firstTombstone + 1] = value; tombstones--; size++; return; } // Remember first tombstone. if (firstTombstone == -1 && k == TOMBSTONE) { firstTombstone = index; } } }
通过上述代码,我们可以带出一个存储规则, 那就是ThreadLocal的值在table 数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下-一个位置,比如ThreadLocal的reference 对象在table数组中的索引为index, 那么ThreadLocal 的值在table 数组中的索引就是index+1。 最终ThreadLocal的值将会被存储在table数组中: table[index + 1]= value。
在Android24之后,是通过ThreadLocalMap来存储线程以及线程对应的值,ThreadLocalMap是ThreadLocal的一个内部类,实现如下:
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private static final int INITIAL_CAPACITY = 16; private Entry[] table; private int size = 0; private int threshold; // Default to 0 private void setThreshold(int len) { threshold = len * 2 / 3; } ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } } private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } } …… }
存储方式有点类似Map,其核心也是通过线程以及存储的值对应来存放数据。因此在不同线程中访问同一个ThreadLocal的set和get方法,它们对ThreadLocal 所做的读/写操作仅限于各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不千扰地存储和修改数据。
MessageQueue
MessageQueue主要包含两个操作:插入和读取,读取本身会伴随删除操作,插入读取对应的方法分别是:enqueueMessage和next。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) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; 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 { 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; } if (needWake) { nativeWake(mPtr); } } return true; }
next代码如下:
Message next() { …… 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; } …… }
next方法是一个无限循环的方法,如果消息队列中没有消息,那么next就会一直阻塞在这里,当有新消息来时,next方法会返回这条消息并将其从单链表中移除。
Looper
Looper在Android的消息机制中扮演者消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就立即处理,否则就一直阻塞在那里。通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环,如下所示:
new Thread("Thread#2"){ @Override public void run() { super.run(); Looper.prepare(); Handler handler=new Handler(); Looper.loop(); Log.v(TAG,"[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.get()); } }.start();
loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null。 当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。也就是说,Looper必须退出,否则loop方法就会无限循环下去。loop 方法会调用MessageQueue的next方法来获取新消息,而next是一个阻塞操作,当没有消息时,next 方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。但是这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。
Handler工作原理
Handler的工作主要包含消息的发送和接收过程,消息发送可以通过post的一些列方法以及send的一系列方法来山西爱你,post的一系列方法最终是通过send的一系列方法来实现的:
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); } 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); }
可以发现,Handler发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper, Looper 收到消息后就开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段。dispatchMessage 的实现如下所示:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
最后用一张图来说明Handler处理消息的过程:
补充:
曾经有个同学去去面试,被问到MessageQueue遵循FIFO(先入先出),为什么postDelayed可以延时?
下面从源码的角度来分析下这问题。
首先我们做一个测试,看这个延时的消息是否被添加到MessageQueue里面去:
private void testPostDelayed(){ boolean result=new Handler().postDelayed(new Runnable() { @Override public void run() { Log.v(TAG,"延迟1秒执行"); } }, 1000); Log.v(TAG,"result="+result); }
运行后结果如下:
结果证明两个问题:1.延时消息第一时间被加入了消息队列,2.postDelayed方法没有BUG(手动滑稽)
我们通过源码来分析postDelayed的流程:
经过N个方法最终还是到了MessageQueue中的enqueueMessage,上面几个方法都没什么好说的,主要看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) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; 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是我们从postDelayed一路带下来的,那么延时相关的核心代码是:
if (p == null || when == 0 || when < p.when) { // 当消息队列中没有消息,或者是按时间来说是该第一个第一个触发的 msg.next = p; mMessages = msg; needWake = mBlocked; } else { // 插入到队列中间,经常我们不需要唤醒事件队列,除非队列的 // 头部有一个障碍或者消息是该队列最早的异步消息 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; }
通过这段代码可以看出,如果通过postDelayed提交一个消息A到消息队列,由于此时会执行MessageQueue的enqueueMessage,由于此时when>0,Looper阻塞,等待下一个消息或者是等Delayed时间结束,自动唤醒;在上述前提下,如果此时Handler有发送一个消息B到消息队列,但此时消息A还在梦游,此时会把B插入到A的前面,然后调用nativeWake()方法唤醒线程,唤醒之后会会重新读取队列,此时B在A前面,因此不需要等待,于是直接返回给Looper;Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间继续阻塞,直到阻塞时间到或者下一次有Message进队。
从上面的分析我们可以清晰的知道postDelayed的工作原理,下次再有人问题来的时候也不至于答不上来。
最后说一下,那个同学就是我……
参考资料:
《Android开发艺术探索》
-
android学习之Handler消息机制原理
2020-07-15 20:07:20android学习之Handler消息机制原理 1.Handler消息机制 2.Handler消息处理流程图 总结: -
源码分析:Handler发送延时消息
2020-01-25 23:31:07源码分析:Handler发送延时消息前言Handler发送延时消息的方式message在MessageQueue中是怎么存储的呢? 前言 Handler的消息分发机制相信都已经知道了,那么Handler发送延时消息是怎么实现的呢? 解答这个问题前先... -
Android Handler延时详解
2020-08-25 09:37:39Handler 发送消息,第一次sendMessage(A,5000),第二次发送sendMessage(B),第三次发送sendMessageDelayed(C,2000); 接收到handlerMessage回调顺序? -->BCA Handler.java ... Handler每次发送消息,... -
从Handler.postDelayed来看看Android怎么实现处理延时消息
2020-09-29 15:04:13Android的消息机制之前有一篇文章有写,里面具体讲到了Handler怎么发送和处理消息的整个过程。感兴趣的同学可以先跳转过去看看 从Handler.post(Runnable r)再一次梳理Android的消息机制(以及handler的内存泄露) ... -
Handler延时消息的实现机制
2020-09-07 16:46:06Handler是如何实现延迟消息的?针对这个问题,我们从源码的角度来剖析一下Handler延时消息的实现逻辑. handler核心的发送消息的方法是sendMessage,有的朋友会说那post呢? PS:post的话其实算是一个handler的语法... -
Handler延时消息是如何实现的?
2021-09-27 19:55:23首先,需要明确一点,Handler 延时消息机制不是延时发送消息,而是延时去处理消息;举个例子,如下: handler.postDelayed(() ->{ Log.e("zjt", "delay runnable"); }, 3_000); 上面的 Handler 不是延时3秒后再... -
【Android】代码延迟执行 以及 Handler原理及应用
2019-05-08 14:44:26昨天在项目中遇到一个错误,Can’t create handler inside thread that has not called Looper.prepare(),意思是不能在没有调用Looper.prepare()的线程里创建handler,我原本是打算延时0.5秒后发送一个网络请求,... -
简述Handler机制原理
2020-10-16 11:21:43Handler是android提供用于更新UI的一套机制,也是消息处理机制。也可以说是消息传递机制。 Handler的主要作用有2个 1.在新启动的线程中发送消息 2.在主线程即UI线程中获取并处理消息 说明:启动一个APP时,... -
Handler延迟消息执行机制堵塞实现机制
2019-03-18 17:27:11Handler延迟消息执行机制,会阻塞吗? 注:本篇文章为个人记录使用,只为后期方便知识点快速查阅到,如有不对请及时告知更正 面试经常被问到到问题 sendMessageDelayed是如何实现延时发送消息的? ... -
Android Handler消息队列的实现原理
2019-08-16 11:18:16我们在写Android程序的时候,有经常用到Handler来与子线程通信,亦或者是用其来管理程序...在这里我将对Android中Handler消息队列的实现进行一个总结,以便深入了解其原理并且巩固其使用方式。 本系列的主要内容如... -
Handler post 方法原理?
2020-11-09 14:48:595.Looper 将新消息交给回调给 handler 中的 handleMessage 后,继续调用MessageQueen 的 next()方法,如果刚刚的延迟消息还是时间未到,则计算时间继续阻塞 三总结 handler.postDelay() 的实现 是通过 MessageQueue ... -
android Handler sendMessageDelayed实现原理
2021-01-11 12:32:54之前在看分析Handler实现原理的,有注意到sendMessageDelayed这个方法,最终都调用到了MessageQueue的enqueue方法,最终都到MessageQueue里面的next方法,至于具体怎么实现delay的,没有仔细研究,今天记录一下 ... -
Handler之消息屏障你应该知道的
2020-11-06 14:37:22由于Handler消息机制的同步屏障问题使用的场景不多,即使不了解可能也不会影响正常开发,因此往往容易被忽视,笔者也是自认比较熟悉Handler的消息机制,在之前同步屏障也算盲区,要想更全面的掌握Handler消息机制,... -
Handler实现原理解析
2020-01-30 16:46:44Handler原理图 主线程中为什么可以直接创建Hanlder Handler使用的两种方式 Handler中构造函数 post,Message调用的原理 Looper Looper的构造函数 Looper的创建 开启Looper循环的loop() MessgaeQueue工作原理 ... -
Android编程中Handler原理及用法实例分析
2021-01-05 23:04:09有关Handler的作用,我们总结为:与其他线程协同工作,接收其他线程的消息并通过接收到的消息更新主UI线程的内容。 我们假设在一个UI界面上面,有一个按钮,当点击这个按钮的时候,会进行网络连接,并把网络上的一个... -
Android_Handler机制原理解析和源码分析
2020-04-14 22:21:34什么是Handler机制 在Android开发的过程中,会将耗时的一些操作放在子线程(work thread)中去执行,然后将执行的结果告诉UI线程(main ...所以Android系统提供了一个消息传递的机制——Handler,可用于将子线程的数... -
Handler postDelayed的实现原理
2020-02-26 11:31:04Handler postDelayed的实现原理 问题描述 Handler.postDelayed()的原理是如何保证延时执行的? 扩展:这样实现的好处是什么? 题目分析 猜测一下 以我们对Handler的了解,内部使用了Looper对消息队列进行循环获取... -
Android 面试题(5):谈谈 Handler 机制和原理?
2019-11-16 20:53:35这一系列文章致力于为 Android 开发者查漏补缺,准备面试。...1、说一下 Handler 消息机制中涉及到哪些类,各自的功能是什么? Handler 主要用于跨线程通信。涉及MessageQueue/Message/Looper/Handl... -
Android Handler postDelayed的原理
2021-06-04 01:20:30前言我们经常用Handler中的postDelayed方法进行延迟操作,像这样new Handler().postDelayed(new Runnable() {@Overridepublic void run() {//操作内容}},100);我们都知道Handler的机制是将消息通过sendMessage()放入... -
Handler机制及原理探究
2017-06-10 14:46:50结合代码和实验,探究Handler的实现原理。 -
Handler API和实现原理
2020-08-18 09:42:05Handler 是一套消息处理机制,是Android SDK来处理异步消息的核心类 Handler有什么作用? Handler 用于子线程与主线程通信。子线程可以通过Handler来通知主线程进行UI更新 Handler如何使用? 参考地址:Android之... -
Android延迟实现的几种解决方法及原理分析
2020-08-28 14:52:12主要给大家介绍了关于Android延迟实现的几种解决方法以及其中的原理分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。 -
Handler机制运行原理和使用
2019-07-21 22:44:23Handler : 对消息的处理 Message : 子线程像主线程发送消息时,用于携带数据 MessageQueue :消息队列,存放Message的一个集合 Looper : 循环从MessageQueue 中取出Message,将其发送给handler 工作流程 主线... -
Android中handler机制原理详解
2021-05-26 14:25:441、handler的作用handler是android线程之间的消息机制,主要的作用是将一个任务切换到指定的线程中去执行,(准确的说是切换到构成handler的looper所在的线程中去出处理)android系统中的一个例子就是主线程中的所有... -
Android 实战中提高Handler发送消息的优先级
2021-09-21 09:05:45在阅读Android 绘制源码的时候,分析了绘制消息message的执行肯定优先所有的message,这个是利用handler的同步屏障机制和异步消息的原理,但是这些机制对开发者不开放的,相关方法也标注为@Hide,我猜测如果对开发者...