Handler都没搞懂,拿什么去跳槽???

0. 前言

做 Android 开发肯定离不开跟 Handler 打交道,它通常被我们用来做主线程与子线程之间的通信工具,而 Handler 作为 Android 中消息机制的重要一员也确实给我们的开发带来了极大的便利。

Handler应用之广泛,可以说只要有异步线程与主线程通信的地方就一定会有 Handler。

所以搞懂 Handler 对理解Android非常有必要。

那么,Handler 的通信机制的背后的原理是什么?

本文带你揭晓。

注意:本文所展示的系统源码基于 Android-27 ,并有所删减。

1. 重识 Handler

我们可以使用 Handler 发送并处理与一个线程关联的 Message 和 Runnable 。(注意:Runnable 会被封装进一个 Message,所以它本质上还是一个 Message

每个 Handler 都会跟一个线程绑定,并与该线程的 MessageQueue 关联在一起,从而实现消息的管理以及线程间通信。

1.1 Handler 的基本用法

1android.os.Handler handler = new Handler(){2  @Override3  public void handleMessage(final Message msg) {4    //这里接受并处理消息5  }6};7//发送消息8handler.sendMessage(message);9handler.post(runnable);

实例化一个 Handler 重写 handleMessage 方法 ,然后在需要的时候调用它的 send 以及 post 系列方法就可以了,非常简单易用,并且支持延时消息。(更多方法可查询 API 文档)

但是奇怪,我们并没有看到任何 MessageQueue 的身影,也没看到它与线程绑定的逻辑,这是怎么回事?

2. Handler 原理解析

相信大家早就听说过了 Looper 以及 MessageQueue 了,我就不多绕弯子了。

不过在开始分析原理之前,先明确我们的问题

  1. Handler 是如何与线程关联的?

  2. Handler 发出去的消息是谁管理的?

  3. 消息又是怎么回到 handleMessage() 方法的?

  4. 线程的切换是怎么回事?

2.1 Handler 与 Looper 的关联

实际上我们在实例化 Handler 的时候 Handler 会去检查当前线程的 Looper 是否存在,如果不存在则会报异常,也就是说在创建 Handler 之前一定需要先创建 Looper 。

代码如下:

 1public Handler(Callback callback, boolean async) { 2        //检查当前的线程是否有 Looper 3        mLooper = Looper.myLooper(); 4        if (mLooper == null) { 5            throw new RuntimeException( 6                "Can't create handler inside thread that has not called Looper.prepare()"); 7        } 8        //Looper 持有一个 MessageQueue 9        mQueue = mLooper.mQueue;10}

这个异常相信很多同学遇到过,而我们平时直接使用感受不到这个异常是因为主线程已经为我们创建好了 Looper,先记住,后面会讲。(见【3.2】)

一个完整的 Handler 使用例子其实是这样的:

 1class LooperThread extends Thread { 2    public Handler mHandler; 3    public void run() { 4        Looper.prepare(); 5        mHandler = new Handler() { 6            public void handleMessage(Message msg) { 7                // process incoming messages here 8            } 9        };10        Looper.loop();11    }12}

Looper.prepare() :

1//Looper2private static void prepare(boolean quitAllowed) {3  if (sThreadLocal.get() != null) {4    throw new RuntimeException("Only one Looper may be created per thread");5  }6  sThreadLocal.set(new Looper(quitAllowed));7}

Looper 提供了 Looper.prepare() 方法来创建 Looper ,并且会借助 ThreadLocal 来实现与当前线程的绑定功能。Looper.loop() 则会开始不断尝试从 MessageQueue 中获取 Message , 并分发给对应的 Handler(见【2.3】)。

也就是说 Handler 跟线程的关联是靠 Looper 来实现的。

2.2 Message 的存储与管理

Handler 提供了一些列的方法让我们来发送消息,如 send()系列 post()系列 。

不过不管我们调用什么方法,最终都会走到 Message.enqueueMessage(Message,long) 方法。

sendEmptyMessage(int) 方法为例:

1//Handler2sendEmptyMessage(int)3  -> sendEmptyMessageDelayed(int,int)4    -> sendMessageAtTime(Message,long)5      -> enqueueMessage(MessageQueue,Message,long)6              -> queue.enqueueMessage(Message, long);

到了这里,消息的管理者 MessageQueue 也就露出了水面
MessageQueue 顾明思议,就是个队列,负责消息的入队出队。

2.3 Message 的分发与处理

了解清楚 Message 的发送与存储管理后,就该揭开分发与处理的面纱了。

前面说到了 Looper.loop() 负责对消息的分发,本章节进行分析。

先来看看所涉及到的方法:

 1//Looper 2public static void loop() { 3    final Looper me = myLooper(); 4    if (me == null) { 5        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 6    } 7    final MessageQueue queue = me.mQueue; 8    //... 9    for (;;) {10       // 不断从 MessageQueue 获取 消息11        Message msg = queue.next(); // might block12        //退出 Looper 13        if (msg == null) {14            // No message indicates that the message queue is quitting.15            return;16        }17        //...18        try {19            msg.target.dispatchMessage(msg);20            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();21        } finally {22            //...23        }24        //...25                //回收 message, 见【3.5】26        msg.recycleUnchecked();27    }28}

loop() 里调用了 MessageQueue.next() :

 1//MessageQueue 2Message next() { 3    //... 4    for (;;) { 5        //... 6        nativePollOnce(ptr, nextPollTimeoutMillis); 7 8        synchronized (this) { 9            // Try to retrieve the next message.  Return if found.10            final long now = SystemClock.uptimeMillis();11            Message prevMsg = null;12            Message msg = mMessages;13            //...14            if (msg != null) {15                if (now < msg.when) {16                    // Next message is not ready.  Set a timeout to wake up when it is ready.17                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);18                } else {19                    // Got a message.20                    mBlocked = false;21                    if (prevMsg != null) {22                        prevMsg.next = msg.next;23                    } else {24                        mMessages = msg.next;25                    }26                    msg.next = null;27                    return msg;28                }29            } else {30                // No more messages.31                nextPollTimeoutMillis = -1;32            }3334            // Process the quit message now that all pending messages have been handled.35            if (mQuitting) {36                dispose();37                return null;38            }39        }4041        // Run the idle handlers. 关于 IdleHandler 自行了解42        //...43    }44}

还调用了 msg.target.dispatchMessage(msg) ,msg.target 就是发送该消息的 Handler,这样就回调到了 Handler 那边去了:

 1//Handler 2public void dispatchMessage(Message msg) { 3  //msg.callback 是 Runnable ,如果是 post方法则会走这个 if 4  if (msg.callback != null) { 5    handleCallback(msg); 6  } else { 7    //callback 见【3.4】 8    if (mCallback != null) { 9      if (mCallback.handleMessage(msg)) {10        return;11      }12    }13    //回调到 Handler 的 handleMessage 方法14    handleMessage(msg);15  }16}

注意:dispatchMessage() 方法针对 Runnable 的方法做了特殊处理,如果是 ,则会直接执行 Runnable.run() 。

分析:Looper.loop() 是个死循环,会不断调用 MessageQueue.next() 获取 Message ,并调用 msg.target.dispatchMessage(msg) 回到了 Handler 来分发消息,以此来完成消息的回调。

注意:loop()方法并不会卡死主线程,见【6】。

那么线程的切换又是怎么回事呢?
很多人搞不懂这个原理,但是其实非常简单,我们将所涉及的方法调用栈画出来,如下:

1Thread.foo(){2    Looper.loop()3     -> MessageQueue.next()4       -> Message.target.dispatchMessage()5        -> Handler.handleMessage()6}

显而易见,Handler.handleMessage() 所在的线程最终由调用 Looper.loop() 的线程所决定。

平时我们用的时候从异步线程发送消息到 Handler,这个 Handler 的 handleMessage() 方法是在主线程调用的,所以消息就从异步线程切换到了主线程。

2.3 图解原理

文字版的原理解析到这里就结束了,如果你看到这里还是没有懂,没关系,我特意给你们准备了些图,配合着前面几个章节,再多看几遍,一定可以吃透。

image

<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">handler-looper-mq.jpg</figcaption>

image

<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">handler_java.jpg</figcaption>

图片来源见【6】

2.4 小结

Handler 的背后有着 Looper 以及 MessageQueue 的协助,三者通力合作,分工明确。

尝试小结一下它们的职责,如下:

  • Looper :负责关联线程以及消息的分发,会与创建它的线程绑定,并负责在该线程下从 MessageQueue 获取 Message,分发给 Handler ;

  • MessageQueue :是个队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message ;

  • Handler : 负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。

对【2】章节提出的问题用一句话总结:

Handler 发送的消息由 MessageQueue 存储管理,并由 Loopler 负责回调消息到 handleMessage()。

线程的转换由 Looper 完成,handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。

3. Handler 的延伸

Handler 虽然简单易用,但是要用好它还是需要注意一点,另外 Handler相关 还有些鲜为人知的知识技巧,比如 IdleHandler。

由于 Handler 的特性,它在 Android 里的应用非常广泛,比如: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。

这些我会讲解一些,我没讲到的可以自行搜索相关内容进行了解。

3.1 Handler 引起的内存泄露原因以及最佳解决方案

Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。

这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。

解决该问题的最有效的方法是:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息

示例代码如下:

 1private static class SafeHandler extends Handler { 2 3    private WeakReference<HandlerActivity> ref; 4 5    public SafeHandler(HandlerActivity activity) { 6        this.ref = new WeakReference(activity); 7    } 8 9    @Override10    public void handleMessage(final Message msg) {11        HandlerActivity activity = ref.get();12        if (activity != null) {13            activity.handleMessage(msg);14        }15    }16}

并且再在 Activity.onDestroy() 前移除消息,加一层保障:

1@Override2protected void onDestroy() {3  safeHandler.removeCallbacksAndMessages(null);4  super.onDestroy();5}

这样双重保障,就能完全避免内存泄露了。

注意:单纯的在 onDestroy 移除消息并不保险,因为 onDestroy 并不一定执行。

3.2 为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?

前面我们提到了每个Handler 的线程都有一个 Looper ,主线程当然也不例外,但是我们不曾准备过主线程的 Looper 而可以直接使用,这是为何?

注意:通常我们认为 ActivityThread 就是主线程。事实上它并不是一个线程,而是主线程操作的管理者,所以吧,我觉得把 ActivityThread 认为就是主线程无可厚非,另外主线程也可以说成 UI 线程。

在 ActivityThread.main() 方法中有如下代码:

 1//android.app.ActivityThread 2public static void main(String[] args) { 3  //... 4  Looper.prepareMainLooper(); 5 6  ActivityThread thread = new ActivityThread(); 7  thread.attach(false); 8 9  if (sMainThreadHandler == null) {10    sMainThreadHandler = thread.getHandler();11  }12  //...13  Looper.loop();1415  throw new RuntimeException("Main thread loop unexpectedly exited");16}

Looper.prepareMainLooper(); 代码如下:

 1/** 2 * Initialize the current thread as a looper, marking it as an 3 * application's main looper. The main looper for your application 4 * is created by the Android environment, so you should never need 5 * to call this function yourself.  See also: {@link #prepare()} 6 */ 7public static void prepareMainLooper() { 8    prepare(false); 9    synchronized (Looper.class) {10        if (sMainLooper != null) {11            throw new IllegalStateException("The main Looper has already been prepared.");12        }13        sMainLooper = myLooper();14    }15}

可以看到在 ActivityThread 里 调用了 Looper.prepareMainLooper() 方法创建了 主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接使用 Handler 了。

注意:Looper.loop() 是个死循环,后面的代码正常情况不会执行。

3.3 主线程的 Looper 不允许退出

如果你尝试退出 Looper ,你会得到以下错误信息:

1Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.2  at android.os.MessageQueue.quit(MessageQueue.java:415)3  at android.os.Looper.quit(Looper.java:240)

why? 其实原因很简单,主线程不允许退出,退出就意味 APP 要挂。

3.4 Handler 里藏着的 Callback 能干什么?

在 Handler 的构造方法中有几个 要求传入 Callback ,那它是什么,又能做什么呢?

来看看 Handler.dispatchMessage(msg) 方法:

 1public void dispatchMessage(Message msg) { 2  //这里的 callback 是 Runnable 3  if (msg.callback != null) { 4    handleCallback(msg); 5  } else { 6    //如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage 7    if (mCallback != null) { 8      if (mCallback.handleMessage(msg)) { 9        return;10      }11    }12    handleMessage(msg);13  }14}

可以看到 Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。

这个就很有意思了,这有什么作用呢?

我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息!

场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。

3.5 创建 Message 实例的最佳方式

由于 Handler 极为常用,所以为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗。

方法有二:

  1. 通过 Message 的静态方法 Message.obtain(); 获?。?/p>

  2. 通过 Handler 的公有方法 handler.obtainMessage();

3.6 子线程里弹 Toast 的正确姿势

当我们尝试在子线程里直接去弹 Toast 的时候,会 crash :

1java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 2

本质上是因为 Toast 的实现依赖于 Handler,按子线程使用 Handler 的要求修改即可(见【2.1】),同理的还有 Dialog。

正确示例代码如下:

1new Thread(new Runnable() {2  @Override3  public void run() {4    Looper.prepare();5    Toast.makeText(HandlerActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show();6    Looper.loop();7  }8});

3.7 妙用 Looper 机制

我们可以利用 Looper 的机制来帮助我们做一些事情:

  1. 将 Runnable post 到主线程执行;

  2. 利用 Looper 判断当前线程是否是主线程。

完整示例代码如下:

 1public final class MainThread { 2 3    private MainThread() { 4    } 5 6    private static final Handler HANDLER = new Handler(Looper.getMainLooper()); 7 8    public static void run(@NonNull Runnable runnable) { 9        if (isMainThread()) {10            runnable.run();11        }else{12            HANDLER.post(runnable);13        }14    }1516    public static boolean isMainThread() {17        return Looper.myLooper() == Looper.getMainLooper();18    }1920}

能够省去不少样板代码。

4. 知识点汇总

由前文可得出一些知识点,汇总一下,方便记忆。

  1. Handler 的背后有 Looper、MessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理;

  2. 在创建 Handler 之前一定需要先创建 Looper;

  3. Looper 有退出的功能,但是主线程的 Looper 不允许退出;

  4. 异步线程的 Looper 需要自己调用 Looper.myLooper().quit(); 退出;

  5. Runnable 被封装进了 Message,可以说是一个特殊的 Message;

  6. Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,也可以说成 Looper 所在的线程,并不是创建 Handler 的线程;

  7. 使用内部类的方式使用 Handler 可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类;

5. 总结

Handler 简单易用的背后藏着工程师大量的智慧,要努力向他们学习。

看完并理解本文可以说你对 Handler 有了一个非常深入且全面的了解,应对面试肯定是绰绰有余了。

6. 参考和推荐

Handler
what-is-the-relationship-between-looper-handler-and-messagequeue-in-android
Android消息机制1-Handler(Java 层)
Android中为什么主线程不会卡死

小编给大家准备安卓进阶学习资料。
加群:4112676

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

推荐阅读更多精彩内容

  • 前言 在Android开发的多线程应用场景中,Handler机制十分常用 今天,我将手把手带你深入分析Handle...
    BrotherChen阅读 471评论 0 0
  • 作为销售人员,首先要对公司、对产品、对自己有信心,时刻告诉自己,公司是有实力的,产品是有优势的,自己是有能力的,销...
    纪桂华阅读 165评论 1 0
  • 今天我是微课堂堂主,今天我要给大家带来的是,小心虫子,这个名字非常的有意思,嗯当时我给大家讲的时候并不紧张,并且觉...
    又呆又萌的萌阅读 281评论 0 0