【源码解析】Handler的工作原理

Handler的使用

在日常开发过程中,Handler 多用来进行切换线程的操作。一般的场景是,在子线程中做完了耗时的操作,然后会使用 Handler 来通知主线程进行 UI 的更新。

例如我们会在主线程中定义一个静态内部类 Handler,这里命名为 MyHandler

private static MyHandler extends Handler {
    @override
    public void handleMessage(Message msg) {
        //todo 在这里根据msg处理并进行UI的更新
    }
}

定义 MyHandler 之后,然后生成一个 MyHandler 实例。

MyHandler mHandler = new MyHandler();

然后在子线程中使用 mHandler 发送消息。

Message msg = new Message();
msg.what = 0;
msg.obj = myObject;
mHandler.sendMessage(msg)

当执行 sendMessage 方法后,最终会执行 MyHandlerhandleMessage 方法,handleMessage 方法的Message 就是从 sendMessage 中传递进来的,所以可以根据 Message 对象判断并处理 UI 更新。

Handler 的使用还是挺简单的,但其内部的实现原理到底是怎样的呢?我们还是通过源码分析来一探究竟。

源码分析

我们从 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;
}

一开始就来检测内存泄漏是否可能存在。如果继承 Handler 的类是匿名内部类、非静态内部类,或者是本地内部类,则会提示该类有可能存在内存泄漏。为什么呢?因为 Handler 一般是在 Activity 或者 Fragment 中使用,匿名内部类、非静态内部类、本地内部类都会持有 Activity 对象,当 Handler 的生命周期比 Activity 长时,会导致 Activity 对象释放不了,导致内存泄露。

然后执行到 Looper.myLooper 方法获得一个 Looper 对象。

/**
* Return the Looper object associated with the current thread.  Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

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

sThreadLocal 是一个静态的 ThreadLocal 对象,关于 ThreadLocal 的作用请参考 【源码解析】ThreadLocal的工作原理,这里就是获取了当前线程下的 Looper 对象。

sThreadLocal 是什么时候将当前线程对应的 Looper 设置进去的呢?我们来看 Looper.prepare 方法。

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

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

可以看到在 prepare 方法中构造了一个 Looper 对象,Looper 对象构造了一个 MessageQueue 对象,并通过 Thread.currentThread 方法引用当前线程对象,然后将 Looper 对象塞到 sThreadLocal 中。

因此在生成 Handler 实例之前,必须要先去调用 Looper.prepare 方法,否则 Looper 对象找不到会抛出运行时异常。

throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");

讲到这里,也许大家可能会有疑问,为什么在平时开发使用那么多次 Handler,都没有见到过要主动调用 Looper.prepare 方法?这个问题我们先hold一下,留到后面说。

我们接着分析 Handler 的构造函数,在得到 Looper 对象后,然后将 Looper 对象的 MessageQueue 赋值给 HandlermQueue 变量,而 mQueue 变量的类型就是 MessageQueue

Handler 的分析到此告一段落,构造函数执行完后,下一步就是使用 Handler 对象发送消息。不管 Handler 是通过 post、sendMessage 还是其他方法,最终调用的都是 sendMessageAtTime 方法。

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 方法。

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

这段代码主要目的是将 Message 消息添加到 MessageQueue 这个消息队列。而 MessageQueue 消息队列就是在上面 Handler 构造函数中 new 出来的,由于 LooperThreaLocal 持有的,而 Looper 又持有 MessageQueue,那么 ThreadLocal 持有 MessageQueue。也就是说每一个线程对应着一个 MessageQueue 对象。

那么添加到 MessageQueueMessage 什么时候才能得到执行呢?Looper 有一个 loop 的方法。

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

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

        msg.target.dispatchMessage(msg);

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

        ...
    }
}

myLooper 方法获取当前线程的 Looper 对象,然后再获取 Looper 对象的 MessageQueue 对象,通过 for 循环,循环不断的从 MessageQueue 队列中获取 Message 消息,Message 消息的 target 就是一个 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);
    }
}

如果 Message 消息定义了 callback 对象,则执行 handleCallback 方法,也就是会执行 Message 消息的 callback 对象的 run 方法,否则如果 HandlermCallback 对象存在,则会执行 mCallbackhandleMessage 方法,当两者都不存在或者 mCallbackhandleMessage 方法返回为 false 时,则会执行 HandlerhandleMessage 方法。

由于 Handler 发送消息实际上是往该 Handler 持有的线程的消息队列中添加消息,当该消息被执行时,就已经切换到了 Handler 所在的线程了。当 Handler 持有的线程为主线程时,消息的执行就肯定运行在主线程,这样就达到了子线程向主线程切换的效果。

上面分析过程中还遗留了一个问题:为什么我们平时使用Handler的时候不用调用 Looper.prepareLooper.loop 方法呢?

因为这两个方法系统已经帮我们做了。在 ActivityThread 中(Activity 的真正入口)的 main 方法有以下逻辑。

public static void main(String[] args) {
    Looper.prepareMainLooper();
    ...
    // 开始循环
    Looper.loop();
    
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLooper 方法就是为主线程构建了一个 Looper 对象,而 Looper.loop 方法就是为主线程开启了消息循环,平时我们创建 Handler 大部分都是在主线程创建的,因此是不需要主动调用 Looper.prepareLooper.loop 方法了。

但是,如果是在子线程创建 Handler,那么在构造该 Handler 之前,必须要主动调用 Looper.prepareLooper.loop 方法,否则会抛出运行时异常。

总结

通过分析 Handler 源码可知,Handler 的原理并不高深,它主要是通过了 ThreaLocal 原理和不断从对应线程的消息队列中取消息的机制,将消息的执行抛到相对应的线程中去执行,从而达到了线程切换的效果。我们平时使用 Handler 切换到主线程去更新 UI 仅仅是其的一小部分功能实现。

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

推荐阅读更多精彩内容