Android面试必问之Handler机制

参考:https://www.jianshu.com/p/13c8a66d3b5c
https://pqpo.me/2017/05/03/learn-messagequeue/

1. 作用

Handler是一种用于线程间的消息传递机制。
因为 Android 中不允许在非主线程更新UI,所以最常使用的地方就是用于子线程获取某些数据后进行UI的更新。

2. 基本使用

2.1 创建Handler实例

//1.自定义Handler类
static class CustomHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        //更新UI等操作
    }
}

CustomHandler customHandler = new CustomHandler();

//2.内部类
Handler innerHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        //更新UI等操作
    }
};

//3.callback方式
Handler callbackHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        //更新UI等操作
        return true;
    }
});

2.2 发送消息

 //1.发送普通消息
 Message msg = Message.obtain();
 msg.what = 0; //标识
 msg.obj = "这是消息体"; //消息内容
 innerHandler.sendMessage(msg);
 
 //2.发送Runnale消息
 innerHandler.post(new Runnable() {
     @Override
     public void run() {
         //更新UI等操作,消息接收后执行此方法
     }
 });

2.3 使用post方法

在这里插入图片描述

2.4 使用sendMessage方法

在这里插入图片描述
在这里插入图片描述

2.5 通过Message与Handler进行通信的步骤

在这里插入图片描述

3. 源码分析

3.1 为什么Handler能够切换线程执行?

因为最终的处理是在 handleMessage方法中进行的,所以我们看看 handleMessage方法是怎么被调用起来的。
在这里插入图片描述
ActivityThreadmain()方法调用了Looper.loop()方法,
–> loop()方法里边调用了msg.target.dispatchMessage(msg)方法,
–> 这个msg是我们发送的Message,target是创建的Activity中的Handler对象,
–> loop()方法拿到了我们发送的信息
–> loop()方法判断当前线程是否已经调用了Looper.loop()方法,没有则抛出异常,
–> 这就是我们创建非主线程的Handler为什么要调用Looper.prepare()的原因。
–> 主线程在ActivityThread.main()中调用了prepare()方法,不需要再额外创建Looper。
在这里插入图片描述
看看MessageQueue的部分源码:
在这里插入图片描述
Handler中的sendMessage():
在这里插入图片描述
sendMessage()方法最终是调用了 sendMessageAtTime()方法,这个方法首先将会拿到一个消息队列 mQueue,这个队列是在创建 Looper的时候默认初始化的,然后会调用enqueueMessage()方法进队。
在这里插入图片描述
MessageQueue中的enqueueMessage():
在这里插入图片描述
异步消息与同步消息唯一的区别就是当有消息屏障时,异步消息还可以执行,而同步消息则不行。

总结一下:

Handler发送的线程不处理消息,只有Looper.loop()将消息取出来后再进行处理,所以在Handler机制中,无论发送消息的Handler对象处于什么线程,最终的处理又是运行在Looper.loop()所在的线程。

3.2 Handler.post(Runnable) 方法是运行在新的线程吗?

调用我们post方法里传递的 Runnable 对象的run()方法,Runnable 跟线程没有半毛钱关系,他只是一个回调方法而已。

3.3 Handler(Callback) 跟 Handler() 这两个构造方法的区别在哪?

HandlerdispatchMessage()方法中,msg先判断callback为不为空,再判断mCallback是否为空(我们自己传的这个Callback),为空调用handleMessage(msg)不为空调用mCallback.handleMessage(msg),根据mCallback.handleMessage(msg)的返回值判断是否拦截消息,如果拦截(返回 true),则结束,否则还会调用 Handler#handleMessage(msg)方法。
总结来说:Callback.handleMessage() 的优先级比 Handler.handleMessage()要高 。如果存在Callback,并且Callback#handleMessage() 返回了 true ,那么Handler#handleMessage()将不会调用。

3.4 子线程可以创建 Handler 吗?

  可以。但是有一些注意事项,子线程创建 Handler 除了需要调用 Looper.prepare()外,还需要调用 Looper.loop()启动。
  任何线程都可以创建 Handler,只要当前线程调用了 Looper.prepare()方法,那么就可以使用 Handler 了,而且同一线程内就算创建 n 个 Handler 实例,也只对应一个 Looper,即对应一个消息队列。
  一个线程可以有多个Handler实例,只对应一个Looper,一个MessageQueue。

3.5 为什么主线程不用调用 Looper.prepare() ?

在这里插入图片描述
在App启动的时候系统默认启动了一个主线程的 Looper,prepareMainLooper()也是调用了 prepare()方法,里面会创建一个不可退出的 Looper,并 set 到 sThreadLocal对象当中。

3.6 为什么创建 Message 对象推荐使用 Message.obtain()获取?

因为 Handler 机制在整个 Android 系统中使用太频繁,所以 Android 就采用了一个缓存策略。就是 Message 里面会缓存一个静态的消息池,当消息被处理或者移除的时候就会被回收到消息池,所以推荐使用 Message.obtain()来获取消息对象。

3.7 梳理

在这里插入图片描述
  把整个Handler机制比作一个流水线的话,那么 Handler 就是工人,可以在不同线程传递 Message到传送带(MessageQueue),而传送带是被马达(Looper)运输的,马达又是一开始就运行了(Looper.loop()),并且只会在一开始的线程,所以无论哪个工人(Handler)在哪里(任意线程)传递产品(Message),都只会在一条传送带(MessageQueue)上被唯一的马达(Looper)运送到终点处理,即 Message 只会在调用 Looper.loop() 的线程被处理。

4. 常见问题&技巧

4.1 为什么 Handler 会造成内存泄漏?

生命周期长的对象引用了生命周期短的对象。 Handler 里面匿名内部类的 Handler 持有 Activity 的引用,而发送的 Message 又持有 Handler 的引用,Message 又存在于 MessageQueue 中,而 MessageQueue 又是 Looper 的成员变量,并且 Looper 对象又是存在于静态常量 sThreadLocal 中。sThreadLocal 间接的持有了 Activity 的引用,当 Handler 发送的消息还没有被处理完毕时,比如延时消息,而 Activity 又被用户返回了,即 onDestroy() 后,系统想要对 Activity 对象进行回收,但是发现还有引用链存在,回收不了,就造成了内存泄漏。
在这里插入图片描述

4.2 怎么防止 Handler 内存泄漏?

想要防止 Handler 内存泄漏,一种方法是把 sThreadLocal 到 Activity 的引用链断开就行了。
  最简单的方法就是在onPause()中使用 Handler 的 removeCallbacksAndMessages(null)方法清除所有消息及回调。就可以把引用链断开了。
  Android 源码中这种方式也很常见,不在 onDestroy()里面调用主要是 onDestroy() 方法不能保证每次都能执行到

4.3 Looper.loop() 为什么不会造成应用卡死?

按照一般的想法来说,loop() 方法是一个死循环,那么肯定会占用大量的 cpu 而导致应用卡顿,甚至说 ANR 。主要是通过 Linux 的 epoll 机制实现的。

5. 总结

  1. Handler的回调方法是在 Looper.loop()所调用的线程进行的;
  2. Handler的创建需要先调用Looper.prepare(),然后再手动调用loop()方法开启循环;
  3. App 启动时会在ActivityThread.main()方法中创建主线程的Looper ,并开启循环,所以主线程使用 Handler 不用调用第2点的逻辑
  4. 延时消息并不会阻塞消息队列;
  5. 异步消息不会马上执行,插入队列的方式跟同步消息一样,唯一的区别是当有消息屏障时,异步消息可以继续执行,同步消息则不行
  6. Callback.handleMessage() 的优先级比 Handler.handleMessage()要高
  7. Handler.post(Runnable)传递的 Runnale 对象并不会在新的线程执行;
  8. Message 的创建推荐使用 Message.obtain()来获取,内部采用缓存消息池实现;
  9. 不要在 handleMessage()中对消息进行异步处理;
  10. 可以通过removeCallbacksAndMessages(null)或者静态类加弱引用的方式防止内存泄漏
  11. Looper.loop()不会造成应用卡死,里面使用了 Linux 的 epoll 机制
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值