面试玄机随录

Android - Handler 消息机制

2020-04-09  本文已影响0人  simplehych

本文不涉及底层原理,仅把知识串一下


2020.5.17 更新

  1. 是什么(线程消息通信机制(共享内存模型和消息传递模型);主要场景是子线程切换到主线程)
  2. 为什么存在(主线程不能进行耗时操作;子线程不能更新 UI(UI 线程不安全;加锁耗时+逻辑复杂);)
  3. 核心 API(Message、MessageQueue、Looper、Handler、ThreadLocal)
  4. 原理
    1. Looper.loop() 和 MessageQueue.next() 内部都是 for(;;) 无限循环,
    2. Looper.loop() 通过 MessageQueue.next() 获取 msg,然后调用 msg.target.dispatchMessage(msg) 即交给 Handler 处理消息
    3. MessageQueue.next() 1. 有消息时返回msg;2.没消息时通过 nativePollOnce 阻塞;3. 队列退出返回 null
    4. 根据 msg.when 系统相对时间进行 MessageQueue 的插入或排队
    5. dispatchMessage 处理优先级 msg.callback > mCallback > handleMessage
  5. 使用方式
    1. 主线程
    2. 子线程
    3. HandlerThread,IntentService
    4. 第三方库 RxJava、EventBus
  6. 注意事项
    1. 切换线程的本质 ThreadLocal
    2. MessageQueue 是一个链表
    3. Handler 依赖 Looper 处理消息,而 Looper 和线程绑定
    4. Handler.post/postAtTime/postDelayed/postAtFrontOfQueue 系列方法通过 getPostMessage(Runnable r){msg.callback = r} 最终调用 Handler.sendMessage/sendMessageDelayed/sendMessageAtTime/sendEmptyMessage/sendEmptyMessageDelayed/sendEmptyMessageAtTime/sendMessageAtFrontOfQueue 系列方法
    5. 内存泄漏(静态内部类+弱引用;移除消息;)
    6. 主线程死循环问题(程序运行本质保证不会退出;主线程的消息循环机制;Linux的循环异步pipe/epoll方式不占用资源)
    7. 主线程卡顿问题(运行在子线程交互;onCreate/onStart/onResume的超时 ANR)
  7. 其他
    1. 子线程更新 UI 的方式。1. 实现 Handler;2. runOnUiThread;3.view.post(Runnable)
    2. 子线程不能更新 UI 的原因,ViewRootImpl 的 checkThread()在 Activity 维护的 View 树的行为
    3. 子线程 Toast 、showDialog,是在 Window.addView,并非在 ViewRootImpl,所以可以在子线程更新 UI,需要初始化子线程的 Looper.prepare()/.loop
    4. 切记调用 Looper.myLooper().quit()
    5. interface IdleHandler { boolean queueIdle()},空闲 Handler,方法返回 false表示只回调一次

Handler 是 Android 消息机制的上层接口。
作用:将一个任务切换到某个指定的线程中去执行
使用场景:通常是在子线程进行耗时I/O等操作,然后回到主线程更新UI;

1 背景

  1. Android 规定访问UI只能在主线程中进行,ViewRootImpl#checkThread。
  2. Android 建议不要在主线程进行耗时操作,否则会导致 ANR

鉴于以上两者存在矛盾,Android提供了Handler这个功能。

补充:

  1. 为什么不允许在子线程访问UI?这是Android的UI控件不是线程安全的,如果在多线程并发访问可能导致UI控件处于不可预期的状态。
  2. 为什么不对UI控件的访问加上锁机制?缺点有两个:1. 会让UI访问逻辑变得复杂;2. 降低UI访问的效率;

扩展:

  1. 区分 Eventbus 消息机制的跨线程发送消息,观察者模式

2 关键字

Handler、Looper、MessageQueue、ThreadLocal

3 原理描述

3.1 ThreadLocal

作用:某些数据是以线程为作用域,不同的线程具有不同的数据副本

使用场景:Looper、ActivityThread、AMS

其他实现方式:提供全局哈希表管理

3.2 MessageQueue

名字是队列,其实采用单链表的数据结构,在插入和删除上有优势。

包含两个操作:插入enqueueMessage 和读取next(while读取,读取操作伴随着删除操作)

next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里,当有新消息到来时,next方法会返回这条消息并将其从单链表中移除。

3.3 Looper

扮演消息循环的角色,不停的从MessageQueue中查看是否有新消息,如果有就会立刻处理,否则就一直阻塞在那里

子线程使用

new Thread(){
    @Override
    public void run(){
        Looper.prepare(); // 为当前线程创建一个Looper
        Handler handler = new Handler();
        Looper.loop();//开启消息循环***
    }
}.start();

Looper.quit(); 直接退出
Looper.quitSafely();把消息队列中的已有消息处理完毕后才安全退出

Looper 最重要的方法是 loop方法,只有调用了loop后,消息系统才会真正地起作用。

  1. loop是一个死循环,会调用MessageQueue的next方法获取新消息
  2. 没消息时,next是一个阻塞操作,也导致loop会阻塞。
  3. 有消息时,会处理消息,执行 msg.target.dispatchMessage(msg); 其中 msg.target就是发送这条消息的Handler对象,在enqueueMessage添加时赋值。

3.4 Handler

主要工作:消息的发送和接收过程。

发送:通过post或send一系列方法实现(post最终调用send),这个过程只是向消息队列插入了一条消息。

接收:Looper不断循环从队列中取消息,然后调用Handler的dispatchMessage处理消息。共有以下三个互斥方式处理,三个优先级高低依次为1、2、3

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg); //  1、 post发送消息所传递的Runnable参数
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) { // 2、初始化Handler时设置的Callback回调
                return;
            }
        }
        handleMessage(msg); // 3、派生子类重写handleMessage方法
    }
}

4 主线程 Handler 使用示例

主线程的消息循环

Android的主线程就是ActivityThread,在入口方法main中创建Looper并开启循环

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper(); // 创建Looper 和 MessageQueue
    ...
    Looper.loop(); // 开启循环
    ...
}

主线程的Handler是内部类 H

ActivityThread和AMS的进程间通信通过ApplicationThread(是Binder,不是线程)进行。

ActivityThread - AMS 通信

内存泄漏问题

在Java中,非静态内部类 & 匿名内部类都默认持有 外部类的引用

解决方案1:静态内部类+弱引用

解决方案2:当外部类结束生命周期时,清空Handler内消息队列

5 参考资料

语雀 Handler面试知识点
《Android开发艺术探索》第10章 Android的消息机制
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
为什么 Android 要采用 Binder 作为 IPC 机制?

上一篇下一篇

猜你喜欢

热点阅读