0.前言
Android开发一定离不开和Handler的打交道,Handler通常是作为主线程和子线程之间的通讯工具,而Handler作为Android中消息机制的重要成员,确实给我们的开发带来了很大的便利。
Handler被广泛使用。可以说,每当有异步线程与主线程通信时,就会有一个Handler。
所以要理解Android,就要理解Handler。
那么,Handler的通信机制背后的原理是什么呢?
这篇文章将向你展示。
注意:本文展示的系统源代码是基于Android-27的,已经删减。
1.重新识别处理器
我们可以使用处理程序来发送和处理与线程相关联的消息和可运行的。(注意:可运行的将被封装在一个消息中,所以本质上它仍然是一个消息)
每个处理程序都绑定到一个线程,并与该线程的消息队列相关联,从而实现消息管理和线程间通信。
1.1 handler 1 Android . OS . handler handler = new handler()的基本用法
2 @覆盖
3公共消息处理消息(最终消息消息){
4//在此接收和处理消息
5}
6};
7//发送消息
8handler.sendMessage(消息);
9 handler . post(runnable);
实例化一个Handler来覆盖handleMessage方法,然后在需要的时候调用它的send和post系列方法,非常简单易用,支持延迟消息。(API文档的查询方式更多)
但奇怪的是,我们没有看到任何消息队列,也没有看到它与线程绑定的逻辑。怎么回事?
2.汉德勒原理分析
相信大家都已经听说过Looper和MessageQueue了,就不拐弯抹角了。
但是在我们开始分析原理之前,让我们先澄清一下我们的问题:
Handler 是如何与线程关联的?Handler 发出去的消息是谁管理的?消息又是怎么回到 handleMessage() 方法的?线程的切换是怎么回事?2.1 Handler 与 Looper 的关联实际上,当我们实例化处理程序时,处理程序将检查当前线程的循环程序是否存在,如果不存在,将报告一个异常,这意味着循环程序必须在处理程序创建之前创建。
代码如下:
1publicHandler(回调回调,booleanasync){
2//检查当前线程是否有活套
3 looper = looper . my looper();
4if(mLooper == null) {
5thrownewRuntimeException(
6“无法在未调用Looper.prepare()”的线程内创建处理程序”;
7}
8//Looper保存一个消息队列
9 mqueue = mloop er . mqueue;
10}
相信很多同学都遇到过这个异常,但是我们直接使用的时候感觉不到这个异常,因为主线程已经为我们创建了一个Looper。先记住,以后再说。(见[3.2])
一个完整的处理程序使用示例实际上是这样的:
1claslooperthreadextendsthread {
2publicHandler mHandler
3publicvoidrun(){
4 loop er . prepare();
5mHandler = newHandler() {
6公共无效处理消息(消息消息){
7//在此处理传入消息
8}
9};
10 loop er . loop();
11}
12}
Looper.prepare():
1//Looper
2 privatestatictvoidprepare(BooleanQualized){
3if(sThreadLocal.get()!= null) {
4thrownewRuntimeException("每个线程只能创建一个循环程序");
5}
6sthreadlocal . set(NewLooper(QuItallowed));
7}
Looper提供Looper.prepare()方法创建Looper,并通过ThreadLocal实现与当前线程的绑定功能。Looper.loop()将开始尝试从消息队列中获取消息,并将其分发到相应的处理程序(参见[2.3])。
也就是说,Handler和线程的关联是通过Looper实现的。
2.2信息的存储和管理
Handler为我们提供了一些发送消息的列方法,比如send () series post () series。
然而,不管我们调用什么方法,我们最终都会用到message.enqueuemessage (message,long)方法。
以sentemptymessage(int)方法为例:
1//处理器
2 sendmessage(int)
3->;sentemptymessagedelayed(int,int)
4->;发送消息时间(消息,长)
5->;排队消息(消息队列,消息,长)
6->;queue.enqueueMessage消息(消息,长);
在这里,消息管理器MessageQueue暴露在水中。
MessageQueue是一个队列,负责消息的进出。
2.3消息的分发和处理
在清楚了解消息的发送和存储管理之后,是时候揭开分发和处理的面纱了。
如前所述,Looper.loop()负责分发消息,本章将对其进行分析。
让我们先来看看所涉及的方法:
1//Looper
2publicstaticvoidloop(){
3 finalooper me = my looper();
4if(me == null) {
5thrownewRuntimeException("无活套;Looper.prepare()未在此线程上调用。);
6}
7 FinalMessagequeue queue = me . mqueue;
8// ...
9 for(;;) {
10//继续从消息队列获取消息
11 message msg = queue . next();//可能会阻止
12//退出Looper
13if(msg == null) {
14//无消息表示消息队列正在退出。
15返回;
16}
17// ...
18天{
19 msg . target . dispatchmessage(msg);
20 end =(slowdispatchethresholdms = = 0)?0:SystemClock . Uptime millis();
21}最后{
22// ...
23}
24// ...
25//回收信息,参见[3.5]
26 msg . recycle Unchecked();
27}
28}
在循环()中调用了MessageQueue.next():
1//消息队列
2下一条消息(){
3// ...
4 for(;;) {
5// ...
6 native epollonce(ptr,next polltimeoutmillis);
七
8同步(此){
9//尝试检索下一条消息。找到了就退货。
10 finalongnow = SystemClock . Uptime millis();
11Message prevMsg = null
12消息消息=消息;
13// ...
14if(msg!= null) {
15if(现在& ltmsg.when) {
16//下一条消息未准备好。设置一个超时时间,当它准备好的时候唤醒。
17 NeXtploltimeoutmillis =(int)Math . min(msg . when-now,Integer。MAX _ VALUE);
18} else{
19//收到消息。
20mBlocked = false
21if(prevMsg!= null) {
22prevmsg . next = msg . next;
23}其他{
24条消息= msg.next
25}
26msg.next = null
27returnmsg
28}
29}其他{
30//没消息了。
31 extpollytimeoutmillis =-1;
32}
33
34//处理退出消息,因为所有未决消息都已处理完毕。
35if(mMounting){
36 dispose();
37returnnull
38}
39}
40
41//运行空闲处理程序。自己了解空闲处理程序
42// ...
43}
44}
它还调用msg.target.dispatchMessage(msg),这是发送消息的处理程序,因此它回调处理程序:
1//处理器
2publicvoiddispatchMessage(消息消息){
3//msg.callback是Runnable。如果是post方法,它将采用这个if
4if(msg.callback!= null) {
5 handlecallback(msg);
6}其他{
7//回调见[3.4]
8if(mCallback!= null) {
9 if(McAllBack . Handlemessage(msg)){
10返回;
11}
12}
13//回调处理程序的handleMessage方法
14 handlemessage(msg);
15}
16}
注意:dispatchMessage()方法对Runnable方法做了特殊处理。如果是,将直接执行Runnable.run()。
分析:Looper.loop()是一个无限循环,它会调用MessageQueue.next()来获取消息,并调用msg . target . dispatchmail(msg)回到Handler来分发消息,从而完成消息的回调。
注意:loop()方法不会阻塞主线程,参见[6]。
那么线程切换呢?
这个道理很多人不懂,但其实很简单。我们绘制涉及的方法调用堆栈,如下所示:
1 read . foo(){
2Looper.loop()
3->;MessageQueue.next()
4->;Message.target.dispatchMessage()
5->;Handler.handleMessage()
6}
显然,Handler.handleMessage()的线程最终是由调用Looper.loop()的线程决定的。
通常,当我们使用它时,我们从异步线程向处理程序发送消息。处理程序的handleMessage()方法在主线程中调用,因此消息从异步线程切换到主线程。
2.3图解原理
正文版的原理分析到此结束。如果你在这里还是不明白,那也没关系。我特意为你准备了一些图片。有了前面的章节,多看几遍,就能彻底理解了。
handler-looper-mq.jpg
handler_java.jpg
图片来源见[6] 2.4汇总
处理程序由Looper和MessageQueue支持,两者协同工作,分工明确。
试着把他们的职责总结如下:
Looper :负责关联线程以及消息的分发,会与创建它的线程绑定,并负责在该线程下从 MessageQueue 获取 Message,分发给 Handler ;MessageQueue :是个队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message ;Handler : 负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。用一句话概括第[2]章中提出的问题:
处理程序发送的消息由消息队列存储管理,循环程序负责将消息回调到处理程序消息()。
线程转换由Looper完成,handleMessage()的线程由Looper.loop()调用方的线程决定。
3.处理器的扩展
Handler简单易用,但还是要注意用好。另外还有一些与Handler相关的鲜为人知的知识和技能,比如IdleHandler。
Handler因其特性在Android中被广泛使用,如AsyncTask、HandlerThread、Messenger、IdleHandler、IntentService等。
这些我会解释一些,但是我可以自己找出我没有提到的。
3.1处理器导致内存泄漏的原因及最佳解决方案
处理程序允许我们发送延迟消息。如果用户在延迟期间关闭活动,活动将会泄露。
泄露是因为Message会持有Handler,而由于Java的特性,内部类会持有外部类,这样Activity就会被Handler持有,最终导致Activity的泄露。
解决这个问题最有效的方法是将Handler定义为一个静态的内部类,在内部持有Activity的弱引用,并及时移除所有消息。
示例代码如下:
1 privatestaticclassafehendrextendshandler {
2
3私人参考<。HandlerActivity>。ref
四
5公共安全处理程序(句柄活动){
6 this . ref = NewWeakReference(activity);
7}
八
9 @覆盖
10发布消息处理消息(最终消息消息){
11 handleractivity activity = ref . get();
12if(活动!= null) {
13 activity . handlemessage(msg);
14}
15}
16}
然后删除Activity.onDestroy()前的消息,增加一层保障:
1 @覆盖
2protectedvoidonDestroy(){
3 safe handler . removecallbacksandmessages(null);
4 super . ondestroy();
5}
这种双重保证可以完全避免内存泄漏。
注意:简单地删除onDestroy中的消息是不安全的,因为onDestroy不一定会执行。
3.2为什么不创建Looper就可以在主线程中直接使用Handler?
我们前面提到每个Handler线程都有一个Looper,主线程也不例外,但是我们从来没有准备过主线程的Looper,可以直接使用。为什么?
注意:一般我们认为ActivityThread是主线。其实不是线程,而是主线程操作的管理器。所以,我觉得认为ActivityThread是主线,主线也可以说是UI线程是可以理解的。
ActivityThread.main()方法中有以下代码:
1//android.app.ActivityThread
2 publicstativoidmain(String[]args){
3// ...
4 loop er . PrepareMainLooper();
五
6 activitythread = new activitythread();
7 thread . attach(false);
八
9if(sMainThreadHandler == null) {
10sMainThreadHandler = thread . gethandler();
11}
12// ...
13 looper . loop();
14
15thrownewRuntimeException("主线程循环意外退出");
16}
looper . prepareinlooper();代码如下:
1/**
2*将当前线程初始化为循环线程,将其标记为
3*应用程序的主弯针。您应用的主要活套
4*是Android环境创建的,应该永远不需要
5*自己调用这个函数。另请参阅:{@link#prepare()}
6*/
7 publicationstationprepareinlooper(){
8 prepare(false);
9同步(Looper.class) {
10if(sMainLooper!= null) {
11行新非法状态异常(“主弯针已经准备好了。”);
12}
13 mainlooper = my looper();
14}
15}
可以看到在ActivityThread中调用Looper.prepareMainLooper()方法创建主线程的Looper,调用loop()方法,可以直接使用Handler。
注意:Looper.loop()是一个无限循环,下面的代码不会正常执行。
3.3主线活套不允许退出
如果您尝试退出Looper,您将收到以下错误消息:
1原因:Java . lang . IllegalStateException:主线程不允许退出。
2 at Android . OS . message queue . quit(message queue . Java:415)
3 at Android . OS . Looper . quit(Looper . Java:240)
为什么?其实原因很简单。主线程不允许退出,说明APP应该挂机了。
3.4隐藏在处理程序中的回调可以做什么?
Handler的构造函数中调用Callback有几个要求,那么它是什么,能做什么?
查看处理程序. dispatchMessage(消息)方法:
1publicvoiddispatchMessage(消息消息){
2//这里的回调是可运行的
3if(msg.callback!= null) {
4 handlecallback(msg);
5}其他{
6//如果回调处理消息并返回真,句柄消息将不会被再次回调
7if(mCallback!= null) {
8 if(McAllBack . Handlemessage(msg)){
9返回;
10}
11}
12 handlemessage(msg);
13}
14}
可以看出汉德勒。回调有权先处理消息。当回调处理并截获一条消息时(返回真),将不会调用处理程序的handleMessage方法。如果回调处理消息但不拦截它,这意味着回调和处理程序可以同时处理消息。
这个很有意思。它是做什么的?
我们可以用回调作为拦截机制来拦截Handler的消息!
场景:Hook activity thread . mH activity thread中有一个成员变量MH,是一个Handler,也是一个极其重要的类。几乎所有的插件框架都使用这种方法。
3.5创建消息实例的最佳方式
Handler很常见,所以Android为了节省开销,为Message设计了一个回收机制,所以我们在使用Message的时候尽量重用它,以减少内存消耗。
有两种方法:
通过 Message 的静态方法 Message.obtain(); 获取;通过 Handler 的公有方法 handler.obtainMessage(); 。3.6 子线程里弹 Toast 的正确姿势当我们尝试在子线程中直接播放Toast时,我们会崩溃:
1java.lang.RuntimeException:无法在未调用Looper.prepare()的线程内创建处理程序
2
本质上,Toast的实现依赖于Handler,Handler可以根据使用Handler的子线程的要求进行修改(参见[2.1]),Dialog也是如此。
正确的示例代码如下:
1新线程(newRunnable() {
2 @覆盖
3publicvoidrun(){
4 loop er . prepare();
5片吐司。maketext (handleractivity。这个,“它不会崩溃的!”,吐司。LENGTH_SHORT)。show();
6 loop er . loop();
7}
8});
3.7使用活套机构
我们可以使用Looper的机制来帮助我们做一些事情:
将 Runnable post 到主线程执行;利用 Looper 判断当前线程是否是主线程。完整的示例代码如下:
1publicfinalclassMainThread {
2
3privateMainThread(){
4}
五
6 PrivateStaticFinalHandler = NewHandler(Looper . GetMainLooper());
七
8 publicationstaticvoidrun(@ NonNull Runnable Runnable){
9 if(IsMaintRead()){
10 runnable . run();
11} else{
12 HANDLER . post(runnable);
13}
14}
15
16 publicationstatibooleanishinthread(){
17 returnlooper . my looper()= = looper . getmainlooper();
18}
19
20}
可以节省大量样例代码。
4.知识点总结
从上面可以得到一些知识点,可以总结一下,便于记忆。
Handler 的背后有 Looper、MessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理;在创建 Handler 之前一定需要先创建 Looper;Looper 有退出的功能,但是主线程的 Looper 不允许退出;异步线程的 Looper 需要自己调用 Looper.myLooper().quit(); 退出;Runnable 被封装进了 Message,可以说是一个特殊的 Message;Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,也可以说成 Looper 所在的线程,并不是创建 Handler 的线程;使用内部类的方式使用 Handler 可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类;5. 总结Handler好用,对工程师隐藏了很多智慧,应该向他们学习。
看完理解这篇文章,可以说你对Handler的理解非常深刻全面,处理好面试肯定绰绰有余。
6.参考和建议
处理者
android中的looper-handler-message-queue之间的关系是什么
Android消息机制1-Handler(Java层)
为什么主线程不会卡在安卓里
其实有很多东西想和大家分享。比如我还欠一个大厂的经验,不过放心我记得。
写一篇我写了很久的文章,精心挑选知识点,逐一突破,通俗易懂。
我相信你已经看到了我的意图,所以这次我给你一个赞赏。如果你觉得对你有帮助,就用行动来支持。
1.《handlemessage Handler都没搞懂,拿什么去跳槽啊?!》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《handlemessage Handler都没搞懂,拿什么去跳槽啊?!》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/junshi/1209103.html