计算机系统应用教程网站

网站首页 > 技术文章 正文

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

btikc 2024-09-02 16:42:49 技术文章 13 ℃ 0 评论


01. 前言

做 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  @Override
3  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//Looper
2private 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//Handler
2sendEmptyMessage(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 block
12        //退出 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            }
33
34            // Process the quit message now that all pending messages have been handled.
35            if (mQuitting) {
36                dispose();
37                return null;
38            }
39        }
40
41        // 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 来分发消息,以此来完成消息的回调

那么线程的切换又是怎么回事呢?

很多人搞不懂这个原理,但是其实非常简单,我们将所涉及的方法调用栈画出来,如下:

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 图解原理

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




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    @Override
10    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@Override
2protected 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();
14
15  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(); 获取;

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  @Override
3  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    }
15
16    public static boolean isMainThread() {
17        return Looper.myLooper() == Looper.getMainLooper();
18    }
19
20}
 

能够省去不少样板代码。

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 有了一个非常深入且全面的了解,应对面试肯定是绰绰有余了。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表