Android | Handler

Handler 的主要使用场景

子线程完成耗时操作的过程中,通过 Handler 向主线程发送消息 Message,用来更新 UI 界面。因为 Android 是在主线程中更新 UI 的,在主线程出现耗时操作时,就会导致用户界面卡顿,所以我们一般都把耗时的操作(网络请求、IO 等)放到子线程中,然后通过 Handler 的方式让主线程更新 UI。

new Handler()

如果是无参构造器,其中调用了重载的构造方法并分别传入 nullfalse。并在构造方法中给两个全局变量赋值,两者都是通过 Looper 来获取。

@UnsupportedAppUsage  
final Looper mLooper;  
final MessageQueue mQueue;  
@UnsupportedAppUsage  
final Callback mCallback;  
final boolean mAsynchronous;public Handler(@Nullable Callback callback, boolean async) {  if (FIND_POTENTIAL_LEAKS) {  final Class<? extends Handler> klass = getClass();  if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  (klass.getModifiers() & Modifier.STATIC) == 0) {  Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  klass.getCanonicalName());  }  }  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;  
}
Looper

启动一个 Java 的入口函数是 main 方法,当 main 函数执行完毕后程序就会停止运行,但是我们打开一个 Activity,只要不返回则会一直显示,即 Activity 所在进程会一直处于运行状态。

实际上 Looper 内部维护一个无限循环,保证 APP 的持续运行。

Activity 启动时,ActivityThread#main 方法是新 APP 进程的入口

// ActivityThread#main
public static void main(String[] args) {//...// 初始化当前进程的 Looper 对象Looper.prepareMainLooper();//...if (sMainThreadHandler == null) {  sMainThreadHandler = thread.getHandler();  }//...// 调用 Looper#loop 方法开启无限循环Looper.loop();//...
}  // Looper#prepareMainLooper
public static void prepareMainLooper() {  prepare(false);  synchronized (Looper.class) {  if (sMainLooper != null) {  throw new IllegalStateException("The main Looper has already been prepared.");  }  // 将从线程本地变量中取出的 Looper 对象赋值给 sMainLooper 对象sMainLooper = myLooper();  }  
}// Looper#prepare
private static void prepare(boolean quitAllowed) {  // 一个线程中的 Looper#prepare 方法只能执行一次 否则会抛出异常if (sThreadLocal.get() != null) {  throw new RuntimeException("Only one Looper may be created per thread");  }  // 新建一个 Looper 对象 并设置到线程本地变量中 // 即创建的 Looper 与当前线程绑定sThreadLocal.set(new Looper(quitAllowed));  
} // Looper#Looper 构造方法
private Looper(boolean quitAllowed) {  // 初始化了 MessageQueue 对象mQueue = new MessageQueue(quitAllowed);  mThread = Thread.currentThread();  
}// Looper#myLooper
public static @Nullable Looper myLooper() {  // 从线程本地变量中取出 Looper 对象return sThreadLocal.get();  
}

Looper#prepare 方法一个线程中只能被调用一次(会判定线程本地变量是否为空),即 Looper 的构造方法在一个线程中只能被调用一次,构造方法中 MessageQueue 在一个线程中只会被初始化一次,所以一个线程只会有一个 MessageQueue 对象。

Looper 的任务就是不断从 MessageQueue 中取出 Message,然后处理 Message 中指定的任务。在 main 方法中调用的 Looper.loop 方法就是完成这件事的。Looper#loop 方法中有个死循环,这就是 Android App 进程能不断运行的原因。

如果从 MessageQueue 中取出的 message 不为空,则取出 messagetarget 对象并调用其 dispatchMessage 方法处理 Message 方法本身。这个 target 对象就是 Handler

// Looper#loop
public static void loop() { final Looper me = myLooper();//...for (;;) {  if (!loopOnce(me, ident, thresholdOverride)) {  return;  }  }
}// Looper#loopOnce
private static boolean loopOnce(final Looper me,  final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // might blockif (msg == null) {return false;}//...try {  msg.target.dispatchMessage(msg);  if (observer != null) {  observer.messageDispatched(token, msg);  }  dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;  } catch (Exception exception) {  //...} finally {  //... }//...return true;
}// Message
public final class Message implements Parcelable {//...// target 即传递的 Handler 对象@UnsupportedAppUsage  /*package*/ Handler target;
}// Handler#dispatchMessage
public void dispatchMessage(@NonNull Message msg) {  if (msg.callback != null) {  handleCallback(msg);  } else {  if (mCallback != null) {  if (mCallback.handleMessage(msg)) {  return;  }  }  // 调用 handlerMessage 方法handleMessage(msg);  }  
}// Handler#handlerMessage
public void handleMessage(@NonNull Message msg) { // 该方法为空// 在创建 Handler 时需要继承并重写该方法
}
Handler#sendMessage 方法

Handler 包括几个重载的 sendMessage 方法,该方法最后会通过 enqueueMessage 方法将 Message 插入到消息队列 MessageQueue 中。这个消息队列就是我们在 ActivityThreadmain 方法中通过 Looper 创建的 MessageQueue

enqueueMessage 方法中,将 Handler 自身设置为 target 对象,后续 Message 会调用此 HandlerdispatchMessage 来处理。MessageQueue 是一个按照 Message 执行时间排序的有序队列,在使用 enqueueMessage 方法时会根据 Message 的时间 when 来有序插入 Message 到队列中。

Handler#post 方法

Looper#loopMessageQueue 中取出 Message 时,会调用 targetdispatchMessage 来处理消息。

如果 msg.callback 不为空,执行的是 handleCallback(msg) 去处理消息,如果 msg.callback 为空,会调用 handleMessage(msg) 来处理消息(即我们重写的方法)。

private static void handleCallback(Message message) {  message.callback.run();  
}

handleCallback 会直接执行 Runnablerun 方法,Runnable 实际上是一个回调接口,与线程 Thread 无关。

Looper#loop 方法为什么不会阻塞主线程

Looper#loop 方法实际上是一个死循环,但是不会造成 UI 线程阻塞。因为在 MessageQueuenext 方法中调用了 native 方法 nativePollOnce,当调用该方法时主线程会释放 CPU 资源进入休眠状态,直到下一条消息到达或者有事务发生,通过 pipe 管道写段写入数据唤醒主线程工作,采用 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

Handler 可能造成内存泄漏

HandlerMessage 被存储在 MessageQueue 中,有些 Message 不能马上被处理,在队列中存在的时间很长(排序靠后或者有使用了延时执行 sendMessageDelay 来发送 msg,导致 Handler 无法被回收,如果 Handler 是非静态的,那么 Handler 也会导致引用它的 ActivityService 不能被回收。

可能场景是:发送一条延时 msgMessageQueue 中,未执行就 finish 关闭 Activity。导致 handlerActivity 内存都不会被释放。

解决方式是使用一个静态的 Handler 内部类继承 handler,且 Handler 持有的对象使用弱引用,并且在 ActivityonDestroy 方法中移除传入到 MessageQueue 中的消息。

Handler 特性
  • 一个线程只有一个 Looper,可以有多个 Handler 来处理消息,Looper 通过 Handler 对象的 handlerMessage 方法来处理消息。
  • 在两个都不是主线程的线程中想要使用 Handler 的话需要在处理逻辑的线程中创建 Looper,有了 Looper 后才能创建 Handler,之后再另外的线程中使用 Looper 线程的 handler 对象来发送 message
  • 主线程获得 Looper 方法是:Looper.getMainprepare(),可以在子线程直接 new 一个 Handler,需要在一个线程先 Looper.prepare()Looper.loop()
  • Handler 在创建时会采用当前线程的 Looper 来构造消息循环系统, Looper 在哪个线程创建,就和哪个线程绑定,Handler 是在其关联的 Looper 对应的线程中处理消息的。
如何验证 Handler 是否持有了外部类的引用

通过 class 判断。

子线程中更新 UI 的方法

首先需要在主线程当中创建一个 Handler 对象,并重写 handleMessage() 方法。

然后当子线程中需要进行UI操作时,就创建一个 Message 对象,并通过 Handler 将这条消息发送出去。

之后这条消息会被添加到 MessageQueue 的队列中等待被处理,而 Looper 则会一直尝试从 MessageQueue 中取出待处理消息,最后分发回 HandlerhandleMessage() 方法中。

由于 Handler 的构造函数中我们传入了 Looper.getMainLooper(),所以此时 handleMessage() 方法中的代码也会在主线程中运行,然后便可以在此更新 UI

一条Message经过以上流程的辗转调用后,也就从子线程进入了主线程,从不能更新UI变成了可以更新UI。

总结

应用启动是从 ActivityThread#main 方法开始的,先执行了 Looper#prepare 方法,创建 Looper 对象并绑定到当前线程 MainThread 中,而 Looper 对象创建时会初始化 MessageQueue 队列,因此我们会在主线程中获得一个 Looper 对象与 MessageQueue 队列。

当我们创建一个 Handler 子对象时,在构造方法中通过 ThreadLocal 方式获取绑定的 Looper 对象,并获取此 Looper 对象的成员变量 MessageQueue 作为该 Handler 对象的成员变量。

在子线程中调用创建好的 Handler 子类对象的 sendMessage 方法,将 Message 对象的 target 属性设置为 Handler 子对象自身,调用 MessageQueue 对象的 enqueueMessage 方法将 msg 插入 MessageQueue 中。

在主线程中的 Looper#loop 方法中会不断读取 MessageQueue 中的消息,如果消息不为空,就会执行 msg#target#dispatchMessage 方法,这个方法会调用我们在创建 Handler 对象时重写的 handlerMessage 方法。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

javascript 设计模式 ( 读书笔记 )

javascript 设计模式 电子书链接 余杭子曰 用对象收编变量,防止变量覆盖和变量污染 let checkObject {checkEmail: function () {console.log("邮箱校验");},checkPhone: function () {console.log("手机号校验");},checkPasswork: function () {console…

【Linux】psplash制作Linux开机动画

1. 下载psplash软件 下载psplash源码到ubuntu中&#xff1a; 下载地址&#xff1a;https://git.yoctoproject.org/psplash/commit/安装依赖环境 sudo apt-get install libgdk-pixbuf2.0-dev2. 准备图片 开机动画静态图片&#xff1a;psplash-poky.png开机动画进度条图片&…

哪些车企AEB标配率「不及格」

对于汽车智能化来说&#xff0c;基础安全不分高低配。但实际情况&#xff0c;却是另一番景象。 在全球范围&#xff0c;目前不少国家及地区的监管机构正在考虑将AEB&#xff08;紧急制动系统&#xff09;作为乘用车的标配纳入法规&#xff0c;“这是道路安全向前迈出了重要的一…

2023年9月青少年机器人技术(三级)等级考试试卷-理论综合

2023年9月青少年机器人技术等级考试&#xff08;三级&#xff09;理论综合试卷 单选题 第 1 题 单选题 Arduino Nano主控板&#xff0c;通过光敏电阻控制LED灯亮度的变化。电路搭设及程序如下图所示&#xff0c;当光照强度逐渐增强时&#xff0c;LED的亮度逐渐减弱&#xff…

CentOS 7设置固定IP地址

当我们安装了一个虚拟机或者装了一个系统的时候&#xff0c;经常会遇到需要设置固定ip的情况&#xff0c;本文就以Centos 7为例&#xff0c;讲述如何修改固定IP地址。 1、用ifconfig命令查看使用的网卡 如上图所示&#xff0c;我们就会看到我们目前使用的网卡名称 2、编辑网卡…

Systemd服务内存占用高的处理

参考文章 ### https://blog.csdn.net/weixin_44821644/article/details/121095406## https://blog.csdn.net/c123m/article/details/124301104 现象 检查 操作系统是4C8G&#xff0c;systemd的内存使用率比较高。操作系统日志没看到异常。很多服务通过systemd托管 ## 检查有…

(三)(Driver)驱动开发之双机调试环境搭建及内核驱动的运行

文章目录 1. 驱动开发环境搭建2. 驱动开发新建项目及项目属性配置和编译3. 双机调试环境搭建3.1 安装虚拟机VMware3.2 配置Dbgview.exe工具3.3 基于Windbg的双机调试 4. 内核驱动的运行4.1 临时关闭系统驱动签名校验4.2 加载驱动 1. 驱动开发环境搭建 请参考另一篇:https://bl…

Oracle11gr2 + plsql 配置

一、在Oracle中使用cmd窗口进行imp导入文件时&#xff0c;有时会报错IMP-00000: 未成功终止导入。将cmd窗口使用管理员运行&#xff0c;在进行imp导入文件时&#xff0c;又会报imp不是内部或外部命令,也不是可运行的程序。针对这种问题&#xff0c;是环境变量没配置好的原因 1…

7. Cesium中的Primitive

1. Primitive 介绍 在 Cesium 中&#xff0c;Primitive 是一种基本的图元&#xff0c;用于呈现 3D 场景中的几何形状、材质和其他属性。 Primitive 由两个部分组成&#xff0c;一个是几何形状&#xff08;Geometry&#xff09;&#xff0c;用于定义 Primitive 的结构&#xf…

Guava-RateLimiter详解

简介&#xff1a; 常用的限流算法有漏桶算法和令牌桶算法&#xff0c;guava的RateLimiter使用的是令牌桶算法&#xff0c;也就是以固定的频率向桶中放入令牌&#xff0c;例如一秒钟10枚令牌&#xff0c;实际业务在每次响应请求之前都从桶中获取令牌&#xff0c;只有取到令牌的请…

Unity Spine 指定导入新Spine动画的默认材质

指定导入新Spine动画的默认材质 找到Spine的Editor导入配置如何修改方法一: 你可以通过脚本 去修改Assets/Editor/SpineSettings.asset文件方法二&#xff1a;通过面板手动设置 找到Spine的Editor导入配置 通常在 Assets/Editor/SpineSettings.asset 配置文件对应着 Edit/Prefe…

k8s简介以及各个组件

Kubernetes 概述 1、K8S 是什么&#xff1f; K8S 的全称为 Kubernetes (K12345678S)&#xff0c;PS&#xff1a;“嘛&#xff0c;写全称也太累了吧&#xff0c;不如整个缩写”。 作用&#xff1a; 用于自动部署、扩展和管理“容器化&#xff08;containerized&#xff09;应用…

wxPython 布局调试技巧

在Show()与MainLoop()直接加入以上代码 import wx.lib.inspection ...frame.Show() wx.lib.inspection.InspectionTool().Show() app.MainLoop()启动后会弹出布局查看工具

蓝桥杯每日一题2032.10.24

蓝桥杯大赛历届真题 - C 语言 B 组 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 由于布局为两个字节为一行&#xff0c;那我们输入两个数就为一行&#xff0c;但是这两个数全部得用二进制进行表示使用bitset bitset:将一个数转化为二进制 bitset<8>:将一个数转化为8位…

【Unity3D】Unity与Android交互

1 Unity 发布 apk 1.1 安装 Android Build Support 在 Unity Hub 中打开添加模块窗口&#xff0c;操作如下。 选择 Android Build Support 安装&#xff0c;如下&#xff08;笔者这里已安装过&#xff09;。 创建一个 Unity 项目&#xff0c;依次点击【File→Build Settings→…

springboot maven项目环境搭建idea

springboot maven项目环境搭建idea 文章目录 springboot maven项目环境搭建idea用到的软件idea下载和安装java下载和安装maven下载和安装安装maven添加JAVA_HOME路径&#xff0c;增加JRE环境修改conf/settings.xml&#xff0c;请参考以下 项目idea配置打开现有项目run或build打…

ubuntu 安装卸载 deb软件

install sudo dpkg -i 软件包名.deb uninstall sudo apt-get remove 软件包名称 reference https://help.ubuntu.com/kubuntu/desktopguide/zh_CN/manual-install.html

什么是WMS系统条码化管理

WMS系统是一种用于仓库管理的信息化系统&#xff0c;旨在提高仓库操作的效率和准确性。而在WMS系统中&#xff0c;条码化管理是一项关键的技术和方法&#xff0c;它通过将商品和物料打上条码&#xff0c;并利用扫描设备进行数据采集和处理&#xff0c;实现了仓库管理的全面自动…

深度学习模型笔记

加载和保存模型参数 保存模型参数 net MLP() # 此处省略训练过程&#xff0c;在训练之后&#xff0c;保存模型参数 # 保存字典格式的模型参数&#xff0c;模型参数名 torch.save(net.state_dict(), mlp.params) 加载模型参数 clone MLP() # 加载模型参数 clone.load_state…

【Axios封装示例Vue2】

文章目录 为什么要封装axios&#xff1f;如何封装axios在Vue组件中使用封装的axios 为什么要封装axios&#xff1f; 在Vue 2项目中&#xff0c;直接在组件中使用axios可能会导致以下问题&#xff1a; 代码重复&#xff1a;每个组件都需要导入axios并编写相似的请求代码&#…