30分钟带你搞懂Handler

Handler是什么?

对于Handler 我们都不太陌生 这是我们Android的消息传输机制 我们都知道它是用来帮我们通过子线程发送消息到主线程 供我们主线程进行更新UI操作的 那他为什么可以完成线程间的通信呢?

Handler 包含了什么?

Handler更新UI界面的机制,也是消息处理的机制,可以发送消息,也可以处理消息
Message 接收处理消息对象
Looper 每个线程只有1个:可读取MessageQueue消息队列,管理此线程里的MessageQueue(消息队列)遵循先进先出读到消息后从消息队列取出交给handler
Message Queue(消息队列):用来存放线程放入的消息

Handler消息机制是什么?

Handler 机制中包含了几个关键的对象,Looper,MessageQueue,Message。在使用 Handler 的时候,在 handler 所创建的线程需要维护一个唯一的 Looper 对象,每个线程对应一个 Looper, 每个线程的 Looper 通过 ThreadLocal 来保证。Looper 对象的内部又维护有唯一的一个MessageQueue 链表,所以一个线程可以有多个 Handler,但是只能有一个 Looper 和一个MessageQueue。Message 在 MessageQueue 不是通过一个列表来存储的,而是将传入的Message 存入到了上一个 Message 的 next 中,在取出的时候通过顶部的 Message 按放入的顺序依次取出 Message。Looper 对象通过 loop()方法开启了一个死循环,不断地从 looper 内的 MessageQueue 中取出 Message,然后通过 handler 将消息分发传回 handler 所在的线程

如果对Handler有了解的朋友会觉得却是就是挺简单的 但是对于初学者来说 这TM是啥都是些什么玩意 哈哈哈 不要着急 我们今天通过源码来给大家讲解一下 关于Handler机制的执行流程

我们先来通过一张流程图来看一下我们是怎么把消息从子线程发送到主线程的:


image.png

Handler源码

首先我们都知道Activity创建以后会首先走到我们ActivityTrhead 的main 函数中(也就是我们的main方法)

ActivityThread.png

这里我们发现 咦 怎么我们main方法中还会帮我们创建Looper的? 当你对java够熟悉的时候 大家就可以进入源码中了解一下 我们就会发现 其实我们对代码的理解过于偏浅 咳咳 跑题了 我们首先去我们的第一步Looper.prepareMainLooper()这个方法中看一看:


image.png

我们发现其实就是调用了Looper中的一个方法 进入方法第一步就执行了一个 本类中的prepare()方法 这个方法是做什么的呢? 我们点进去继续看:


image.png

这里我们发现它给我们做了一个判断 sTreadLocal.get() 获取到的这个对象是否不为空 不为空就给我们抛出异常 否则就帮我们set()一个。 我们这个时候就想问了 这个sTreadLocal是个什么东西?为什么要通过他来获取Looper呢?

image.png

我们发现这个sThreadLocal 就是线程内部的数据存储类的对象 ,通过他可以在指定的线程中存储数据,该数据只有在指定线程中可以获取
我们看一下这个ThreadLocal给我们提供的set() get()方法:

set()方法:


image.png

这里我们发现一个ThreadLocalMap 一个map集合 我们发现这一步吧我们的当前线程和我们当前线程的Looper对象 通过 key value 的形式 绑定到了一个ThreadLocalMap 对象中
我们看一下Looper的构造方法中都创建了什么:

image.png

我们发现它帮我们创建了一个MessageQueue();并把我们的的当前线程对象赋值给了mThread 这里特别重要 我们的Looper只能有一个MessageQueue 而且是在创建Looper时创建了 也就是同步创建的。

有了赋值set() 我们再来看看取值get()方法

get()方法 :


image.png

image.png

image.png

get()方法中代码很多 但是原理很简单 就是有就使用 没有就赋值嘛
我们只需要知道我们通过这个get()方法返回的是我们当前线程的Looper对象就可以啦

弄懂这两个方法以后我们就知道了我们prepare这个方法中都了什么 :判断我们Looper是否存在 不存在的话就帮我们创建 存在就从ThreadLocalMap中取出当前线程对应的Looper对象

我们继续往下看:
prepareMainLooper这个方法:


image.png

我们这里的sMainLooper就是一个默认为空的Loopr对象


image.png

我们看到如果他存在的话 就抛出异常 不存在的话就调用本类的myLooper方法
这个myLooper都干了什么呢?我们点进去看一下 :


image.png

这个地方就通过我们ThreadLocal的get()方法取出我们当前线程的Looper对象

到这里我们的 Looper.prepareMainLooper(); 创建主线程Looper对象就完成了

我们接下来往下看


image.png

我们发现我们这里又调用了Looper.loop() 这个方法, 这个方法就是我们轮询消息的方法


image.png

又是一段长代码 是不是看的让人头大 不要慌 看源码就是很繁琐的 我们要有耐心
我们首先看到 调用了myLooper()这个方法 这个方法是做什么呢?

image.png

我们发现它和我们ThreadLocal.get()这个方法功能是一样的 都是获取到我们保存的Looper对象
我们接下来看它又做了哪些事呢?
image.png

这里把我们Looper对象的MessageQueue取出来
image.png

通过死循环 不断的调用MessageQueue 消息队列 对象的next()方法 获取到Message 直到我们的消息队列中没有消息 返回 这个next()方法里面是怎么把消息给我们取出呢?
这个方法中代码太多 我就把核心给取出展示:
image.png

image.png

这里就是定义一个空的Message 然后把我们的消息队列中的消息 进行取出 把每一个值都赋值给我们空的Message 然后把我们赋值过后的Message返回

如果我们的消息队列中有消息的话 就会一直调用这个next方法进行取值 直到消息队列中的消息为空 next就会堵塞线程 否则loop循环就会无限循环下去 当我们获取到消息以后我们用做了什么事情呢?

image.png

我们发现我们调用了一个msg.target.dispatchMessage()方法 首先我们要知道msg.target是什么


image.png

原来他是一个Handler 的对象 我们接下来再看dispatchMessage这个是干了什么:


image.png

我*** 有没有感觉到最下面那一行代码特别熟悉:
这就是我们创建Handler的时候


image.png

这个handleMessage方法吗 原来就是这个地方把我们的msg传递给我们的Handler了啊 是不是觉得其实也不是那么的困难 看完了以上的代码 我们接下来再来看我们的Handler 哈哈哈哈哈 这里才到我们的Handler 是不是觉得已经很无聊了 马上步入正题:

我们先看一下我们创建Handler的时候都帮我们干了什么:


image.png

继续往里看:


image.png

我们发现它调用了myLooper()这个方法 这个发法我们之前已经看到过了:
是用来获取我们当前线程的Looper对象的 接下来又把我们当前线程Looper对象中的MessageQueue绑定到我们的Handler中的MessageQueue 。接下来就来看我们的发送消息事件吧
image.png

我们今天通过一个简单的发送消息来进行讲解:
我们点进去sendMessage方法发现:


image.png

这里发送了一条默认为0的延迟消息 这个方法又做了什么呢:

image.png

又指定时间发送了一条消息-->

image.png

这个方法定义了一个空的MessageQueue绑定我们Handler的MessageQueue 又往下走enqueueMessage()方法:

image.png

我们发现这里把我们存储到msg.target的这个Handler赋值给了我们当前的Handler 接下来调用了MessageQueue的enqueueMessage() :


image.png

首先对我们的 msg.target 也就是 Hander进行了一次判空 如果为空就给我们抛出异常

         Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

首先判断我们消息队列中是否有消息 如果没有就直接放到第一个 如果有消息的话就放到他的后面

这个时候我们的消息就完全的存放到了我们MessageQueue中 我们的的ActivityThread 的main 方法中的Looper.loop 就会把我们消息队列中的消息轮询取出 给我们handler使用了

还有一点就是我们Handler的内存泄漏问题:

在使用 Handler 的过程中,需要注意内存泄漏,因为 handler 是用来进行线程间通信的,所
以新开启的线程会持有 Handler 引用的,如果 Activity 中创建 Handler,并且是非静态内部类
的形式,就有可能造成内存泄漏。非静态的内部类是会隐式持有外部类的引用,所以当其他
线程持有了 handler,线程没有被销毁,就意味着 Activity 会一致被 Handler 持有引用而无法导
致回收。同时 MessageQueue 中如果存在未处理完的 Message,Message 的 target 也是对 Acivity
的持有引用,也会造成内存泄漏。
解决的方法,可以使用静态的内部类+弱引用的方式。在外部类对象被销毁时,将
MessageQueue 中 的 消 息 清 空 。 如 Activity 的 onDestroy 时 将 消 息 情 况 , 调 用
handler.removeCallbacksAndMessages(null); 清空消息

以上就是我总结的Handler啦 有没有学到什么呢~
如果您发现了哪些错误可以联系我哦~

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

推荐阅读更多精彩内容