Android Handler使用介绍

        Android 中的 Handler 是用来和线程通信的重要工具。它主要用于在后台线程中执行任务,并将结果传递回主线程以更新用户界面。

一、基本概念

  • 线程间通信: Android 应用通常具有主线程(也称为 UI 线程)和后台线程。Handler 允许您从后台线程向主线程发送消息,从而在更新 UI 时避免出现线程安全问题。
  • 消息队列: Handler 使用消息队列来存储消息。每个消息都可以包含一个数据包,用于在线程之间传递信息。

        Handler 不仅仅用于将消息发送到主线程,您还可以在不同的线程之间进行通信。您可以在某个线程上创建 Handler,并将其传递给其他线程以便它们能够向该 Handler 发送消息。

1、使用案例

  • 在后台线程执行任务后更新 UI。
  • 在网络请求完成后通知 UI 更新。
  • 实现定时器功能,定期执行某个任务。

2、注意事项

  • Handler 与线程安全相关。确保您理解线程之间的通信机制以避免出现竞态条件或死锁。
  • 避免在 Handler 中进行耗时操作,以免阻塞主线程。

        总的来说,Handler 是 Android 开发中非常有用的工具,用于实现多线程通信和管理 UI 线程的消息处理。它为开发者提供了一种简单而有效的方法来处理异步任务和更新用户界面。 

二、使用方法

1、创建Handler

        您可以通过以下方式创建一个 Handler:

Handler handler = new Handler();

        这将创建一个与当前线程的 Looper 相关联的 Handler。

2、发送消息

        要向 Handler 发送消息,可以使用以下方法:

handler.sendMessage(Message msg);

        其中 Message 对象包含您要传递的信息。

3、处理消息

        要在 Handler 中处理消息,您需要重写 handleMessage(Message msg) 方法。例如:

Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // 处理收到的消息
    }
};

4、延迟消息

        Handler 还允许您发送延迟消息,可以使用以下方法:

handler.sendMessageDelayed(Message msg, long delayMillis);

        这样可以在指定的延迟时间后将消息发送到消息队列中。

5、移除消息

        有时候您可能需要在某个时刻取消将要发送的消息。您可以使用以下方法:

handler.removeMessages(int what);

        这将移除具有特定标识符的所有消息。

三、子线程创建Handler

        我们平时使用 Handler 都是在主线程中 new Handler(),那么在子线程中也可以这样创建Handler吗?

private Handler threadHandler;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    new Thread(new Runnable() {
        @Override
        public void run() {
            threadHandler = new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(@NonNull Message msg) {
                    return false;
                }
            });
        }
    }).start();
}

        了解 Handler 原理的都知道,Handler 的消息处理是通过 Looper.loop() 里的死循环,不断的从消息队列中取出消息并处理,在主线程中已经自动调用了 Looper.loop() 方法,所以我们可以直接使用 new Handler() 创建,而在子线程中需要我们手动创建。

private Handler threadHandler;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            threadHandler = new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(@NonNull Message msg) {
                    return false;
                }
            });
            Looper.loop();
        }
    }).start();
}

          上面的代码没有问题,但我们都是使用 handler.sendManager() 发送消息,在其他地方我们怎么创建该子线程对应的 handler 去发送消息呢?因为无法拿到子线程的 Looper,所以无法创建。

        下面我们创建一个自定义子线程,提供获取 Looper 方法:

public class MyHandlerThread extends Thread {
    Looper looper;

    MyHandlerThread(String name){
        super(name);
    }

    @Override
    public void run() {
        super.run();
        Looper.prepare();
        looper = Looper.myLooper();
        Looper.loop();
    }

    Looper getLooper(){
        return looper;
    }

    @Override
    public synchronized void start() {
        super.start();
    }
}

        使用自定义 Thread 创建 handler。

MyHandlerThread handlerThread = new MyHandlerThread("cx");
handlerThread.start();

Handler handler = new Handler(handlerThread.getLooper());

//子线程使用完成一定要退出,防止内存溢出
handler.getLooper().quit();

        上面方法中 handlerThread.getLooper() 可能存在空指针异常,以为 run() 为异步加载。所以必须要保证 run() 执行完,再去执行 getLooper() 方法,可以使用 wait() 和 notify() 实现。

public class MyHandlerThread extends Thread {
    Looper looper;

    MyHandlerThread(String name){
        super(name);
    }

    @Override
    public void run() {
        super.run();
        Looper.prepare();
        synchronized (this) {
            looper = Looper.myLooper();
            notifyAll(); //唤醒wait()
        }
        Looper.loop();
    }

    Looper getLooper(){
        synchronized (this) {
            while (looper == null) {//用while,防止被其他notify唤醒,重新判断
                try {
                    wait(); //释放锁,并等待
                } catch (InterruptedException e) {
                }
            }
        }
        return looper;
    }

    @Override
    public synchronized void start() {
        super.start();
    }
}

wait():CPU 释放,线程继续做别的事情。

sleep():睡眠,线程停止等待。

        上面主要是为了理解 wait()、notify() 和 synchronized() 的使用,其实 MyHandlerThread 已经帮我们实现好了,直接使用 HandlerThread 即可。

四、Handler相关问答

1、用一句话概括Handler,并简述其原理

        Handler 是 Android 系统的根本,在 Android 应用被启动的时候,会分配一个单独的虚拟机,虚拟机会执行 ActivityThread 中的 main 方法,在 main 方法中对主线程 Looper 进行了初始化,也就是几乎所有代码都执行在 Handler 内部。Handler 也可以作为主线程和子线程通讯的桥梁。 Handler 通过 sendMessage 发送消息,将消息放入 MessageQueue 中,在 MessageQueue 中通过时间的维度来进行排序,Looper 通过调用 loop 方法不断的从 MessageQueue 中获取消息,执行 Handler 的 dispatchMessage,最后调用 handleMessage 方法。

2、为什么系统不建议在子线程访问UI(为什么不能在子线程更新UI)

        在某些情况下,在子线程中是可以更新 UI 的。但是在 ViewRootImpl 中对 UI 操作进行了 checkThread,但是我们在 onCreate 和 onResume 中可以使用子线程更新UI,由于我们在 ActivityThread 中的 performResumeActivity 方法中通过 addView 创建了 ViewRootImpl,这个行为是在 onResume 之后调用的,所以在 onCreate 和 onResume 可以进行更新UI。
        但是我们不能在子线程中更新 UI,因为 UI 更新本身是线程不安全的,如果添加了耗时操作之后,一旦 ViewRootImpl 被创建将会抛出异常。一旦在子线程中更新 UI,容易产生并发问题。

3、一个Thread可以有几个Looper,几个Handler

        一个线程只能有一个 Looper,可以有多个 Handler。

  • 一个Looper:因为Looper需要循环,循环后面的代码无法执行了,所以一个线程只有一个Looper
  • 多个Handler:为了相互独立,互不干扰,各自处理各自的消息,谁发生的Message谁处理,也可以通过removeMessages清空掉自己的Message。

        线程在初始化时候调用 Looper.prepare() 方法对将静态对象 ThreadLocal 作为 key(整个进程全部线程的 Looper 共用一个 sThreadLocal),创建 Looper 对象作为 value 存储在当前线程的 ThreadLocalMap 中(其实就只有一个 key 对应 value,key 为 sThreadLocal、value 为 looper 对象。这两只有一个,所以 ThreadLocalMap 只有一个键值对)。

        通过线程独有的 ThreadLocal 实现将 Looper 存储在 ThreadLocalMap 这样键值对的数据结构中。

4、Message是如何创建的

        首先考虑一个问题,屏幕刷新率 60Hz(即每秒刷新60次),每次刷新要用到 3 个 Message,也就是每秒钟至少要创建 180 个 Message。这样不断的创建回收,就会出现内存抖动的问题,从而导致 GC、屏幕卡顿等问题。

        为了解决上面的问题,采用 Message 了享元的设计模式,使用 obtain() 方法创建。在Handler 中创建两个线程池队列,一个是我们比较熟悉的 MessageQueue,另一个就是回收池 sPool(最大长度是 50 复用池)。MessageQueue 中 Message 回收时,我们将清空数据的 Message 放回到 sPool 队列中。创建 Manager,我们直接从 sPool 池中取出来就可以了。

        应用场景:地图、股票、RecyclerView复用等对数据的处理都使用了享元模式。

5、主线程中Looper的轮询死循环为何没有阻塞主线程

        Looper 轮询是死循环,但是当没有消息的时候他会 block(阻塞, 阻塞代码没有执行计时操作),ANR 是当我们处理点击事件的时候 5s 内没有响应,我们在处理点击事件的时候也是用的 Handler,所以一定会有消息执行,并且 ANR 也会发送 Handler 消息,所以不会阻塞主线程。Looper 是通过 Linux 系统的 epoll 实现的阻塞式等待消息执行(有消息执行无消息阻塞),而 ANR 是消息处理耗时太长导致无法执行剩下需要被执行的消息触发了 ANR。

6、ANR机制

         启动 Service 为例,APP进程 startService,system_service 进程通知 ActivityManager开始计时,同时 Service 进程开始启动,启动完成后会发送消息到 ActivityManager。

        如果时前台 Service,ActivityManager 20秒没有收到启动完成的消息就会产生ANR。

7、使用Hanlder的postDealy()后消息队列会发生什么变化

        Handler 发送消息到消息队列,消息队列是一个时间优先级队列,内部是一个单向链表。发动 postDelay 之后会将该消息进行时间排序存放到消息队列中。

8、点击页面上的按钮后更新TextView的内容,谈谈理解

        点击按钮的时候会发送消息到 Handler,但是为了保证优先执行,会加一个标记异步,同时会发送一个 target 为 null 的消息,这样在使用消息队列的 next 获取消息的时候,如果发现消息的 target 为 null,那么会遍历消息队列将有异步标记的消息获取出来优先执行,执行完之后会将 target 为 null 的消息移除。(同步屏障:拦截同步消息执行,优先执行异步消息。 View 更新时,draw、requestLayout、invalidate 等很多地方都调用异步消息的方法)。

        同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用 Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的setAsynchronous(true)时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在 loop() 中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

9、生产者-消费者设计模式

        在生产者-消费者模式中如果消费者消费能力大于生产者,或者生产者生产能力大于消费者,需要一个限制,在 java 里有一个 blockingQueue。当目前容器内没有东西的时候,消费者来消费的时候会被阻塞,当容器满了的时候也会被阻塞。

        这里 Handler.sendMessage 相当于一个生产者,MessageQueue 相当于容器,Looper 相当于消费者。Handler 不使用 java 的 blockingQueue 的原因是除了我们上层需要使用到 Handler,其实底层的消息都是需要传递给 Handler 处理,比如:驱动层需要发事件给APP、屏幕点击事件、底层刷新通知等,所以我们使用的是 Native 层提供的 MessageQueue 实现消息队列。

10、Handler是如何完成子线程和主线程通信的

        在主线程中创建 Handler,在子线程中发送消息,放入到 MessageQueue 中,通过 Looper.loop 取出消息进行执行 handleMessage,由于 looper 我们是在主线程初始化的,在初始化 looper 的时候会创建消息队列,所以消息是在主线程被执行的。

11、关于ThreadLocal

        ThreadLocal 实例进程内只有一个(静态实例),但其内部的 set 和 get 方法是获取的当前线程的 ThreadLocalMap 对象,ThreadLocalMap 是每个线程有一个单独的内存空间,不共享,ThreadLocal 在 set 的时候会将数据存入对应线程的 ThreadLocalMap中,key = ThreadLocal,value = 值。

12、Handler内存泄漏问题及解决方案

        内部类持有外部类的引用导致了内存泄漏,如果 Activity 退出的时候,MessageQueue中还有一个 Message 没有执行,这个 Message 持有了 Handler 的引用,而 Handler 持有了 Activity 的引用,导致 Activity 无法被回收,导致内存泄漏。使用 static 关键字修饰,在 onDestory 的时候将消息清除。

        简单理解,当 Handler 为非静态内部类时,其持有外部类 Actviity 对象,所以导致 static Looper -> mMainLooper -> MessageQueue -> Message -> Handler -> MainActivity,这个引用链中 Message 如果还在 MessageQueue 中等待执行,则会导致 Activity 一直无法被释放和回收。 

        导致的原因是因为 Looper 需要循环,所以一个线程只有一个 Looper,但一个线程中可有多个 Handler,MessageQueue 中消息 Message 执行时不知道要通知哪个 Handler 执行任务,所以在 Message 创建时中存入了 Handler 对象 target 用于回调执行的消息。如果 Handler 是 Activity 这种短生命周期对象的非静态内部类时,则会让创建出来的 Handler 对象持有该外部类 Activity 的引用,Message 还在队列中导致引用着 Handler,而非静态内部类 Handler 引用外部类 Activity 导致 Activity 无法被回收,最终导致内存泄漏。

        解决办法:

  • Handler 不能是 Activity 这种短生命周期的对象类的内部类;
  • 在 Activity 销毁时,将创建的 Handler 中的消息队列清空并结束所有任务。
  • 将 handler 设置成 static,static 变量是全局变量,不能够自动引用外部类变量,这时Handler 就不再持有 MainActivity,MainActivity 就可以正常释放。

13、Handler异步消息处理(HandlerThread)

        内部使用了 Handler+Thread,并且处理了getLooper 的并发问题。如果获取 Looper 的时候发现 Looper 还没创建,则 wait,等待 looper 创建了之后在 notify。

14、子线程中的Looper,无消息时如何处理

        子线程中创建了 Looper,当没有消息的时候子线程将会被 block,无法被回收,所以我们需要手动调用 quit 方法将消息删除并且唤醒 looper,然后 next 方法返回 null 退出 loop。

15、多个Handler往MessageQueue中添加数据(Handler可能处于不同线程),如何确保线程安全

        在添加数据和执行 next 的时候都加了 this 锁,这样可以保证添加的位置是正确的,获取的也会是最前面的。

16、关于IntentService

        HandlerThread + Service 实现,可以实现 Service 在子线程中执行耗时操作,并且执行完耗时操作时候会将自己 stop。

17、Glide如何维护生命周期

        一般想问的应该都是这里为什么会判断两次 null :

@NonNull
private RequestManagerFragment getRequestManagerFragment(@NonNull final android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) {
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
    if (current == null) {
        current = pendingRequestManagerFragments.get(fm);
        if (current == null) {
            current = new RequestManagerFragment();
            current.setParentFragmentHint(parentHint);
            if (isParentVisible) {
                current.getGlideLifecycle().onStart();
            }
            pendingRequestManagerFragments.put(fm, current);
            fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
            handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
        }
    }
    return current;
}

1)在多次调用 with 的时候,commitAllowingStateLoss 会被执行两次,所以我们需要使用一个 map 集合来判断,如果 map 中已经有了证明已经添加过了。
2)handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(),我们需要将 map 里面的记录删除。

18、handler主线程阻塞了怎么办

        Android 系统事件驱动系统,loop 循环处理事件,如果不循环程序就结束了。

19、Handler底层为什么用epoll,为什么不用select和poll

        Socket 非阻塞 IO 中 select 需要全部轮询不适合,poll 也是需要遍历和 copy,效率太低了。epoll 非阻塞式 IO,使用句柄获取 APP 对象,epoll 无遍历,无拷贝。还使用了红黑树(解决查询慢的问题)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/488958.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

标题:Three.js:开源的JavaScript 3D库探索与实践

摘要: 本文将深入探讨Three.js,一个开源的JavaScript 3D库。Three.js提供了一种简单易用的方式来创建和显示3D图形,打破了Web开发和3D图形之间的障碍。本文将介绍Three.js的特性和用法,以及如何通过简单的示例来展示其功能。 一、…

Go语言学习Day2:注释与变量

名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 1、注释①为什么要写注释?②单行注释…

本周四Techtalk技术交流社区邀请吕海波老师为大家带来精彩技术分享

欢迎您关注我的公众号【尚雷的驿站】 **************************************************************************** 公众号:尚雷的驿站 CSDN :https://blog.csdn.net/shlei5580 墨天轮:https://www.modb.pro/u/2436 PGFans:ht…

leetCode刷题 18. 删除链表的倒数第 N 个结点

目录 1. 思路 2. 解题方法 3. 复杂度 4. Code 题目: 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 示例 1: 输入:head [1,2,3,4,5], n 2 输出:[1,2,3,5]示例 2: 输入&…

【二叉树】Leetcode 94. 二叉树的中序遍历【简单】

二叉树的中序遍历 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。 示例 1: 输入:root [1,null,2,3] 输出:[1,3,2] 解题思路 中序遍历是一种二叉树遍历方式,按照“左根右”的顺序遍历二叉树节点。 1、递归…

43 带 fixed 列的 el-table 不兼容于 sortablejs

前言 这是一个基于 sortablejs 来实现的 el-table 的拖拽功能的基础实现 然后 这个过程中遇到的一个比较特殊的问题是, 关于 el-table-column 的 fixed 的属性, 对于 sortablejs 这边来定位目标选择列 影响的一个问题 在基础的用例中, 使用 “.el-table__body-wrapper tbo…

免费redis可视化工具windows/mac都可以使用,开源免费

官方地址:RedisInsight | The Best Redis GUI github开源地址:GitHub - RedisInsight/RedisDesktopManager Redis Desktop Manager – Redis可视化管理工具、redis图形化管理工具、redis可视化客户端、redis集群管理工具。 官方下载方式 滚动到页面底…

考研数学一——概率论真题——自我总结题型整理(总分393)

系列文章目录 终于考完研了,本人考的是南京航空航天大学的仪器科学与技术,英一数一电路,以下是成绩单: 平时习惯整理自己的学习体系,以下是一个记录。 其实,每个人都应该训练,看到某一类题目…

怎么开发水果店小程序_指尖上的新鲜果园

水果店小程序:指尖上的新鲜果园,让生活更甜美! 在这个快节奏的现代生活中,人们对于便捷、高效的生活方式有着越来越高的追求。购物也不例外,我们都在寻找着一种更加轻松、快捷的购物方式。而水果店小程序,…

javaSwing连连看游戏

一、简介 基于java的连连看游戏设计和实现,基本功能包括:消除模块,重新开始模块,刷新模块,选择难度模块,计时模块。本系统结构如下: (1)消除模块: 完成连连…

Adobe Illustrator和Photoshop哪个难学?另一款好用设计软件上位!

当设计开始时,几乎没有人不知道。 Adobe 公司的两大设计软件:Adobe Illustrator 和 Photoshop。虽然 Adobe Illustrator和 Photoshop 很有名,有一定设计经验的设计师在前期探索使用后可以对 Adobe Illustrator和 Photoshop 的使用差异有一个大…

蓝桥杯JAVA-试题-基础练习

一、十六进制转八进制 资源限制 内存限制:512.0MB C/C时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s 问题描述   给定n个十六进制正整数,输出它们对应的八进制数。输入格式   输入的第一行为一个正整…

AI老人跌倒监测报警摄像机

AI老人跌倒监测报警摄像机是一种基于人工智能技术的智能监控设备,专门用于监测老年人的跌倒情况并提供实时报警功能,以及时处理紧急情况,保障老人安全。这种摄像机利用先进的AI算法和深度学习技术,能够实时监测老人的行为&#xf…

Linux下的I/O模型

目录 一、什么是IO? 二、IO操作的两个阶段 三、五种I/O模型 1、阻塞I/O(blocking I/O) 2、非阻塞I/O(non-blocking I/O) 3、多路复用I/O(multiplexing I/O) 4、信号驱动I/O(signal-driven I/O) 5、异步I/O(asynchronous I/O) 四、五种I/O模型比较 一、什么…

ElasticSearch启动报错:Exception in thread “main“ SettingsException

Exception in thread "main" SettingsException[Failed to load settings from [elasticsearch.yml]]; nested: ParsingException[Failed to parse object: expecting token of type [START_OBJECT] but found [VALUE_STRING]]; 这个报错说明elasticsearch.yml这个配…

Java虚拟机运行原理

在 Java 中新建一个类Test: class Test {int a; }在Main方法中创建两个 Test 对象,并给 a 赋不同的值。 写一个 exchange 方法,在方法中交换两个Test 对象,最后输出两个对象中 a 的值。 public class Main {public static void…

OpenCV4.9的是如何进行图像操作

返回:OpenCV系列文章目录(持续更新中......) 上一篇:OpenCV4.9矩阵上的掩码操作 下一篇:使用 OpenCV 添加(混合)两个图像 输入/输出 C 图像 从文件加载图像 Mat img imread(filename); 如…

NVIDIA 宣布推出适用于人形机器人的 GR00T 项目基础模型和主要 Isaac 机器人平台更新

NVIDIA 宣布推出适用于人形机器人的 GR00T 项目基础模型和主要 Isaac 机器人平台更新 Isaac 机器人平台现为开发人员提供新的机器人训练模拟器、Jetson Thor 机器人计算机、生成式 AI 基础模型以及 CUDA 加速感知和操作库 GTC — NVIDIA 今天宣布推出 GR00T 项目,这…

【数据分享】1929-2023年全球站点的逐月平均露点(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据,气象指标包括气温、风速、降水、能见度等指标,说到气象数据,最详细的气象数据是具体到气象监测站点的数据! 有关气象指标的监测站点数据,之前我们分享过1929-2023年全球气象站…

为什么有些网站会提示不安全,提示您与此网站之间建立的连接不安全

有时候当我们尝试访问一个网站时,浏览器会弹出一个警告,提示“您与此网站之间建立的连接不安全”。这是什么意思?这种网站真的不安全吗? 理解HTTP与HTTPS HTTP(超文本传输协议)是互联网上用于传输数据的基…
最新文章