参考 “酱爆大头菜”:Handler就好比你开了个餐馆 - 掘金

CSDN

handler一

本文就以生活点餐的例子再结合源码原理进行解析。希望对你有一点帮助。 来,咱们进入角色。

Handler,Looper,MessageQueue,Message的全程协作的关系就好比一个餐厅的整体运作关系

  • Handler好比点餐员
  • Looper好比后厨厨师长。
  • MessageQueue好比订单打单机。
  • Message好比一桌一桌的订单。

接下来我们回顾下我们餐厅点餐的场景,餐厅点餐分为标准点餐和特殊点餐,我们分解来看。

标准流程

  1. 首先进入一家店,通过点餐员点餐把数据提交到后厨打单机。
  2. 然后厨师长一张一张的拿起订单,按照点餐的先后顺序,交代后厨的厨师开始制作。
  3. 制作好后上菜,并标记已做好的订单。

特殊流程

  1. 订单为延迟订单,比如客人要求30分钟后人齐了再制作,这时会把该订单按时间排序放到订单队列的合适位置,并通过SystemClock.uptimeMillis()定好闹铃。至于为什么用uptimeMillis是因为该时间是系统启动开始计算的毫秒时间,不受手动调控时间的影响。
  2. 如果打单机中全是延迟订单,则下令给后厨厨师休息,并在门口贴上免打扰的牌子(needWake),等待闹铃提醒,如有新的即时订单进来并且发现有免打扰的牌子,则通过nativeWake()唤醒厨师再开始制作上菜。
  3. 但是为了提升店铺菜品覆盖,很多相邻的店铺都选择合作经营,就是你可以混搭旁边店的餐到本店吃,此时只需点餐员提交旁边店的订单即可,这样旁边店的厨师长就可以通过打单机取出订单并进行制作和上菜。

总结

一家店可以有多个点餐员,但是厨师长只能有一个。打单机也只能有一个。

映射到以上场景中

  • 一家店就好比一个Thread
  • 而一个Thread中可以有多个Handler(点餐员)
  • 但是一家店只能有一个Looper(厨师长),一个MessageQueue(打单机),和多个Message(订单)。

根据以上的例子我们类比看下源码,充分研究下整个机制的流程,和实现原理。

Looper的工作流程

ActivityThread.main();//初始化入口1. Looper.prepareMainLooper(); //初始化Looper.prepare(false); //设置不可关闭Looper.sThreadLocal.set(new Looper(quitAllowed)); //跟线程绑定1.1.Looper.mQueue = new MessageQueue(quitAllowed); //Looper和MessageQueue绑定1.2.Looper.mThread = Thread.currentThread();2. Looper.loop();2.1.myLooper().mQueue.next(); //循环获取MessageQueue中的消息nativePollOnce(); //阻塞队列native -> pollInner() //底层阻塞实现native -> epoll_wait();2.2.Handler.dispatchMessage(msg);//消息分发

myLooper().mQueue.next()实现原理

  1. 通过myLooper().mQueue.next() 循环获取MessageQueue中的消息,如遇到同步屏障 则优先处理异步消息.
  2. 同步屏障即为用Message.postSyncBarrier()发送的消息,该消息的target没有绑定Handler。在Hnandler中异步消息优先级高于同步消息。
  3. 可通过创建new Handler(true)发送异步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保证UI绘制优先执行。

Handler.dispatchMessage(msg)实现原理

  1. 优先回调msg.callback。
  2. 其次回调handler构造函数中的callback。
  3. 最后回调handler handleMessage()。

Hander发送消息的流程

1.Handler handler = new Handler();//初始化Handler1.Handler.mLooper = Looper.myLooper();//获取当前线程Looper。2.Handler.mQueue = mLooper.mQueue;//获取Looper绑定的MessageQueue对象。2.handler.post(Runnable);//发送消息sendMessageDelayed(Message msg, long delayMillis);sendMessageAtTime(Message msg, long uptimeMillis);Handler.enqueueMessage();//Message.target 赋值为this。Handler.mQueue.enqueueMessage();//添加消息到MessageQueue。

MessageQueue.enqueueMessage()方法实现原理

  1. 如果消息队列被放弃,则抛出异常。
  2. 如果当前插入消息是即时消息,则将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,并通过needWake唤醒Looper线程。
  3. 如果消息为异步消息则通过Message.when长短插入到队列对应位置,不唤醒Looper线程。

经常有人问为什么主线程的Looper阻塞不会导致ANR?

  1. 首先我们得知道ANR是主线程5秒内没有响应。
  2. 什么叫5秒没有响应呢?Android系统中所有的操作均通过Handler添加事件到事件队列,Looper循环去队列去取事件进行执行。如果主线程事件反馈超过了5秒则提示ANR
  3. 如果没有事件进来,基于Linux pipe/epoll机制会阻塞loop方法中的queue.next()中的nativePollOnce()不会报ANR。
  4. 对于以上的例子来说,ANR可以理解为用户进行即时点餐后没按时上菜(当然未按时上菜的原因很多,可能做的慢(耗时操作IO等),也可能厨具被占用(死锁),还有可能厨师不够多(CPU性能差)等等。。。),顾客发起了投诉,或差评。但如果约定时间还没到,或者当前没人点餐,是不会有差评或投诉产生的,因此也不会产生ANR。

以上的所有内容均围绕原理,源码,接下来我们举几个特殊场景的例子

1.为什么子线程不能直接new Handler()

       new Thread(new Runnable() {@Overridepublic void run() {Handler handler = new Handler();}}).start();以上代码会报以下下错误java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()at android.os.Handler.<init>(Handler.java:207)at android.os.Handler.<init>(Handler.java:119)at com.example.test.MainActivity$2.run(MainActivity.java:21)at java.lang.Thread.run(Thread.java:919)
  • 通过报错提示 “not called Looper.prepare()” 可以看出提示没有调用, Looper.prepare(),至于为什么我们还得看下源码
 public Handler(Callback callback, boolean async) {...省略若干代码//通过 Looper.myLooper()获取了mLooper 对象,如果mLooper ==null则抛异常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;}public static @Nullable Looper myLooper() {//而myLooper()是通过sThreadLocal.get()获取的,那sThreadLocal又是个什么鬼?return sThreadLocal.get();}//可以看到sThreadLocal 是一个ThreadLocal<Looper>对象,那ThreadLocal值从哪赋值的?static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();//sThreadLocal 的值就是在这个方法里赋值的private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}//具体的赋值点sThreadLocal.set(new Looper(quitAllowed));}
  • 通过以上的源码注释,完全明白了报错的日志的意思,报错日志提示我们没有调用Looper.prepare()方法,而Looper.prepare()方法就是sThreadLocal赋值的位置。

子线程怎么创建Handler呢?只需在new Handler()之前调用下Looper.prepare()即可。


2. 为什么主线程可以直接new Handler

  • 子线程直接new Handler会报错,主线程为什么就不会报错呢?主线程我也没有调用Looper.prepare()啊?那么我们还得看下源码了。
    //我们看下ActivityMain的入口main方法,调用了 Looper.prepareMainLooper();public static void main(String[] args) {...Looper.prepareMainLooper();...}//看到这一下就明白了,原来主线程在启动的时候默认就调用了prepareMainLooper(),而在这个方法中调用了prepare()。  //提前将sThreadLocal 进行赋值了。public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}
  • 如果有对 ThreadLocal对象不明白的可看我这篇 ThreadLocal原理解析

3.Handler为什么会内存泄露?

  • 首先普及下什么叫内存泄露,当一个对象不再使用本该被回收时,但另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这种情况下就产生了内存泄漏。
  • 我们举一个Handler内存泄露的场景。
public class HandlerActivity extends AppCompatActivity {private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler);handler.sendEmptyMessageDelayed(1,5000);}
}
  • 当以上代码写完后编译器立马会报黄并提示 “this handler should be static or leaks might occur...Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.”

大致意思就说 “由于这个处理程序被声明为一个内部类,它可以防止外部类被垃圾回收。如果处理程序正在对主线程以外的线程使用Looper或MessageQueue,则不存在问题。如果处理程序正在使用主线程的Looper或MessageQueue,则需要修复处理程序声明,如下所示:将处理程序声明为静态类;并且通过WeakReference引用外部类(弱引用)”。

  • 说了这么一大堆,简单意思就是说以上这种写法,默认会引用HandlerActivity,当HandlerActivity被finish的时候,可能Handler还在执行不能会回收,同时由于Handler隐式引用了HandlerActivity,导致了HandlerActivity也不能被回收,所以内存泄露了

我们来写一种正确的写法

public class HandlerActivity extends AppCompatActivity {MyHandler handler = new MyHandler(this);@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler);handler.sendEmptyMessageDelayed(1,5000);}private static class MyHandler extends Handler{private WeakReference<HandlerActivity> activityWeakReference;public MyHandler(HandlerActivity activity) {//弱引用activityWeakReference = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);}}
}
  • 以上写法使用了静态内部类+弱引用的方式,其实如果在handleMessage()方法中无需访问HandlerActivity 的成员则无需使用弱引用,只需静态内部类即可,弱引用只是方便调用HandlerActivity 内部成员。
  • 非静态内部类和非静态匿名内部类中确实都持有外部类的引用, 静态内部类中未持有外部类的引用,不影响后续的回收,因此没有内存泄露。

来个美丽的分割线 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 以下为自己的理解 

handler二

  精通Android下的Hendler机制,并能熟练使用

Message:消息;其中包含了消息ID,消息对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理

Handler:处理者;负责Message发送消息及处理。Handler通过与Looper进行沟通,从而使用Handler时,需要实现handlerMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等(主线程中才行)

MessageQueue:消息队列;用来存放Handler发送过来的消息,并按照FIFO(先入先出队列)规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等Looper的抽取。

Looper:消息泵,不断从MessageQueue中抽取Message执行。因此,一个线程中的MessageQueue需要一个Looper进行管理。Looper是当前线程创建的时候产生的(UI Thread即主线程是系统帮忙创建的Looper,而如果在子线程中,需要手动在创建线程后立即创建Looper[调用Looper.prepare()方法])。也就是说,会在当前线程上绑定一个Looper对象。

Thread:线程;负责调度消息循环,即消息循环的执行场所。

知识要点 

一、说明

  1. handler应该由处理消息的线程创建。
  2. handler与创建它的线程相关联,而且也只与创建它的线程相关联。handler运行在创建它的线程中,所以,如果在handler中进行耗时的操作,会阻塞创建它的线程。

二、一些知识点

1、Android的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有个Looper。主线程(UI线程)就是一个消息循环的线程。2、获取looper:

Looper.myLooper();      //获得当前的Looper

Looper.getMainLooper () //获得UI线程的Lopper

3、Handle的初始化函数(构造函数),如果没有参数,那么他就默认使用的是当前的Looper,如果有Looper参数,就是用对应的线程的Looper。4、如果一个线程中调用Looper.prepare(),那么系统就会自动的为该线程建立一个消息队列,然后调用 Looper.loop();之后就进入了消息循环,这个之后就可以发消息、取消息、和处理消息。

消息处理机制原理:

一、大致流程:

在创建Activity之前,当系统启动的时候,先加载ActivityThread这个类,在这个类中的main函数,调用了Looper.prepareMainLooper();方法进行初始化Looper对象;然后创建了主线程的handler对象(Tips:加载ActivityThread的时候,其内部的Handler对象[静态的]还未创建);随后才创建了ActivityThread对象;最后调用了Looper.loop();方法,不断的进行轮询消息队列的消息。也就是说,在ActivityThread和Activity创建之前(同样也是Handler创建之前,当然handler由于这两者初始化),就已经开启了Looper的loop()方法,不断的进行轮询消息。需要注意的是,这个轮询的方法是阻塞式的,没有消息就一直等待(实际是等着MessageQueue的next()方法返回消息)。在应用一执行的时候,就已经开启了Looper,并初始化了Handler对象。此时,系统的某些组件或者其他的一些活动等发送了系统级别的消息,这个时候主线程中的Looper就可以进行轮询消息,并调用msg.target.dispatchMessage(msg)(msg.target即为handler)进行分发消息,并通过handler的handleMessage方法进行处理;所以会优于我们自己创建的handler中的消息而处理系统消息。

0、准备数据和对象:

①、如果在主线程中处理message(即创建handler对象),那么如上所述,系统的Looper已经准备好了(当然,MessageQueue也初始化了),且其轮询方法loop已经开启。【系统的Handler准备好了,是用于处理系统的消息】。【Tips:如果是子线程中创建handler,就需要显式的调用Looper的方法prepare()和loop(),初始化Looper和开启轮询器】

②、通过Message.obtain()准备消息数据(实际是从消息池中取出的消息)

③、创建Handler对象,在其构造函数中,获取到Looper对象、MessageQueue对象(从Looper中获取的),并将handler作为message的标签设置到msg.target上

1、发送消息:sendMessage():通过Handler将消息发送给消息队列

2、给Message贴上handler的标签:在发送消息的时候,为handler发送的message贴上当前handler的标签

3、开启HandlerThread线程,执行run方法。

4、在HandlerThread类的run方法中开启轮询器进行轮询:调用Looper.loop()方法进行轮询消息队列的消息

【Tips:这两步需要再斟酌,个人认为这个类是自己手动创建的一个线程类,Looper的开启在上面已经详细说明了,这里是说自己手动创建线程(HandlerThread)的时候,才会在这个线程中进行Looper的轮询的】

5、在消息队列MessageQueue中enqueueMessage(Message msg, long when)方法里,对消息进行入列,即依据传入的时间进行消息入列(排队)

6、轮询消息:与此同时,Looper在不断的轮询消息队列

7、在Looper.loop()方法中,获取到MessageQueue对象后,从中取出消息(Message msg = queue.next())

8、分发消息:从消息队列中取出消息后,调用msg.target.dispatchMessage(msg);进行分发消息

9、将处理好的消息分发给指定的handler处理,即调用了handler的dispatchMessage(msg)方法进行分发消息。

10、在创建handler时,复写的handleMessage方法中进行消息的处理

11、回收消息:在消息使用完毕后,在Looper.loop()方法中调用msg.recycle(),将消息进行回收,即将消息的所有字段恢复为初始状态

Handler的理解相关推荐

  1. android handler的理解

    android handler的理解 在看handler源码前,我一直以为google构造handler的目的是方便开发者在其他线程中 调用执行主线程的方法或者在主线程中调用执行其他线程的方法.看完源 ...

  2. 自己写个C++版本Handler来理解Android的Handler机制

    由于日常工作不需要经常写android上层应用,对Android的Handler机制一直处于模模糊糊的状态.使用Handler之后,回去写c++代码时,时刻怀念Android里面的Handler,希望 ...

  3. intent和handler的理解和使用

    intent和handler的理解和使用 一.当前的对intent和handler的理解 1.intent是作为一个参数传递机制存在的. (1)它可以向activity传递参数以及Object.(Ob ...

  4. netty中handler的理解

    前言 在写netty服务端代码时,我们一般会定义handler如下: 或者定义成这样 看到这你能说出两者的区别吗?还是没有区别? 正题 我们先看下initChannel的调用时机: 当客户端建立连接时 ...

  5. handle和handler的理解

    维基百科对handler的解释: Handler, an asynchronous callback (computer programming) subroutine in computing .. ...

  6. Handler自我理解

    一Handler 1.什么是Handler          Handler可以用来在线程之间进行通信, Android中的界面组件只能在创建界面的线程中修改, 如果主线程创建界面, 那么新线程就无法 ...

  7. 正确理解 AsyncTask,Looper,Handler三者之间的关系(基于android 4.0)

    Looper 和Handler 是理解好AsyncTask的一个基础,我们可以先从这里开始,先给出一个主线程和子线程互相通信的例子. 1 package com.example.loopertest; ...

  8. 五分钟深入理解Handler

    Handler大家最熟悉不过,每次重新回顾Handler都有不同的感觉,学到新的东西.现分享一些自己对Handler的理解. Handler发送消息 首先Handler通过sendMessage方法将 ...

  9. 安卓开发之Handler、HandlerThread学习篇

    安卓开发之Handler.HandlerThread学习心得篇           开篇说明:本文采用的都是最基础最简单的例子,目的只有一个:希望大家将学习的焦点放在Handler的理解和使用上,我不 ...

最新文章

  1. python基于tpot训练模型在获得最佳模型之后对模型进行交叉验证分析并可视化实战
  2. NoSQL架构实践(二)——以NoSQL为主
  3. nodejs下载安装教程(XP版)
  4. 强化学习note2——value iteration和policy iteration的区别,MC和TD的区别
  5. 处理训练集和测试集分布同的方法(对抗训练)
  6. pytorch 之 冻结某层参数,即训练时不更新
  7. windows:(1)xmind常用快捷键
  8. 借助钉钉宜搭,奶茶店开始用黑科技管理门店了
  9. 导师推荐 | 第 5 期临床基因组家系分析,同时解决科研和临床问题
  10. mysql buffer 命中率_从MySQL的源码剖析Innodb buffer的命中率计算
  11. 使用Docker搭建LAMP环境,上线wordpress
  12. 存储过程系列三:根据表别名方式distinct去重插入
  13. carmaker/matlab联合仿真(二) 新建测试场景
  14. SAP针对中国市场推出双轨制医疗计划
  15. 表白代码大全,快来向你的ta表白吧~~~
  16. 端午节放假安排出来啦,收藏这些端午节海报为端午节活动做准备吧
  17. Linux 上的NetworkManager示例
  18. 用python做股票因子分析_因子分析(by+alphalens)
  19. 温度及pH敏感性聚乙烯醇/羧甲基壳聚糖水凝胶/金银花多糖/薄荷多糖/O-羧甲基壳聚糖水凝胶
  20. 学会这几个.你就能成为bat脚本小子了...(转来看看的)

热门文章

  1. 漫画统计学(统计基础+SPSS)
  2. delphi 人脸比对_OpenCV学习笔记3:找出人脸,同时比较两张图片中的人脸相似度 | 学步园...
  3. 整顿职场,从 ROC 曲线开始
  4. 小程序怎么搭建?学会这些技巧,开启创业之路
  5. linux rzsz(lrzsz)安装
  6. ProxyCap +ccproxy 组合使用Socks5 实现代理服务器
  7. C语言代码程序运行不出
  8. STM32CubeMX学习笔记(20)——DAC接口使用(输出正弦波)
  9. ioctl _IO,_IOW,_IOWR
  10. 直播视频app源码,自定义可点击可滑动的通用RatingBar