Android消息机制源码解析

概述

Android 的消息机制主要是指 Handler 的运行机制。Android 规定只有主线程可以访问 UI ,子线程中无法访问 UI。但是主线程中不建议进行耗时操作,因为这会引起 ANR。
系统为什么不允许子线程中访问 UI?
如果多线程并发访问,UI 控件处于不可控制的状态。如果对 UI 控件的访问上锁,首先上锁机制会让 UI 访问的逻辑变得复杂;其次会降低 UI 的访问效率,因为锁机制会阻塞某些线程的执行。所以,采用单线程模型来处理 UI 操作简单高效。

消息机制解决了我们需要从服务器获取数据后操作 UI 的矛盾。但是更新 UI 仅仅是 Handler 的一个使用场景。本质上来说,Handler 并不是专门用于更新 UI 的,它只是在更新 UI 的场景中用的比较多。Handler 的使用过程很简单,通过它可以轻松的将一个任务切换到 Handler 所在的线程中去执行。Handler 的运行需要底层的 MessageQueue 和 Looper 的支撑。

Android消息机制有哪些核心成员组成的呢?

  • MessageQueue:
    消息队列。 它的内部存储了一组消息,以队列的形式对外提供插入和删除的操作。但是内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。
  • Looper:
    消息循环。以无限循环的形式去查询是否有新消息,如果有的话就处理消息,没有就一直等待。
  • Handler:
    消息传递的主要操作者。将消息从一个线程传递到另外一个线程。
  • ThreadLocal:
    不是线程,它的作用是可以在每个线程中存储数据。
    Handler 创建的时候会采用当前线程的 Looper 来构造消息循环系统,Handler 内部要使用 ThreadLocal 获取当前线程的 Looper。ThreadLocal 可以在不同的线程中互不干扰的存储并提供数据,通过 ThreadLocal 可以轻松获取每个线程的 Looper。
    注意:线程默认是没有 Looper 的,如果需要使用 Handler 必须为线程创建 Looper。主线程(UI线程 ActivityThread)被创建时就会初始化 Looper,这也是主线程中默认可以使用 Handler 的原因。

Handler分析

Handler 是Android消息机制的上层类,也是Android消息机制中我们接触最多的类。使用消息机制的时候,我们都是通过 Handler 的 post 和 send 系列方法开始的,我们从这里开始分析,借助源码,探索Android消息机制的底层原理。我们先看 post 系列方法源码:

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

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

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}

public final boolean postDelayed(Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

我们再看 send 系列方法的源码:

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

public final boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
}

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

画个简易的图,理清这些方法之间的关系。

image

图中可以发现:post 系列和 send 系列的方法最终都指向了 sendMessageAtTime()方法。也就是说,send 系列的方法最终都是靠 sendMessageAtTime()方法实现的。我们看下 sendMessageAtTime 方法的实现:首先获取一个 MessageQueue,然后传给 enqueueMessage 方法,调用此方法。下面是 enqueueMessage 方法源码:

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

可以看到,最后执行的是 MessageQueue 类的 enqueueMessage 方法。我们知道 MessageQueue有两个很重要的方法:enqueueMessage 和 next。enqueueMessage 负责往消息队列里添加数据,next 负责从消息队列里取出数据。所以,handler post 和send 系列的方法做的事情就是往 MessageQueue 里面添加数据。

现在我们知道数据是怎么添加到消息队列里面去的了,这是消息传递的第一步,那第二步就是把消息取出来进行处理,虽然处理消息依然是 handler 的事,但把消息取出来却是 Looper 默默一直干的事。我们分析下 Looper。

Looper

Looper 是让我们整个消息机制循环起来的核心类。普通的线程是没有消息队列的,也是无法使用 Handler 的(主线程:ActivityThread 被创建的时候默认初始化 Looper ,这也是我们可以直接在主线程使用 Handler 的原因)。正是借助 Looper 让线程成为 Looper 线程,线程和 Looper 绑定后,也就有了消息队列,因为消息队列 (MessageQueue) 是放在 Looper 类里面的。那么我们怎么做可以让普通线程变成可以使用消息机制的线程呢?很简单,使用 Looper 类的两个静态方法:

  • Looper.prepare()
  • Looper.loop()

这两个方法具体做了什么呢?我们看下源码:

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

prepare 方法很简单,里面就做了一件事,就是给变量 sThreadLocal 设置值。sThreadLocal 变量是一个 ThreadLocal 类型的变量:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

如果对 ThreadLocal 不太了解可以先看下这篇文章 Android消息机制-ThreadLocal。我们知道,ThreadLocal 的作用就是保证使用 ThreadLocal 存储的数据只属于当前线程,其他线程无法获取。sThreadLocal 泛型是 Looper,所以这个变量会存储一个 Looper 对象。prepare() 方法是调用了 sThreadLocal 的 set 方法存储了一个新建的 Looper 对象。存储之前会判断是否已经有了 Looper 对象,如果已经有了,会抛异常。所以,一个线程中只能有一个 Looper 对象。这样就保证了这个 Looper 对象只属于当前线程,而且只有一个 Looper 对象。我们看看 new Looper(quitAllowed)这步都干了什么:

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

可以看到,首先,构造方法是私有的,就是在别的类中不能实例化 Looper 对象。方法中就是给两个变量赋值,一个是我们的消息队列 mQueue,一个是线程对象 mThread。此时,我们的 Looper 对象有了消息队列,而且获取到了当前线程。然后看下 loop() 方法:

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();//1、获取looper对象
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;//2、获取消息队列

    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // 取出队列中的消息
        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.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);
        }

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

这里就出现了我们一直想寻找的东西啦。首先 1 处通过 myLooper()方法获取到存储的 looper 对象:

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

然后 2 处通过获取到的 Looper 对象获取到对象中的消息队列。获取到消息队列后,通过一个不设参数的 for 循环方法不断取出消息,如果消息不为空,就执行:

msg.target.dispatchMessage(msg);

msg我们都知道是 Message,那么 msg.target 是什么呢?哈哈,还记得我们前面分析的 Handler 把消息存入消息队列的过程吗,Handler 的存储方法:enqueueMessage 方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;//这里把handler对象赋给了target
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

所以,这个 target 就是我们的 handler 对象,next 方法取出消息后就调用了我们 handler 对象的 dispatchMessage() 方法。这里就是我们能不断处理消息的关键: Looper 对象一直在幕后不断的取出消息给我们的 handler 对象,然后由 handler 对象去处理消息。

现在我们已经清楚了消息队列是什么时候构造的,消息是什么时候存入队列的,消息是怎么取出的,还差一步,我们看看 handler 是怎么处理消息的,就是我们上面取出消息后执行的方法:dispatchMessage

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

首先判断 msg 的 callback 是否为空,callback 是什么呢?是我们调用 post(Runnable r) 方法传入的 runnable 对象:

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

不为空的时候用的是 post 系列方法,如果为空则用的是 send 系列方法。第二步,判断 mCallback 是否为空,mCallback 又是什么呢?

final Callback mCallback;
public interface Callback {
    public boolean handleMessage(Message msg);
}

看到这里,大家应该明白了吧,终于看到我们熟悉的处理消息的方法了:handleMessage。mCallback 是否为空对应的是 Handler 类的不同构造方法。

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

我们在创建 handler 对象的时候,可以直接传入一个匿名内部类去实现 handleMessage 方法;也可以构造方法不传参,然后去实现 handlerMessage 方法。所以,在 dispatchMessage 方法中,判断如果 mCallback 为空的话,执行 handleMessage 方法。这样,我们就走到了 handleMessage 方法,就可以按照我们的业务逻辑去处理消息了。

最后画一张图总结一下整个流程:


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

推荐阅读更多精彩内容