Android-Handler详解_使用篇

本文我将从Handler是什么、有什么、怎们用、啥原理,四个方面去分析。才疏学浅,如有错误,欢迎指正,多谢。 

1.是什么

因为Android系统不允许在子线程访问UI组件,否则就会抛出异常。所以咱们平实用的最多的可能是在子线程将更新UI的任务传达给UI线程去执行。但是别误会,它绝不仅仅是用来在子线程更新UI。Handler可以将任何线程的任务切换到它所在的线程去执行。

总体的流程其实就是: 初始化Handler并初始化CallBack重写handleMessage方法用来接收消息。handler的sendMessage发送消息。嗯嗯看起来好像就这么简单,我们实际用一下。

2.有什么

Handler的运行机制要依赖其他三个重要的类,分别是Message、MessageQueue、Looper。

2.1 Handler

最重要的作用就是,负责发消息和收消息。

2.2 Message

Message一看便知,是消息,消息机制肯定离不开消息这个载体啊,Messege就是消息本息。

它包含几个比较重要的成员变量what、arg1、arg2、都是int类型,what我们一般用来区分不同的消息;obj是个Object可以传递对象,一般传递数据主要在这个里面;还有target,它是Handler类型的,我们能接收到消息target功不可没《划重点》。

创建Message对象可以直接new Message() 也可以通过Message.obtain()获取,建议使用后者。然后通过handler的sendMessage(message)就把消息发出去了。上面提到的几个变量我们可以给他们赋值也可以不赋值,但是建议最少给what赋个值,如果都不赋值收到多个消息时会无法区分甲乙丙丁谁是谁,仅一条消息时您随意。发送消息还有sendEmptyMessage空消息不用message、sendMessageDelayed延时消息、sendEmptyMessageDelayed延时空消息、sendEmptyMessageAtTime等。

2.3 MessageQueue

MessageQueue消息队列专门用来存储消息,起名稍微有点误导性,它数据结构不是一个队列而是一个单列表。

此刻先记住他有两个重要的方法enqueueMessage和next(),前者用来存储消息后面用来从中取出消息并发送给handler的handlerMessage,注意这里的next是MessageQueue中方法和上面提到的Message类的next变量别混淆。
MessageQueue不生产消息,是个仓库管理员,只负责入库和出库。

看看它为啥是个单链表吧

链表就是对象的循环调用。当前要处理的messge是放到MessageQueue的mMessages变量,MessageQueue的mMessages变量是个Message,而Message类有一个next变量也是Message类型,同时这个Message类型的next变量又有自己的next变量,层层嵌套。后面说原理时具体分析。先上图,内容太多没有截取完整但能表达清楚关系:这里是使用下面3.1中方式一发送消息的断点截图。

2.4 Looper

Looper 负责取消息并发给handler的handlerMessage方法,核心方法是loop()它会调用MessageQueue的next()方法取消息并调用Handler的dispatchMessage(msg)将消息发送给handleMessage回到方法 ;

因为使用Handler必须要有looper因此它还提供了prepare()方法可以帮你实例化一个looper并存在当前线程的ThreadLocal中,因为你当前线程可能不是在主线程但你又要在主线程接收消息测试可以调用另一个方法getMainLooper,他可以为你提供应用主线程的looper,它的注释是这样写的:Returns the application's main looper, which lives in the main thread of the application.

3.怎么用

这里我根据初始化handler对象的不同区分了五种使用场景,后续也都会以方式一、方式二、方式三、方式四、方式五来区分。

  1. Handler发送Message
    1. 方式一  自定义一个TestHandler类继承Handler,并使用弱引用,避免内存泄漏,否则会一片黄色报警很不美观,里面写了两个构造方法分别是空参的Handle()和Handler(@NonNull Looper looper),实现主线程和子线程都能发消息,其实还有其他的构造方法我们后面分析。
               //插入方式一对应的代码

先定义TestHandler

public static class TestHandler extends Handler{private final WeakReference<Activity> weakReference;private final HandlerActivity activityWeak;//给方式一用public TestHandler(HandlerActivity activity){super();weakReference = new WeakReference<>(activity);activityWeak = (HandlerActivity) weakReference.get();activityWeak.handlerTv4.setText("已赋值");}//给方式三用public TestHandler(HandlerActivity activity,Looper looper){super(looper);weakReference = new WeakReference<>(activity);activityWeak = (HandlerActivity) weakReference.get();}@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);activityWeak.doMessage(msg);}
}

//初始化handler

testHandler = new TestHandler(this);

方式一发消息

//                Message message1 = new Message();Message message1 = Message.obtain();message1.what = WHAT1;testHandler.sendMessage(message1);Message message2 = Message.obtain();message2.what = WHAT2;message2.obj = "空消息";testHandler.sendMessage(message2);Message message3 = Message.obtain();message3.what = WHAT3;message3.obj = "延时消息";testHandler.sendMessageDelayed(message3, DELAYTIME_1600);Message message4 = Message.obtain();message4.what = WHAT4;message4.obj = "延时空消息";testHandler.sendMessageDelayed(message4, DELAYTIME_1600);Message message5 = Message.obtain();	message5.what = WHAT5;message5.obj = "延时空消息";// 这个也是延时消息,在第二个参数后发出,但他是相较于最近一次开机时间的,因此基本桑拿是秒发,而且还会插队到其他消息前面,改成90000000L就一时半会收不到了。从testHandler.sendEmptyMessageAtTime(WHAT5,DELAYTIME_1600);

方式二  

直接传入一个 CallBack,Handler(@Nullable Callback callback),CallBack可以直接new也可以改变成把CallBack单独提出来,效果一样

        testHandler2 = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message message) {doMessage(message);return false;}
});


方式三

Handler(@NonNull Looper looper),传入一个looper,这样就可以在任何线程创建Handler了,我这里用的主线程的Looper所以在子线程这样写就可以把任务发到主线程去执行了。

为了验证传入Looper真实有效特意在子线程发送的。

                        new Thread(new Runnable() {@Overridepublic void run() {//方式三TestHandler testHandler3 = new TestHandler(HandlerActivity.this, Looper.getMainLooper());Log.e(TAG, "onClick: handlerTv3    ThreadId="+ Thread.currentThread().getId() );Message message1 = Message.obtain();message1.what = WHAT1;testHandler3.sendMessage(message1);}}).start();

用主线程创建的handler,用来子线程发消息OK,正常就这么用;
用子线程创建的handler如果没有设置looper就会在收不到消息并且报一个错:This is not main thread, and the caller should invoke Looper.prepare()  and Looper.loop()called byandroid.os.Handler.<init>:122 com.example.testdemo3.activity.HandlerActivity$TestHandler.<init>:170 com.example.testdemo3.activity.HandlerActivity$4$1.run:151 java.lang.Thread.run:929 <bottom of call stack> <bottom of call stack> <bottom of call stack> <bottom of call stack>
 创建handler实例时传入主线程的Looper.getMainLooper()就能在主线程收到


方式四

传入Looper和Handler.Callback

//有系统版本限制
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {Message message = Message.obtain();message.what = 5;//方式四Handler handler = Handler.createAsync(Looper.myLooper(), new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {Log.e(TAG, "createAsync-handleMessage:  what= "+msg.what );return false;}});}


方式五  

它不是一种实例化Handler的方式,方便下文引用它因此请他入列位列末席。一种特殊的发消息方式,是post一个Runnable。它基于以上四种方式任何一个,只是改变了发送方式采用了post但后面实际也会转成sendMessagexx方法(它也有postDelayed等延时方法,使用方法类似请各位大佬自行查看源码),这是在post方法的run回调方法里接收,但没法区分是哪个

testHandler.post(new Runnable() {@Overridepublic void run() {Log.e(TAG, "run:  收到消息但不知道是什么" );}
});

小贴士:

提示一:同一个handler多次发送同一条消息

handler.sendMessage(message);
handler.sendMessage(message);    

如果同一个handler发送同一条消息连续发送两遍会引发闪退,下面的报错:
 java.lang.IllegalStateException: { when=0 what=5 target=android.os.Handler } This message is already in use.

提示二:

上面方式三在子线程发消息就是调用了Looper.getMainLooper()这里可能比较好奇,既然使用Handler必须要有一个Looper那主线程的looper是哪来的? 原来在应用刚启动时在ActivityThread的main方法中已经调用了。

prepareMainLooper方法会调用prepare()方法可以帮你实例化一个looper并存在当前线程的ThreadLocal中,并且还有贴心的存在了Looper中提供了getMainLooper供人调用,真相大白了。同样looper.prepare()也会在threadlocal存一个looper后面mylooper()方法会将其取出来使用。
提示三:

一个线程只能有一个Looper,在创建looper对象时如果超过一个就会抛异常Only one Looper may be created per thread,因此不要多次调用prepare方法创建looper对象。

为了方便阅读将文章分为《Android-Handler详解_使用篇》和《Android-Handler详解_原理解析》两篇.

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

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

相关文章

Leetcode239_滑动窗口最大值

1.leetcode原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 2.题目描述 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口…

python实战之宝塔部署flask项目

一. 项目 这个demo只是提供了简单的几个api接口, 并没有前端页面 # -*- coding: utf-8 -*- import flask as fk from flask import jsonify, requestapp fk.Flask(__name__)app.route(/api/hello, methods[GET]) def get_data():return hello world# 假设我们要提供一个获取用…

Vue3气泡卡片(Popover)

效果如下图&#xff1a;在线预览 APIs 参数说明类型默认值必传title卡片标题string | slot‘’falsecontent卡片内容string | slot‘’falsemaxWidth卡片内容最大宽度string | number‘auto’falsetrigger卡片触发方式‘hover’ | ‘click’‘hover’falseoverlayStyle卡片样式…

CSS(六)

一、精灵图 1.1 为什么需要精灵图 一个网页中往往会应用很多小的背景图像作为修饰&#xff0c;当网页中的图像过多时&#xff0c;服务器就会频繁地接收和发送请求图片&#xff0c;造成服务器请求压力过大&#xff0c;这将大大降低页面的加载速度。 因此&#xff0c;为了有效…

解决VMWare Esxi 6.5.0 导出虚拟机时发生网络错误

解决办法&#xff1a;使用vmware ovftool工具导出。 1 先安装该工具到windows下面&#xff0c;有32位的和64位的 2 用管理员进入命令方式&#xff1a; 进入&#xff1a;c:\windows 进入工具命令当前文件夹&#xff08;具体看用户的安装路径&#xff09;&#xff1a; cd \p…

vue3全局控制Element plus所有组件的文字大小

项目框架vue-右上角有控制全文的文字大小 实现&#xff1a; 只能控制element组件的文字及输入框等大小变化&#xff0c;如果是自行添加div,text, span之类的控制不了。 配置流程 APP.vue 使用element的provide&#xff0c;包含app <el-config-provider :locale"loca…

C++零基础入门学习视频课程

教程介绍 本专题主要讲解C基础入门学习&#xff0c;所以不会涉及很深入的语法和机制。但会让你整体多面的了解和学习C的核心内容&#xff0c;快速学习使用C&#xff0c;我们的目标是先宏观整体把握&#xff0c;在深入各个击破&#xff01; 学习地址 链接&#xff1a;https:/…

ThreadPool-线程池使用及原理

1. 线程池使用方式 示例代码&#xff1a; // 一池N线程 Executors.newFixedThreadPool(int) // 一个任务一个任务执行&#xff0c;一池一线程 Executors.newSingleThreadExecutorO // 线程池根据需求创建线程&#xff0c;可扩容&#xff0c;遇强则强 Executors.newCachedThre…

ssrf利用

ssrf利用 查找内网存活主机ip&#xff08;文件读取&#xff09; 使用file://协议 file:// 从文件系统中获取文件内容&#xff0c;格式为file://文件路径 file:///etc/passwd 读取文件passwd file:///etc/hosts 显示当前操作系统网卡的IP file:///…

CVPR`24 | FRESCO:高质量、连贯的Zero-shot视频转换新方案(北大南洋理工)

论文链接&#xff1a;https://arxiv.org/pdf/2403.12962.pdf 代码链接&#xff1a;https://github.com/williamyang1991/FRESCO 工程地址&#xff1a;https://www.mmlab-ntu.com/project/fresco/ 文本到图像扩散模型在图像领域的显著功效激发了人们对其在视频领域应用潜力的广…

Ansible-1

Ansible是一款自动化运维、批量管理服务器的工具&#xff0c;批量系统配置、程序部署、运行命令等功能。基于Python开发&#xff0c;基于ssh进行管理&#xff0c;不需要在被管理端安装任何软件。Ansible在管理远程主机的时候&#xff0c;只有是通过各种模块进行操作的。 需要关…

Web开发基本流程

Web是全球广域网&#xff0c;能够通过浏览器访问的网站。我们要访问网站&#xff0c;首先要在浏览器输入对应的域名。 浏览器也是一个程序&#xff0c;京东的网站也是一个程序&#xff0c;在京东那边电脑运行着&#xff0c;我们只是通过浏览器远程访问。京东的程序由三个部分组…