Handler源码分析

HandlerAndroid os 体系占举头轻重地位,关于一些用法,我想闭着眼睛各位都会写,那么关于源码是要分析的重点,Handler、Looper、MessageQueue、Message同步消息异步消息

Handler采用生产者-消费者模型,Handler就是生产者,Looper则是消费者具体实现给予dispatchMessage ,当然还少不了队列MessageQueue ,队列中的具体对象就是Message

handler流程图.png

发送消息

handler发送消息有俩种sendMessagexxx()postxxx() ,但是都会调用enqueueMessage

Handler.java

//post...
public final boolean post(@NonNull Runnable r) {
     return  sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
     return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
//....

//sendMessage...
public final boolean sendMessage(@NonNull Message msg) {
     return sendMessageDelayed(msg, 0);
}
    
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
      if (delayMillis < 0) {
          delayMillis = 0;
      }
      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    //..
    return enqueueMessage(queue, msg, uptimeMillis);
}



//重点❤ 队列、消息体、插入延迟时间毫秒
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    //默认变量 在handler() 产生 基础创建默认为 false
    if (mAsynchronous) {
       //重要❤ 重要的标识, 区分同步/异步
        msg.setAsynchronous(true);
    }
    //重要❤,入队
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到最终都会调用enqueueMessage

MessageQueue queue: 通过链表形式对Message 进行存储,并通过when 的大小对 Message 进行排序。

Message msg:具体消息对象,long when时间、Handler target发送者、Message next单链表指针

long uptimeMillis: 插入延迟时间、默认0当前

重点❤mAsynchronous

标识区分当前发送的消息是属于什么类型,同步消息 True 或者 异步消息 False

下面在looper具体分析....

消息插入

MessageQueue.java
    
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //保证多线程入队先加锁
    synchronized (this) {
        //....
        msg.markInUse(); //标记正在使用
        msg.when = when; // when 属性
        Message p = mMessages; //拿到链表头部的消息
        boolean needWake;
        //重点❤ 满足以下3种情况之一就把msg插入到链表头部
        //1.队列为null
        //2.当前时间没有延迟0
        //3.插入的时间比链表的对象时间早
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; //如果处于阻塞状态,需要唤醒
        } else {
            //重点❤ 唤醒标识 ,
            //4.如果p != null且msg并不是最早触发的,就在链表中找一个位置把msg插进去
            //5.如果处于阻塞状态,并且链表头部是一个同步屏障(target为null的Message),并且插入消息是最早的异步消息,需要唤醒
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                //当遍历到队尾、或者是  msg的时间比当前时间更早 
                if (p == null || when < p.when) {
                    break;
                }
                //发现了异步消息的存在,不需要唤醒
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            //单链表、插入msg信息
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
        // 唤醒 Native 中的 MessageQueue线程
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

总结

enqueueMessage有返回值,插入是否成功,true表示插入成功,false表示插入失败

同步屏障就是 msg.target=null 即 handler=nullMessage

  1. 插入链表的头部

    • Message触发根据时间when判断,越早触发越会向前排列,当生产者存入消息时,message没有携带hander 或者是 异步消息时间最早就会优先排列
  2. 插入链表中部

    • 如果插入的消息不是空,且插入的消息不是最早有一定的延迟,就会遍历MessageQueue队列找到合适的位置,保证队列的执行顺序有序
  3. 判断是否调用nativeWake方法

    根据状态值是否为true,来决定是否调用nativeWake方法唤醒当前的线程的MessageQueue

    • 如果插入的消息在链表的头部,needWake = true 表示此时nativePollOnce方法进入阻塞状态,等待被唤醒返回
    • 如果插入的消息在链表的中部,如果链表头是一个消息屏障,同时插入的是一个最早的异步消息,需要唤醒。

消息轮动

1、Looper的创建

在Android中应用的程序入口是**ActivityThread.java -- main **方法,而应用消息的循环也是在这里创建,参考源码

public static void main(String[] args) {
       
    //创建消息循环Looper
    Looper.prepareMainLooper();
    //....
    //执行消息循环
    Looper.loop();
    
}
Looper.java 

@Deprecated
public static void prepareMainLooper() {
    //创建不允许退出Looper
    prepare(false);
    //同步锁保证只有一个线程
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //将创建的looper实例赋值给sMainLooper,其专门为UI线程保留,只要某个线程调用了prepareMainLooper方法
        sMainLooper = myLooper();
    }
}


/**
*quitAllowed 表示是否允许Looper运行时退出
**/
private static void prepare(boolean quitAllowed) {
    //Looper只能执行一次
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //把创建Looper实例,并且把实例looper存放到TLS中
    sThreadLocal.set(new Looper(quitAllowed));
}


// 获取当前线程TLS区域的Looper
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

可以看到,线程调用了prepareMainLooper,就代表它成了一个UI线程,其内部调用的prepare()来创建Looper实例,当要获取创建Looper实例时,是通过myLooper拿到,也是就get - ThreadLocal

2、ThreadLocal创建

线程本地存储区(Thread Local Storage,简称TLS),每个线程都有自己的私有本地存储区域,不同线程之间彼此不能访问对方的TLS区域

public class ThreadLocal<T> {
    //有省略...
    
    //获取当前线程TLS区域的数据
    public T get() {
        Thread t = Thread.currentThread();
        //用了Map存放
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    //将value存储到当前线程的TLS区域。
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    
    //获取内部内ThreadLocalMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

   //Map集合
    static class ThreadLocalMap {
     
        private static final int INITIAL_CAPACITY = 16;

        private Entry[] table;
        
        //有省略..
        
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            // Android-changed: Use refersTo()
            if (e != null && e.refersTo(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();
        }
        
    }
}

可以看到类型T泛型,所以这里存储是Looper泛型,sThreadLocalget() / set()操作的类型都是Looper类型

ThreadLocal的作用:每个线程只能保存一个Looper,不同的线程Looper是不同的,这样通过调用Looper.perpare(false),UI线程中就保存了它对应的唯一的Looper实例

当在UI线程中调用Looper.myLooper就返回了UI线程关联的Looper实例

子线程中如果想获取UI线程关联的Looper实例,就需要调用getMainLooper方法

区别:ui线程的looper是不允许退出,而我们一般创建的looper是允许退出

Looper.java

public static void prepareMainLooper() {
    //不允許
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
    
public static void prepare() {
    //允许
    prepare(true);
}

3、MessageQueue创建

在调用Looperprepare方法就会创建Looper的实例,同时会把Looper实例通过ThreadLoca保存到线程中,在创建Looper时还会同时创建MessageQueue

Looper.java
    
private Looper(boolean quitAllowed) {
    //创建MessageQueue对象, * 是否退出传递进去
    mQueue = new MessageQueue(quitAllowed);
    //获取当前线程
    mThread = Thread.currentThread();
}


MessageQueue.java
 
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    //native层init,返回mPtr指针通信
    mPtr = nativeInit();
}

MessageQueue的构造中通过Native方法在native层创建一个属于NativeMeesageQueue的消息队列,然后把NativMessageQueue的地方返回到Java层保存在mPtr中,java层与native层之间通信就是通过mPtr指针

Looper.java
    
//获取meesageQueue对象
public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
}

通过LoopermyQueue方法就能获取到它关联的MessageQueue实例

4、消息轮询

可以看到在UI线程中,在ActivityThread的mian方法在创建Looper后,通过Looper.loop方法就启动了消息循环,这个函数会不断的从MessageQueue中取出消息、处理消息

Looper.java

@SuppressWarnings("AndroidFrameworkBinderIdentity")
public static void loop() {
    //获取looper
    final Looper me = myLooper();
    //...
    for (;;) {
        //返回false 退出循环
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}



@SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {
    //重点❤ 从Looper中取出MessageQueue
    Message msg = me.mQueue.next(); 
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    //....
    try {
        //重点❤   Message.target 就是对应的Handler.dispatchMessage回调消息
        msg.target.dispatchMessage(msg);
    }
    //....
    //重点❤,回收消息
    msg.recycleUnchecked();

    return true;
}

可以看出在Loop()中是一个死循环,通过looper.mQueue.next()来获取出最新的消息,当没有消息返回false进行跳出循环条件

Looper.java

public void quit() {
    mQueue.quit(false);
}

//quitSafely和quit方法的区别是,quitSafely方法会等MessageQueue中所有的消息处理完后才退出,而quit会直接退出
public void quitSafely() {
    mQueue.quit(true);
}

Looperquit()quitSafely()被调用,从而调用MessageQueuequit()来通知消息队列退出

5、出队列

MessageQueue的 next()是最关键的函数

@UnsupportedAppUsage
Message next() {
    //mPtr是在构造中被赋值,是指向native层的MessageQueue
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    /**
    *重点❤  死循环,里面分为两部分:
    * 1、处理java层消息
    * 2、如果没有消息处理,执行IdleHandler
    **/
    for (;;) {
        
        //处理native层消息,是一个阻塞操作  等待nextPollTimeoutMillis时间后唤醒, messageQueue被唤醒
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            //mMessages表示取出java层队列的第一个消息
            Message msg = mMessages;
            //遇到同步屏障  msg.target为null的消息
            if (msg != null && msg.target == null) {
                //在do-while中找到异步消息,优先处理异步消息,其中异步消息时候,isAsynchronous = true
                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;
                    //从mMessages的头部获取一条消息并返回 
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    //设置消息的使用状态,即flags |= FLAG_IN_US
                    msg.markInUse();
                     //返回这个消息
                    return msg;
                }
            } else {
            //msg == null,没有消息
                //设置nextPollTimeoutMillis为-1,准备进入阻塞,等待MessageQueue被唤醒
                nextPollTimeoutMillis = -1;
            }

           //调用了quit方法
            if (mQuitting) {
                dispose();
                return null;
            }

            
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                //没有IdleHandler需要处理,可直接进入阻塞
                mBlocked = true;
                continue;
            }
            
            //有IdleHandler需要处理
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            
            //把mIdleHandlers列表转成mPendingIdleHandlers数组
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }


        //遍历mPendingIdleHandlers数组
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            //取出IdleHandler
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                //执行IdleHandler的queueIdle方法,通过返回值由自己决定是否保持存活状态
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                 // 不需要存活,移除
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        //重置pendingIdleHandlerCount和nextPollTimeoutMillis为0
        pendingIdleHandlerCount = 0;
        //当=0:表示下一次循环会马上从Messages中取出一个Message判断是否到达执行时间
        nextPollTimeoutMillis = 0;
    }
}
消息处理整理:
  • Part1先执行nativePollOnce() ,组赛队列,其中的nextPollTimeoutMillis表示下一次等待的超时时长,当nextPollTimeoutMillis = 0或者达到对应的时间值,那么它就会立即返回
  • nextPollTimeoutMillis = -1 表示在队列MessageQueue中没有消息,会一直等待下去,直到Hanlder发送消息到队列中,执行nativeWake()后,MessageQueue被唤醒,nativePollOnce就会返回,但它此时并不是立即返回,会先处理native层的消息后,再返回,然后获取java层的消息处理
  • nex()会从mMessages链表的表头中获取一个消息,首先判断它是否同步屏障,区分条件为msg.target = null 的 Message

    • 同步屏障:优先处理异步消息,异步消息在执行顺序上会比同步消息优先执行Message.setAsynchronous=true 设置异步消息,常用的消息为同步消息
  • 执行时间if(now == msg.when) 如果达到执行数据,next()就会返回这条消息给Looper处理,并且从链表中删除,如果没有到执行时间就设置nextPollTimeoutMillis 时间等待时长

总结

  1. 创建Looper时候,只能通过Looperprepare()方法创建,在创建Looper时会内部创建一个MessageQueue,并且把Looper保存在线程的ThreadLocal中对应(Map),一个线程只能对应一个Looper,一个Looper只能对应一个MessageQueue
  1. 在创建MessageQueue时候,MessageQueueNativeMessageQueue建立连接,NativeMessageQueue存储地址位于MessageQueue的mPtr字段中,java层与native通过mPtr字段进行通信。
  1. ThradLocal的作用,Looper属于某个线程,而MessageQueue存储在Looper中,所以MessageQueue通过Looper特定的线程上关联,而Handler在构造中又与LooperMessageQueue相互关联,通过Handler发送消息的时候,消息就会被插入到Handler关联的MessageQueue中,而Looper会不断的轮询消息,从MessageQueue中取出消息给相应的Handler处理,所有最终通过Handler发送的消息就会被执行到Looper所在的线程上,这就是 Handler线程切换的原理,无论发送消息Handler对象处于什么线程,最终处理消息都是在Looper所在的线程。
  1. LooperMessageQueue中取出消息后,会交给当前消息的msg.target(Hanlder)处理,在Handler中处理消息的回调优先级为:Message的CallBack > Handler的CallBack > Handler的handleMessage方法
  1. APP在启动的时候ActivityThread.main()方法中的Looper.prepareMainLooper()中已经调用Looper.prepare(false),所以在主线创建Handler无需我们手动调用Looper.prepare(),而在子线程中,如果我们不传递UI线程所属的Looper去创建Handler,那么就需要调用Looper.prepare()后再创建Handler传递消息,主要是因为 Handler 要和某个线程中的MessageQueue 和 Looper 关联,只有调用 Looper.prepare()方法,Looper和MessageQueue才属于某个线程。
  1. 消息池是一个单链表,复用池Message时候,从头部取出,如果取不到,则新建返回,回收Message时候,也是从头插入。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容