Handler源码解析系列一

版权声明

版权声明:本文为博主原创文章,转载请注明出处+地址

什么是Handler消息机制

Android内部的Handler消息机制,是将子线程处理的结果,通过Handler异步回调给主线程,让主线程去进行操作的一个机制。

Handler消息机制的工作流程图

Handler调用流程图.png

Handler的源码解析

Handler使用案例

首先,给大家简单的展示下Handler的使用流程代码

/**
 * Function:简述Handler使用方式
 *
 * @author wanzi Created on 2019/4/28
 */
public class HandlerActivity extends AppCompatActivity {
    private MyHandler mHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mHandler = new MyHandler();
        Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.what = 1;
                mHandler.sendMessage(msg);
            }
        }, 5000, TimeUnit.MILLISECONDS);
    }

    private static class MyHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                Log.d("HandlerTestActivity", "Handler发来贺电");
            }
        }
    }
}

Handler是如何将消息加入到消息队列?

分析源码,我们首先要找到切入点,mHandler.sendMessage(msg),代码从这个函数开始调用的handler,那么继续往下追踪,我们来研究下,在发送消息之后,Handler干了什么?
Handler.java

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
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);
    }

在经过层层调用之后,我们发现调用到了enqueueMessage方法,根据字面意思,就是加入消息队列,那我们继续看里面的实现
Handler.java

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  • 将当前的handler对象赋值给了msg.target
  • 调用了messagequeue的enqueueMessage方法
    MessageQueue.class
 boolean enqueueMessage(Message msg, long when) {
       ...

        synchronized (this) {
         ...
            if (p == null || when == 0 || when < p.when) {
           ...
            } else {
                ...
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

          ...
        }
        return true;
    }

到此为止,msg算是终于成功的加入到消息队列当中,当然MessageQueue.enqueueMessage的内容远不止这么简单,里面包含一些异常处理,唤醒机制等等,单独写一篇文章详细的讲解。

Handler如何从消息队列获取消息?

主线程的Looper如何创建?

说到获取消息,我们还得从Looper说起,大家都知道,在主线程中创建的Handler对象,是自带Looper对象的,那么主线程中的Looper是从哪里创建的呢?
我们知道,程序启动开始的入口在ActivityThread.class的main函数,我们来看看代码
ActivityThread.class

public static void main(String[] args) {
      ...
        Looper.prepareMainLooper();

       ...
        Looper.loop();

        ...
    }

简化之后,我们可以看出,在main函数中,有两处和Looper相关的代码,那么我们分别来看下
** Looper.class**

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

 private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
  • prepareMainLooper最终调用到的是prepare函数
  • prepare的quitAllowed参数值为false,意味着在主线程中,不允许退出
  • 在prepare函数中,我们new了一个Looper对象,放在了一个ThreadLocal的对象中保存
  • sMainLooper 是将sThreadLocal中保存的值取出,赋值给它

ThreadLocal的简单介绍

在整个Looper的创建以及获取的过程,其实主要跟ThreadLocal的set,get函数相关,那么我们就来看看这两个函数的源码

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

public T get() {
        Thread t = Thread.currentThread();
        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();
    }
  • set函数虽然只需要传value值进来,但是其实它是获取的当前线程作为key值,将其保存,这也就说明为什么一个线程只对应来一个looper,因为他的存储的数据结构只支持一对一的关系。
  • get函数也是通过以当前线程作为key将对应的entry取出,返回给调用方

那么以上就是mainLooper的创建的主要流程

主线程的Looper如何开启消息轮询的呢?

大家回顾下前面ActivityThread.main方法,我们还有一行代码没有分析,那就是Looper.loop,根据字面意思我们也能猜出,这是开始进行循环,话不多说,上代码
Looper.class

 public static void loop() {
        final Looper me = myLooper();
    ...
        final MessageQueue queue = me.mQueue;
    ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
    ...
    ...
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
          ...
    }

这里我们同样省略了很多源码,只分析主要流程

  • 通过myLooper()获取到当前线程的looper对象,上面也带大家一起分析过源码了,有不清楚的可以回过去看看
  • 通过拿到的looper对象去获取到当前的消息队列
  • 进入死循环
    • 不断的从队列中获取新的消息
    • 获取到消息后,msg.target.dispatchMessage(msg)

以上就是Looper开启轮询的整个调用流程。

下面我带大家一起看下几个问题:

  1. MessageQueue.next是如何拿到message的呢?
    MessgeQueue.class
  Message next() {
      ...
        for (;;) {
           ...
            synchronized (this) {
              ...
                Message prevMsg = null;
                Message msg = mMessages;
                ...
                if (msg != null) {
                    if (now < msg.when) {
                       ...
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                       ...
                        msg.markInUse();
                        return msg;
                    }
                } else {
                  ...
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

             ...
    }
  • 在next函数中也有一个死循环,当获取到我们的msg之后,跳出循环
  • next函数,就是从当前的message获取到nextMessage,并将其返回给looper

看完之后,你一定还有很多疑问,为什么要用到死循环?当消息队列为空的时候,next函数会怎么办?等等等等,跟MessageQueue.enqueueMessgae方法一样,next函数的实现大有文章,单独为大家写一篇详细介绍,这里只作简单的流程分析。

  1. 什么情况下msg为空,跳出整个循环呢?
    这一块先跟大家简单的介绍一下,在next函数中,我们发现有return 为 null的情况,取决于一个叫mQuitting的参数,其实就是调用了MessageQueue.quit()方法,将其设置为true。但是在主线程中,大家还记得我们在Looper.prepare传入的false参数吗?它不允许主线程的MessageQueue执行quit方法,直接抛出异常。
 void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
  1. msg.target.dispatchMessage(msg)是怎么回调到handler.handleMessage方法的呢
    ** Message.class **
   /*package*/ Handler target;

是的,正如上面我们所预料的,target其实是一个handler的对象,在Handler.enqueueMessage方法中,我们将msg与handler进行了关联,那么在Looper.loop里面,msg.target.dispatchMessage(msg),根据源码可以得知,我们将其回调给了handleMessage进行事件处理。

/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

以上就是针对Handler源码的一个简单的分析,后续会对一些细节化的设计,进行专题讲解,敬请关注。

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