美文网首页面试玄机随录
Android - Handler 消息机制

Android - Handler 消息机制

作者: simplehych | 来源:发表于2020-04-09 09:47 被阅读0次

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


    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 机制?

    相关文章

      网友评论

        本文标题:Android - Handler 消息机制

        本文链接:https://www.haomeiwen.com/subject/jhmnphtx.html