Handler 源码解析 (Android Learning Note)

工作流程简述

Handler 涉及的知识点有,Thread,Loop,MessageQueue ,Message。这些对象相互关联,相互配合完成 Handler 的工作。
大体流程示意图如下:


Handle

简单介绍下:
Handler 通过 post Runnable 或 sendMessage 方法来发送要处理的 Message 到消息队列(MessageQueue),这个消息队列是由 Looper 创建,管理的。Thread 中可以调用 Looper 的 loop() 方法,loop() 会无限循环,从消息队列中取出待处理的消息,交给 Handler(Message 的 Target) 来处理,处理完之后,Looper 继续从队列中取下一个消息再给 Handler (Message 的 Target),直到队列中没有消息,退出循环。这个 Looper 是在线程中创建出来并运行的,被 Handler 持有。

下面会结合一些概念,源代码和示例代码介绍流程中的一些细节,帮助理解。

Handler 概念

Handler, (以下翻译自官方文档)Handler 可以发送和处理与它相关的 MessageQueue 中的 Message 和 Runnable。每个 Handler 实例都与一个单独的线程和这个线程的消息队列关联。在创建一个新 Handler 时,它与这个线程和消息队列绑定到了一起,它将消息和任务发送至消息队列,并在消息离开消息队列时执行它们。

Handler 有两个主要的用处:

  1. 在未来某个时间点去调度 Message 和 Runnable。
  2. 在不同的线程中将任务添加到消息队列(线程间通信)。

可以使用 post(Runnable) , postAtTime(Runnable, long) , postDelayed(Runnable, long) , sendEmptyMessage(int) , sendMessage(Message) , sendMessageAtTime(Message, long) , and sendMessageDelayed(Message, long) 这些方法调度消息。 post 版本的方法可以将 Runnable 对象加到消息队列中, sendMessage 版本的方法可以将绑定数据的 Message 对象用 Handler 的 handleMessage(Message) 方法处理(必需实现 Handler 的子类)。

当应用程序的进程被创建的时候,主线程会运行起来一个消息队列用来处理程序的高级对象(activities, broadcast receivers 等等 )和它们创建的任意 windows。你也可以创建自己的线程和主线程通过 Handler 进行通信。

Looper,MessageQueue 的创建和 Looper 的存取

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

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

这是 Handler 的两个构造方法,从方法中可以看到,Handler 必须引用 Looper(mLooper) 和 MessageQueue(mQueue)对象,Looper 是怎么创建?如何传给 Handler 被引用的?可以先从下面的几段代码入手了解。

public void mainThreadHandler(View view) {
    final Handler handler = new Handler();

    final Runnable task = new Runnable() {
        @Override
        public void run() {
            String msg = "Handler Post Runnable 线程名: " + Thread.currentThread().getName()
                    + (isMainThread()?", 是":", 不是") + "主线程";
            makeToast(msg);
        }
    };

    Runnable task2 = new Runnable() {
        @Override
        public void run() {
            String msg = "thread 线程名: " + Thread.currentThread().getName()
                    + (isMainThread()?", 是":", 不是") + "主线程";
            makeToast(msg);
            handler.post(task);
        }
    };
    Thread thread = new Thread(task2, "Sub Thread01");
    thread.start();
}

主线程创建的 Handler ,不用传入 Looper,因为主线程已经调用了 prepareMainLooper(); 方法,拥有了 Looper。而 Handler 是通过前面第一种构造方法中 mLooper = Looper.myLooper(); 代码获得的 Looper。

现在来看 Looper.myLooper(); 中的代码,了解获取 Thread 中 Looper 的过程。

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

//public class ThreadLocal<T>
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Looper.myLooper(); 调用了 sThreadLocal.get() 方法,获取当前线程的 ThreadLocalMap 对象,这个对象就是用来存储 Looper 的,通过 ThreadLocal 做为 key 获取到对应的实体,从而获取我们想要的值,Looper。

哪 Looper 又是怎样存储的呢?

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

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

是调用的 Looper 的 prepare 方法,只不过主线程调用的是 prepareMainLooper() ,子线程调用 prepare(boolean quitAllowed)
必要的校验后,new 出一个新 Looper 对象,使用 sThreadLocal.set(value) 方法存储该 Looper 到当前线程中。这就和之前获取 Looper 的方法对应了起来。

MessageQueue 是在创建 Looper 过程中创建出来的,并被 Looper 所引用。

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

至此,Handler 相关的几个重要的部分都被创建了出来。

将消息添加到消息队列

下面的代码,使用的是传入 Looper 的 Handler 构造方法。

public void testHandler(View v) {
    final Runnable postTask = new Runnable() {
        @Override
        public void run() {
            String msg = "post: " + (isMainThread() ? "是" : "不是") + "主线程, 线程名:" + Thread.currentThread().getName();
            SecondActivity.this.makeToast(msg);
        }
    };

    final HandlerThread outerThread = new HandlerThread("HandlerThread");
    outerThread.start();

    final Runnable threadTask = new Runnable() {
        @Override
        public void run() {
            /* 1 */
            Looper.prepare();
            final Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    if (msg.what == 1) {
                        String msgStr = "send: " + (isMainThread() ? "是" : "不是") + "主线程, 线程名:" + Thread.currentThread().getName();
                        SecondActivity.this.makeToast(msgStr);
                    }
                }
            };
            handler.post(postTask);
            Message message = new Message();
            message.what = 1;
            handler.sendMessage(message);
            Looper.loop();

            /* 2
            final Handler handler = new Handler(outerThread.getLooper());
            handler.post(postTask);
            */

            /* 3
            final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(postTask);
            */
        }
    };
    Thread t = new Thread(threadTask, "subThread");
    t.start();
}

注释1,传入的 Looper 是在 subThread 这个线程中创建的,postTask 和 sendMessage 都会在线程 “subThread” 中执行。
注释2,传入的 Looper 是在 HandlerThread 这个线程中创建的,postTask 会在线程 “HandlerThread” 中执行。
注释3,传入的 Looper 是在主线程中创建的,所以 postTask 会在主线程中执行。
以上线程中创建 Looper 的方法都是一样的,都遵从前面介绍的过程,handler 不同的构造方法大同小异,目的都是获得 Looper。

Post Runnable 其实和 SendMessage 是一样的,最终还是封装成 Message 发送出去,供 Handler 处理。

//Handler Class
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

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

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

//Message Queue
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        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 {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            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;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

代码虽长,但能展示清楚发送消息的过程。先看 post(Runnable r) ,里面调用的是 sendMessageDelayed ,原来 post 方法实际上也是发送消息,但是 Message 对象在哪呢,再看 getPostMessage(Runnable r) 方法中,创建了一个 Message 对象,并把 Runnable 传给了它的 callback。至此,我们明白了 post 和 send 两种方法的区别。
sendMessageDelayed 方法调用 sendMessageAtTime ,而 sendMessageAtTime 中则是获取到消息队列 mQueue 调用 enqueueMessage ,将 Message 消息添加到了队列里。
MessageQueue 中是使用链表的数据结构来管理队列的, mMessages 是整个链表的第一个 Message 元素,下一个 Message 则是 mMessages.next,enqueueMessage(Message msg, long when) 方法的作用就是按照 when 时间来排序,插入新的 Message到整个 Message 链表中。
这个就是整个将消息添加到消息队列的过程。

消息是怎么 one by one 取出来处理的

...
final Runnable threadTask = new Runnable() {
    @Override
    public void run() {
    Looper.prepare();
    final Handler handler = new Handler();                          handler.post(postTask);
    Looper.loop();
    }
};
Thread t = new Thread(threadTask, "subThread");
t.start();

线程中如果只调用 Looper.prepare(); 是无法使 Handler 处理消息的,为什么呢?
因为没有让这个 Looper 运转起来,去取消息给 Handler。只有使用了 Looper.loop(); 方法才能让 Handler 正常的处理消息。
哪就来看看 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;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

loop() 方法中启动了一个无限循环,通过 queue.next 方法获取一个消息,如果获取的是 null 则意味着消息队列没有了消息,退出循环。
如果有消息,则调用 msg.target.dispatchMessage(msg); 方法,msg.target 就是 Handler ,在前面的 sendMessage 源代码中可以看到 msg.target = this;

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

该方法中,如果有 callback 就调用 callback 的 run 方法,这对应的就是 handler.post(runnable)。而如果没有 callback 则调用 Handler 的 handleMessage 方法,这对应的就是 handler.sendMessage(message) 方法,需要 Handler 重写 handleMessage 方法来处理消息。

完成消息的处理后,则将该消息的数据重置,标记成已使用,放入重用池中等待重用。至此一个消息取出处理就完成,再发起新一轮循环。

消息的重用

创建消息时,如果使用 Message message = Message.obtain(); 方法创建 Message 会比直接 new 出来的更省内存,原因就是 Message 是可以复用的。

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 单向链表的头元素),则取出,重置状态后 return 出去。sPoolSize 减一。没有重用池则 new Message。

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

在介绍 loop() 的时候,处理完消息,会调用该 msg 的 recycleUnchecked 方法,这个方法的作用看源代码可知,重置消息的数据,标记为已使用,并且当重用池中消息的个数小于规定的 MAX_POOL_SIZE 时,则将重置后的消息插入到重用池链表中,置于第一个位置。
至此,消息的复用也介绍完了。

总结

通过这个简单的示意图,能够清楚的知道整个 Handler 的运作流程。通过不同部分的源代码,了解到不同流程部分的细节,线程如何创建 Looper,并启动 looper。Handler 如何发送消息到消息队列,looper 如何从消息队列中去除消息交给 Handler 处理,消息如何被复用等。

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

推荐阅读更多精彩内容