Android面试时被问到Handler机制可怎么办之Handler初级探索

0. 场景重现

面试官:请做下自我介绍

我:我叫孙小胖儿,吧啦吧啦吧啦。。。。

面试官:好的,那我们来问点技术问题,请问你对Handler机制了解么

我:Handler机制主要是由Handler、Message、MessageQueue、Looper组成的,它们,它们,嗯。。。。呃。。。。

面试官:好了,咱们的面试就到这儿吧

我:哦,好的。

1. Handler 是什么

1.1 Handler 的定义

以下是 Android 对于Handler的定义[1]

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

简单说就是:我们可以通过Handler发送并处理与线程的消息队列相关联的MessageRunnable(其实Runnable 最终会被封装进 Message,所以 Runnable 本质上也是个 Message,这个后面再展开说)。每个 Handler 实例都会跟一个线程及其消息队列关联,当创建 Handler 的时候,它会和一个 Looper 绑定。 Handler有两个主要的用途:

(1) 在未来的某个时间执行 Message 或者 Runnable

(2) 在不同的线程间执行一些操作

1.2 Handler 的基本用法

java 复制代码
import android.os.Handler;  
import android.os.Message;

public class HandlerTestActivity extends AppCompatActivity {

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch(msg.what) {
                case 1:
                    // do something
                    break;
            }
        }
    };
    
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_test);
        
        // message形式
        sendMessage();
        // Runnable形式
        postRunnable();
    }
    
    private void sendMessage() {
        Message msg = Message.obtain();  
        msg.what = 1;  
        mHandler.sendMessage(msg);
    }
    
    private void postRunnable() {
        mHandler.post(new Runnable() {  
            @Override  
            public void run() {  
              // do something
            }  
        });
    }
}

上面这段代码就是Handler最基本的用法了,首先实例化一个Handler对象(第6行),然后通过sendMessage(sendMessage()方法) 或者 post(postRunnable()方法)的形式,来做一些线程相关的操作了。

咦?这里只有HandlerMessage,并没有 MessageQueueLooper 啊,搞咩啊~~~

2. Handler 机制探究

2.1 Handler 把 Message 送到哪里去了

通过 Handler 的基本用法,我们已经知道 Handler 通过 sendMessage 方法把 Message 发送了出去,但是 Message 被送到哪里去了呢?我们来看看 sendMessage 方法都做了什么。 从上述代码中可以看出,虽然我们调用的是Handler#sendMessage这个方法,但是实际执行的代码是在 sendMessageAtTime这个方法中,在这个方法中出现了一个和 Message 相关的东西:MessageQueue,我们继续往下看 在enqueueMessage这个方法中,我们看到,最后执行的是

java 复制代码
queue.enqueueMessage(msg, uptimeMillis);

而这个 queue 正是在 sendMessageAtTime 中定义,然后传入到enqueueMessage 这个方法中的。至此,Handler 终于是将 Message 放到了 MessageQueue

2.2 Message 是怎么回到 Handler 的

我们实例化一个Handler对象(假定命名为:mHandler)的时候,会重写 handleMessage 这个方法,这样当我们在调用 Handler#sendMessage这个方法的时候,才会由这个 mHandler 来处理 Message,那么,问题来了,这个 Message 又是怎么回到 Handler 的呢?

我们仔细看一下在【2.1节】提到的 enqueueMessage 这个方法 可以看到这个方法体内的第一行代码是:

java 复制代码
msg.target = this;

也就是说,Handler 把它自己存到了 Message 的 target 中,这样做的意义是什么呢? 说这个之前,我们先来看看 Handler 的构造方法

从图中可以看到,

java 复制代码
public Handler() {  
    this(null, false);  
}
 
![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/526179d2b0ec4f2e8a232eb7be2c9579~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=2218&h=2342&s=575489&e=png&b=202124)
public Handler(@Nullable Callback callback) {  
    this(callback, false);  
}

最终调用的是下面这个构造方法,而在这个构造方法中,Looper终于是浮出了水面。

java 复制代码
public Handler(@Nullable Callback callback, boolean async) {
    ...
    mLooper = Looper.myLooper();  
    if (mLooper == null) {  
        throw new RuntimeException(  
            "Can't create handler inside thread " + Thread.currentThread()  
            + " that has not called Looper.prepare()");  
    }  
    mQueue = mLooper.mQueue;  
    mCallback = callback;  
    mAsynchronous = async;
}

注意 :下面这两个方法,从API 30开始废弃了,官方文档给出的解释是:在Handler的构造方法中隐式的选择Looper会引发 Bug[2]

java 复制代码
public Handler() {  
    this(null, false);  
}
 
![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6b5dd0eed1284ff4ab5dcc6c0b9ec269~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=2218&h=2342&s=575489&e=png&b=202124)
public Handler(@Nullable Callback callback) {  
    this(callback, false);  
}

好了,言归正传,我们来看看这个Looper是什么。

Looper中有两个比较重要的方法,一个是prepare()方法,一个是loop()方法,我们先说prepare()方法。 从上图可以看到,在prepare()方法中,实例化了一个Looper对象,而在Looper的构造方法中,实例化了消息队列(见下图)

至此,Looper、MessageQueue、Message、Handler 这四者都出现了,也通过 MessageQueue、Message 关联到一起了。

那么我们接下来看看 Looper 中另一个比较重要的方法:loop() loop()方法中有个for循环,还是个死循环,在这里有个方法,名为:loopOnce,我们看看它做了哪些事情 在 loopOnce 这个方法中,我们可以看到,它首先调用了MessageQueue的next()方法,获取到了Message(图中第161行代码) 在对这个取出来的 Message 做了一些列处理后,执行了 msg.target.dispatchMessage 这个代码。还记得我们前面说过的msg.target是什么么?对了,它就是发送这个 message 的 Handler,好了,Handler 向消息队列发送的消息,最终还是又回到了这个 Handler 里,让它自己处理。最后来看看这个 dispatchMessage()方法做了什么 好了,现在 Handler、Message、MessageQueue、Looper这四个终于是形成了一个回环。它们之间的工作流程如下:

3. 扩展一下

3.1 为什么说 Runnable 也是 Message

我们来看下 Handler#post()方法做了什么,就知道了 可以看到,在post方法里调用的还是 sendMessageDelayed() 方法,但是有个区别,传给sendMessageDelayed() 的第一个参数,是从 getPostMessage() 这个方法取得,我们跟进去看一下 在这里可以看到,Runnable被当做了Message的callback保存了下来,因此可以简单的认为 Runnable 也是 Message

3.2 Handler是怎么实现线程切换的

Handler 实现线程切换,其实依赖的是 Looper,因为在 Handler 初始化的时候就跟 Looper 绑定了,而Looper 又是跟线程绑定的,所以,Handler 最后执行 HandleMessage() 方法的时候所在的线程就是 Looper 所在的线程。

java 复制代码
public Handler(@Nullable Callback callback, boolean async) {  
    ...
  
    mLooper = Looper.myLooper();  
    if (mLooper == null) {  
        throw new RuntimeException(  
            "Can't create handler inside thread " + Thread.currentThread()  
            + " that has not called Looper.prepare()");  
    }  
    mQueue = mLooper.mQueue;  
    ...
}

Looper.loop()
 -> MessageQueue.next()
  -> Message.target.dispatchMessage()
   -> Handler.handleMessage()

通常情况下,我们在使用 Handler 的时候除非特别指定,否则用的都是主线程的 Looper,因此子线程发送消息以后,经过Looper#loop()->MessageQueue.next()->Message.target.dispatchMessage()这一系列的调用之后,会回到主线程执行Handler#handleMessage()

3.3 为什么能在主线程直接使用 Looper

那是就得从Looper#prepareMainLooper()方法说起了 初看这个方法,好像没有什么特别之处,不过仔细看下箭头指向的那行代码 prepare(false),这里传入的是false,从参数的提示可以看到是不允许退出,因此主线程的Looper是不能退出的。

再看下这个方法的注释,即@deprecated那里写的,你的应用程序的main looper已经被Android 环境创建了,那我们来验证一下 全局搜索一下prepareMainLooper(),可以看到在ActivityThread#main()方法中调用了 Looper.prepareMainLooper(),所以这也就印证了注释中的说明,确实是 Android 帮我们创建了,因此我们可以直接在主线程(UI线程)使用Looper了。

3.4 Message 的创建

在Android 源码里,对于 Message ,有个特别说明 建议我们使用Message#obtain()Handler#obtainMessage()这个两个方法来创建 Message 对象,从这段描述来看,Android 给 Message 设计了一个回收机制,所以为了节省内存开销,还是按照官方推荐的方式来获取 Message 吧

4. 总结

  1. Handler 背后还有 Looper、MessageQueue、Message
    • message:消息。
    • MessageQueue:消息队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message。读取会自动删除消息,单链表维护,插入和删除上有优势。在其next()方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。
    • Looper:消息循环器,负责关联线程以及消息的分发,在该线程下从 MessageQueue获取 Message,分发给Handler,Looper创建的时候会创建一个 MessageQueue,调用loop()方法的时候消息循环开始,其中会不断调用messageQueue的next()方法,当有消息就处理,否则阻塞在messageQueue的next()方法中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也就跟着退出。
    • Handler:消息处理器,负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。
  2. 使用 Handler 前,要先创建 Looper 或者使用主线程的 Looper
  3. 主线程的 Looper 不允许退出
  4. Runnable 被封装进了 Message,可以说是一个特殊的 Message

5. 参考资料

  1. Handler
  2. Handler 都没搞懂,拿什么去跳槽啊?
  3. handler机制

6. 备注

1\]:[Handler](https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.com%2Freference%2Fandroid%2Fos%2FHandler "https://developer.android.com/reference/android/os/Handler") \[2\]:[Handler#Handler()](https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.com%2Freference%2Fandroid%2Fos%2FHandler%23Handler() "https://developer.android.com/reference/android/os/Handler#Handler()") 重新出发再学习,写下来一是加深印象,二是记录下这个重新学习的过程,有不足之处欢迎指正

相关推荐
似霰11 分钟前
安卓adb shell串口基础指令
android·adb
fatiaozhang95272 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
CYRUS_STUDIO3 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师4 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师4 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫4 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白4 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
dpxiaolong5 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
tangweiguo030519877 小时前
Android 混合开发实战:统一 View 与 Compose 的浅色/深色主题方案
android
老狼孩111227 小时前
2025新版懒人精灵零基础及各板块核心系统视频教程-全分辨率免ROOT自动化开发
android·机器人·自动化·lua·脚本开发·懒人精灵·免root开发