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

Handler的机制总结

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

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

  • epoll是通过在内核中创建一个虚拟文件来接收外部数据的
  • 会与用户进程mmap映射同一个物理内存区域
  • 当该文件有数据写入时,则内核会将该FD的消息通过共享内存区域传递给用户进程
#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添加到消息链表中

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的时间,然后

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

               // 当消息链表中没有需要处理的消息时
                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
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中:

 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的消息是怎么处理的

            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来唤醒线程处理.

                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());
                }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容