Handler详解

Read the fucking source code!

还是源码精妙


当我们面试的时候,经常遇到这样一题,请描述一下Handler的工作方式

然后我们会说,Handler机制包括Handler,Message,MessageQueue,Looper。

Handler会发送Message给MessageQueue,Looper会不断的从Message中取出一个Message,然后调用Handler的handleMessage方法。

当然,还可以说的更详细一点,不过最近感觉,只是了解了使用方法不行,了解了工作机制还是不行,要深入的了解源码,才能感觉到一个优秀的操作系统的伟大之处。

首先我们说一下这几个的数量关系:

一个线程有一个Looper,一个Looper内部有一个MessageQueue,一个MessageQueue有多个Message,并且一个MessageQueue可能有多个Handler的引用,一个Message只对应一个Handler

关于Looper

主线程的Looper

我们都知道一个线程有一个Looper,那么这个怎么实现的呢,我们看源码。

Looper.java


static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;

首先看第一行 有一个ThreadLocal变量,一个线程维护一个ThreadLocal副本,互不干扰,这点大家应该都清楚。因为这个sThreadLocal是static final的,所以我们在任何线程都能拿到这个变量。

第二行有个变量sMainLooper,唯一标识UI线程的Looper,这个Looper怎么实例化的呢,在我们的Application入口处:

 Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        Looper.loop();

我们看到了在这里的“ Looper.prepareMainLooper()”

我们看一下这个方法都干了什么:

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();
        }
    }

第一行“prepare”,我们应该有印象,在子线程里构造Looper,我们会用到Looper.prepare(),其实prepare()方法内部就是调用了这个方法:

Looper.java

public static void prepare() {
        prepare(true);
    }

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));
    }

方法首先判断该线程是不是已经有一个Looper了,就抛出异常

我们看到当传入参数为false的时候,就会执行Looper(quitAllowed)这个构造方法,我们来看一下这个构造方法:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

这一段揭示了Looper和MessageQueue和Thread的关系:Thread有一个Looper,一个Looper有一个MessageQueue!

在这个构造方法中就实例化了一个MessageQueue,这个就是主线程的MessageQueue,mThread就是主线程。

调用构造函数之后,就把一个Looper加入到了sThreadLocal中了。

最后一行调用了myLooper():

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

这个方法就是返回当前的Looper实例。

子线程的Looper

上面我们说,如果子线程想拿到一个Looper要调用Looper.prepare(),这个方法又会调用prepare(true),当参数为true时,我们也调用了Looper()的含参构造方法,至于这个参数什么意思,下面再说。就会把新的looper加入到sThreadLocal中。

Looper.loop()

这个方法就是开始循环:

 public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

这一部分的代码很长,总的来说,就是开启了一个无限循环,不断的从MessageQueue中拿消息,

如果没有消息,就会退出,上面说 queue.next(); // might block,下面我们再来分析MessageQueue。

小结:怎么在子线程和主线程拿到Looper我们搞清楚了,Looper怎么和MessageQueue对应起来的,下面慢慢说。

MessageQueue

MessageQueue在哪实例化的呢,在这:

Looper.java

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

下面来看MessageQueue的构造方法。

MessageQueue.java

 MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

这个变量看名字应该是标记MessageQueue是否可以退出。

Message

一个Message只属于一个Handler,这个在下面会说。

其他三个只是处理消息的媒介,这个才是真正的消息内容,我们看Message都有什么实例变量:

 public int what;
 public int arg1;
 public int arg2;
 public Object obj;

常用的就是这几个,what和obj我们最常用。

what就是用户自定义的标识Message的标记,在Handler处理的时候,可以根据这个what做具体的工作。

obj是Message携带的唯一一个对象。

实例化一个Message对象的时候,最好的方法是调用Message.obtain();

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

spool应该是Message链中的最后一个消息,然后取出这个消息返回,这一段自己没有看太懂,想深入了解的同学可以自己查一下。

小结:Message大致的用法就是这样,比较简单主要是注意要习惯用Message.obtain来获取消息。下面我们来看Handler。

Handler

Handler和Message的关系

一个Message只属于一个Handler,我们看一个Message怎么和一个Handler关联起来的,我们通常发送消息用Handler.sendMessage,我们看这个方法:

Handler.java

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

其实调用了这个方法sendMessageDelayed(msg, 0);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);
    }

这一段就是将Handler发送的message送到Handler对应的Looper的MessageQueue中。

我们看一下enqueueMessage这个方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

找了那么久,终于找到了,其实就是这个msg.target = this,每个Message的Target就是在这设置的。

我们还可以重复利用Message,用这个方法Handler.obtainMessage(int what, Object obj)。

Handler.java

public final Message obtainMessage(int what, Object obj)
    {
        return Message.obtain(this, what, obj);
    }

Handler和MessageQueue的关系

上面我们说发送Message的时候,会将这个Message发送到Handler对应的MessageQueue中,这个MessageQueue怎么来的呢?

我们知道MessageQueue实际上在Looper里,我们看这个Looper怎么得到的:

Handler.java

public Handler() {
        this(null, false);
    }

Handler的构造方法

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

我们看到这个Looper是通过Looper.myLooper()这个方法来的,如果当前线程没有Looper实例,就会抛出异常,所以在子线程实例化Handler的时候,就必须调用Looper.prepare();

当然这也不是唯一的选择,看另一个构造方法:

public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

我们可以传入一个Looper,这个Looper通常是主线程的Looper或者是HandlerThread的Looper。

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

推荐阅读更多精彩内容