handler原理

应用启动流程:launchAPP zygote fork一个进程,启动虚拟机,启动ActviityThread,执行main方法,
调用Looper.prepareMainLooper(); Looper.loop,开启循环
开启循环是开启了一个死循环,死循环中,会不停的拿消息出来,Looper.mQueue.next(),
然后得到msg,而后通过msg.target获取到这个msg对应的handler,调用handler的dispatchMessage。对应代码 msg.target.dispatchMessage(msg);
有一个判断 当这个msg为空时,退出循环。

我们平时使用handler,开始在handler.sendMessage(),结束在handler的handlerMessage,这中间发生了什么事呢,追源码看一下
handler发送消息有很多方法,但是最终都是调用 了enqueueMessage(queue, msg, uptimeMillis);该方法第一行 msg.target = this;将message与发送的handler绑定到一起。
而后调用了queue.enqueueMessage(msg, uptimeMillis),这个queue其实就是Messagequeue,往下看Messagequeue的enqueueMessage干了什么,源码如下


Snipaste_2022-09-21_22-03-21.png

从这里可以看出,enqueueMessage为msg入队的操作,并且msg的消息队列实际上是一个链表,每个msg的next都指向了下一个msg,这里实际为一个优先级队列。

取消息对应的为next()函数,代码如下:
Snipaste_2022-09-21_22-09-29.png

这个next的调用方为looper。调用Looper.loop(),其中又调用了MessageQueue的next()。返回一个msg,而后调用msg的handler.dispatchMessage(msg),最终调用了我们常用的handler的handlerMessage;
这其中有一个比较核心的点,我们一般使用handler都是从主线程或者子线程发送,最后接收的地方都为主线程。从内存上来说,message本身为一块内存,而内存的使用对象可以是任意线程,无所谓在哪里发送消息,想一想,我们平时使用时实现handleMessage方法是不是都在主线程?自然而然就在主线程接收了,同理,如果在子线程接收了,那就会在子线程接收到消息。

MessageQueue

再来说一下MessageQueue,MessageQueue为单链表实现的优先级队列。
单链表:因为msg只有一个next,所以是单向的。
为什么是优先级队列:是有先后顺序的。我们调用sendMessageAtTIme的时候需要传入一个时间。代码如下:


Snipaste_2022-09-21_22-35-16.png

可以看到,for循环里面对比了传入的msg跟当前轮询的msg的执行时间,如果当前msg的时间小于轮询的msg,那就直接插入队列,将传入的msg的next指向当前轮询的msg。
为什么是队列?因为MessageQueue的next()每次取都是从队头取,也就是所谓的先进先出原则。

Looper

Looper的核心在他的构造函数上,以及loop(),ThreadLocal
Looper的构造函数为private,调用prepare()初始化。在此方法中new对象,这是为了方便管理,防止外部随便创建对象。
prepareMainLooper方法中,首先调用了prepare方法,调用了sThreadLocal的set方法。而sThreadLocal只有一个static final的对象,所以一个线程中只有一个Looper。
怎样保证唯一的?sThreadLocal是一个static final的对象,set方法之前会先get,如果不为空则抛出异常,set中,先根据当前所在的线程拿到线程持有的map,然后将ThreadLocal对象为key,Looper为value放入。从而保障唯一。源码如下:


Snipaste_2022-09-22_22-48-40.png

MessageQueue是怎么创建的:实在Looper对象创建的时候创建的。既然Looper既然是唯一的,那MessageQueue也是唯一的。源码如下:


Snipaste_2022-09-22_22-46-24.png

MessageQueue是属于哪个线程的?MessageQueue是一个容器,是一块内存。内存不能说是属于哪个线程的,所以这个问题本身是错误的。只有方法函数可以说是哪个线程的。

一个线程有几个handler?

无数个,你可以随便new

一个线程能有几个Looper?1个,原因如上。

handler内存泄漏原因?

因为匿名内部类的创建方式,会持有外部类的引用,但是在Activity销毁的时候,Handler如果没有手动销毁,那就会内存泄漏。举个例子:如果msg是一个20分钟后执行的任务,msg的target指向了handler,handler所处的Activity销毁之后,但是由于GC不能回收msg,所以不能回收handler。也不能回收handler所持有的Activity。总结一下为:内部类对象的生命周期大于了持有的外部类对象的生命周期,导致泄漏。

为什么主线程可以直接new Handler,子线程为什么不行。

其实核心不在于handler,核心在于Looper,因为主线程启动的时候默认创建了,而子线程是没有的,所以需要手动创建,也就是调用Looper.prepare(), Looper.loop;

那当子线程没有消息的时候怎么办?没有消息的时候,就调用了Looper的quit(),looper的loop死循环只有一种情况会退出,那就是msg为空的时候,什么时候为空呢,当Looper调用quit的时候,会反回一个空的消息。

handler一整套运作机制是一个生产者消费者的设计模式,但是有区别的点在于,生产着消费者的仓库是有限制大小的,而messagqueue是没有大小限制的,所以当消息过多的时候执行速度跟不上会卡顿,并且消息过多,会导致内存溢出。

有两种情况会堵塞、等待:

第一种,当取出的消息还没到执行的时刻,就会等待,比如:handler post延迟10s任务。
源码里面又一层判断,如果msg的执行时间大于当前时间,那会计算出需要延迟多少秒,而后进行下次循环,首先会判断 延迟的时间是否为0,不为0,则会调用nativePollOnce方法进行睡眠等待。
第二种:消息队列为空的时候,会一直等待直到下次事件,nativeWake唤醒

既然可以有多个handler能往队列里放消息,那是如何保证线程安全的?

用锁。synchronized 内置锁。在enqueueMessage(),next(),quite()方法中都加了 对象锁,也就是MessageQueue的对象,并且每个线程只有一个对象。所以当取消息或者放消息,在一个线程中同时只能有一个在操作。

主线程是否能quite

不行。主线程调用会抛异常。

msg的复用,为什么要复用?

复用是为了防止,msg频繁的创建回收出现内存抖动。如果不复用,容易产生内存碎片,而如果此时有对象需要申请内存,内存碎片的大小小于申请的内存大小,就会出现OOM,比如 内存长度为10,在4跟9的位置内存被使用,此时有一个对象需要申请一个长度为5的内存,那此时内存就不够用,会oom。
使用Message.obtain() 使用了享元设计模式,实现内存复用。

Looper死循环为什么不会导致应用卡死?

Looper的死循环跟应用卡不卡是没有关系的。平时应用中的点击事件或者广播之类的东西都是最后包装程msg然后触发的,当一个msg的执行时间大于5s就会被系统判定为anr,然后应用结束。而looper的死循环是一直在处理消息,当消息为空时,会挂起 释放cpu资源,但是如果有事件来到,则会被唤醒继续处理任务。

同步屏障

普通的msg是属于异步任务,需要入队列等待轮训。而有一些任务是需要立马执行,叫做同步任务。比如我们的点击事件,屏幕刷新事件。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 首先,我将Handler相关的原理机制形象的描述为以下情景: Handler:快递员(属于某个快递公司的职员) M...
    夏天之灵阅读 215评论 0 0
  • Android Handler 的原理分析 Handler 是安卓中最常用的组件。作用就是 线程间的消息通知 但...
    萌萌的白天阅读 453评论 0 0
  • 1. 介绍 做Android开发一定会用到Handler,通常是用来将一些子线程中得到的结果post到主线线程,即...
    小山豆几阅读 1,262评论 0 0
  • Handler 原理 一、Handler消息发送机制 1. 发送消息 1.1 添加消息 调用Handler.sen...
    还是昵称啊阅读 191评论 0 0
  • Handler的原理分析这个标题,很多文章都写过,最近认真将源码逐行一字一句研究,特此也简单总结一遍。 首先是Ha...
    却把清梅嗅阅读 762评论 1 6