前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android中的Handler机制中的问题总结

Android中的Handler机制中的问题总结

作者头像
None_Ling
发布2020-04-09 11:54:22
1K0
发布2020-04-09 11:54:22
举报
文章被收录于专栏:Android相关Android相关

Handler的机制总结

如何保证线程间Looper的唯一性
  • Android中通过Looper.prepare()生成一个Looper对象
  • 将Looper对象保存在ThreadLocal
  • ThreadLocal获取,来保证每个线程间只会有一个Looper对象
  • 当有Message要进入Looper时,从ThreadLocal中获取Looper
Loop的实现原理

Looper通过Linux的epoll来实现阻塞唤醒的.

  • epoll是通过在内核中创建一个虚拟文件来接收外部数据的
  • 会与用户进程mmap映射同一个物理内存区域
  • 当该文件有数据写入时,则内核会将该FD的消息通过共享内存区域传递给用户进程
代码语言:javascript
复制
#include <sys/epoll.h>

// 创建关联的文件,返回文件描述符
int epoll_create(int size); 

// 根据传入的fd来订阅关心的Event
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 

// 同步开始等待事件,当事件发生后,返回正在等待的文件描述符的数量
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
Handler.postDelay的实现

Handler是如何实现定时唤醒的,其实也就是通过epoll中的timeout来进行阻塞唤醒的.

当有消息要加入MessageQueue的时候, 会将计算好的时间when添加到消息链表中

代码语言:javascript
复制
boolean enqueueMessage(Message msg, long when) {
        ...
       synchronized(this){
       // 由于直接给对象加锁,所以不需要再申请Lock或者添加volatile等修饰词
        ...
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 其中mMessage代表当前正在执行的消息体, 如果enque的消息时间比头指针还要早
                // 那么就会唤醒执行任务, 不直接执行任务的原因是这个函数可能会在子线程被调用
                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;
    }

而在MessageQueue.next函数中,会根据当前时间以及下条消息触发的时间来计算下一条消息timeout的时间,然后

代码语言:javascript
复制
for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            // nextPollTimeoutMillis就是本条消息与下条消息中间需要等待的时间
            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) {
                    if (now < msg.when) {
                        //  如果被唤醒时,当前时间比下条消息的时间要小, 则认为还没到处理时间
                        //  则继续计算下一条消息与现在的时间间隔, 重新进入epoll
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //  如果当前时间大于下条时间了,则会返回该条消息分发给对应的
                        //  Handler进行处理
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            // 同时将mMessage设置为下一条要处理的消息
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //  如果链表中没有消息的话,那么就一直等待epoll被唤醒
                    nextPollTimeoutMillis = -1;
                }
                //  判断Looper退出
                if (mQuitting) {
                    dispose();
                    return null;
               }
              ... 
              //  如果链表中没有消息的话, 就认为的Looper处于IDLE状态, 开始处理空闲状态的回调, 请参照下一条
              ...
        }
MessageQueue.IdleHandler的实现

接上文, 当链表中没有消息的时候, MessageQueue.next()会走到以下逻辑开始处理Idlehandler

代码语言:javascript
复制
               // 当消息链表中没有需要处理的消息时
                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)];
                }
                //  收集当前需要回调的Idlehandler
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            //  开始执行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 {
                    // 如果idler返回值为false的话,那么就会移除掉该Handler
                    // 即如果在queueIdle中返回false,则只会响应一次
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        //  从回调中移除该idler
                        mIdleHandlers.remove(idler);
                    }
                }
            }
SyncBarrier的作用与实现
作用

Handler中存在SyncBarrier, SyncBarrier的主要作用是阻拦当前链表中的消息执行, 插入高优先级任务优先执行 .

例如, 当前主线程中有A->B->C->D->E五个消息, 而当E需要高优执行的时候(其实就是Android中的VSync重绘任务), 其中A为SyncBarrier, 则当轮到A执行的时候, MessageQueue会继续遍历消息链表, 然后找到标记为Asynchronous的消息, 进行执行该消息.

PS: 即使使用PostDelay发送的Asynchronous的消息, 也会阻塞等待该消息执行完后, 才会执行其他的消息.

当需要插入任务时, 使用Handler.getLooper().getMessageQueue().postSyncBarrier()插入栅栏, 在要插入的消息中设置Message.setAsynchronous(false), 代表该消息不是同步执行的即可. 而目前应用层是不能调用postSyncbarrier函数的, 它被hide

实现

postSyncBarrier其实只是向MessageQueue中插入了一条消息作为Barrier,而这条消息的特点是:

  • message.target=null
  • message.arg1 = token
代码语言:javascript
复制
private int postSyncBarrier(long when) {
        //  插入一个SyncBarrier
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();  // 标记该消息正在使用
            msg.when = when; // 当前时间戳
            msg.arg1 = token;  // 标识该Barrier的Token值

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    // 遍历消息链表, 找到比当前时间小的消息, 保存到prev以及p中
                    prev = p;  // 代表PrevNode
                    p = p.next;  // 代表NextNode
                }
            }
            // 其实就是插入一条消息
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

MessageQueue中插入了消息之后,就可以开始插入高优消息了, 例如VSYNC的绘制消息, 最终会调用到postCallbackDelayedInternal中:

代码语言:javascript
复制
 private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
         //  其中delayMillis为0
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        //  如果不需要delay的话,那么就直接走scheduleFrameLocked开始执行Vsync信号
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
        //  如果有延迟的话,则会将该消息设置为Asynchronus并且插入到MessageQueue中
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

再来看一下MessageQueue.enqueueMessage中,对于这种Asynchronous的消息是怎么处理的

代码语言:javascript
复制
            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 {
                //  如果Handler所在的线程正在阻塞等待唤醒, 当前已经插入了Barrier, 
                //  并且插入的消息是Asynchronous的消息的话,则需要唤醒对应线程处理
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    //  遍历链表,找到对应时间的消息
                    if (p == null || when < p.when) {
                        break;
                    }
                    //  判断插入消息的上一条消息是否是Asynchronous,也就是在这条消前还有
                    //  需要处理的消息并且这条消息是Asynchronous的话,意味着时间还没到,则不需要唤醒
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // 判断是否需要唤醒, 如果需要的话,就开始唤醒
            if (needWake) {
                nativeWake(mPtr);
            }

而在MessageQueue.next中,会判断当前要处理的message.target是否为空, 如果为空则认为是插入了SyncBarrier, 就开始遍历寻找链表中Asynchronous的消息, 找到了就会返回这条消息,同时使用这条消息计算timeoutMillies来唤醒线程处理.

代码语言:javascript
复制
                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());
                }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Handler的机制总结
    • Handler.postDelay的实现
      • MessageQueue.IdleHandler的实现
        • SyncBarrier的作用与实现
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档