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

[2]:Handler#Handler()

重新出发再学习,写下来一是加深印象,二是记录下这个重新学习的过程,有不足之处欢迎指正

相关推荐
拭心5 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王7 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡7 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道8 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库8 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道9 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe9 小时前
Android Hook - 动态加载so库
android
居居飒10 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He13 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗13 小时前
Android笔试面试题AI答之Android基础(1)
android