美文网首页
Android Handler机制不一样角度去理解

Android Handler机制不一样角度去理解

作者: 杨旭_ | 来源:发表于2019-01-15 15:53 被阅读85次

    阅读此篇文章至少需要会使用Handler

    按照惯例首先说两个知识点

    1,Android 中只有主线程才能更新UI(这是一个规定,主要是为了保证UI绘制的流畅,防止并发出问题)

    2,Android 中主线程不允许阻塞超过5s,否则可能会ANR

    这两个常识的问题,接下来叙述咱们的主题,

    很常见的一个需求,去请求一个网络,回来展示数据。

    1,全靠主线程必然不行了,主线程不能阻塞等待网络请求回来。

    2,全靠子线程也不行,子线程不允许更新UI(为什么不允许更新UI,去了解一下View的绘制机制就明白了,等我写完了会添上跳转)

    本文的重点来了 :一句话概括handler机制的核心就是线程之间通信。

    Q,问题一,如果让你设计一个线程之间通信机制,你怎么做?

    先喝杯阔乐好好想一想。

    不想工作,想喝阔乐

    一杯可乐下肚,顿时灵光乍现。

    首先利用面向对象这个概念,我们创建一个对象obj,然后在子线程中给obj的一个属性赋值,然后在Main线程中,在操作obj对象,就可以拿到从子线程中赋值的结果了,完成一次线程通信。

    Q 问题二来了,Main线程什么时候去操作obj对象?

    第一,我不知道其他线程什么时候给他赋值。

    所以我这里就开启一个死循环,一直去看我的对象的属性中是否有值,有的话就进行操作就行了

    Q问题三,死循环不会导致OOM吗,不会ANR吗?

    正常死循环必然不行,Android中有一种循环机制,阻塞式死循环,简单介绍一下,有消息就处理,没有消息就会休眠,有消息就会唤醒继续处理消息。

    这样就是一套完整的线程通信机制了,其实也就是Handler通信机制,下面带入源码 再说一遍,能懂多少是多少,不懂多看几遍就懂了。

    上源码之前,先简单说一下比较重要的几个类和其中的作用。

    Handler  对应我们前边说的obj对象,他持有的属性不是单一的一个bean对象而是一个MessageQueue对象,从名字就能看出来是一个队列

    messageQueue 对应上边obj对象的属性,但是他是一个队列

    Looper  用来开启阻塞式死循环,遍历的是MessageQueue对象。

    message 具体我们要通信的消息实体的封装

    不懂没关系,往下看,然后再回来看

    1,handler 负责把Message 放到MessageQueue当中(在子线程当中)

    2,Looper 开启无限循环把MessageQueue当中的Message,拿出来进行分发。(这个操作在我们的Main线程)

    需要注意的地方 一个线程只能对应一个Looper,每一个Looper只能对应一个MessageQueue,为什么要这样呢?

    一个线程如果有八个循环,八个队列,

    第一 性能方面说,一个循环一个队列就能够完成的,弄这么多没必要,家里有矿也不建议。

    第二 线程顺序执行 ,程序直接卡在第一个循环这里,后面的七个循环根本执行不到,因为第一个循环没有消息就休眠,不会往下执行。

    第三,多个队列,先遍历那个,总不能按心情来是吧,得有个顺序,所以八个队列等于一个队列。

    文章过半,总结一下

    1,开启循环:每个线程只有一个Looper,用来阻塞式循环,每个Looper对应一个MessgeQueue;无限的循环遍历MessageQueue,如果里边有消息就去处理消息,消息处理完继续循环,这样就一直循环下去,也是我们程序为什么不会退出的原因。

    2,发送消息: handler创建的时候会根据线程去绑定,拿到对应线程的队列looper和MessageQueue,发送消息的过程就是在其他线程把Message放到MessageQueue当中

    3,回调消息: handler发送消息的时候会对Message消息打上tag,当looper遍历到Message对象,这个时候已经到了主线程,Message.tag就拿到了handler对象,然后回调对应的方法handler.handleMessage()。

    要阅读什么机制,首先了解他的设计原理和流程,在看源码就是很简单了,源码就是把设计原理和流程转换成了机器能看懂的语言而已。

    分为两部分,

    第一循环部分Looper 部分源码,(我喜欢截图因为简单)

    looper启动循环的几种方法

    第一个会调用到第二个,第三个也会调用到第二个。

    1,sThreadLocal这样理解,类似一个和线程有关的HasMap(当然他不是集合结构,作用类似),就是为了保证一个线程只有一个looper对象,也叫容器单例(设计模式中就看到这个词,今天我拿出来装一下,以后别人问你单例模式你可以拿出去装一下),

    2,多次调用prepare 会抛一个异常,也就是为了保证一个looper双重措施吧。

    3,如果looper第一次为空,就创建并且放到sThreadLocal容器当中。

    looper的构造函数

    1,创建一个MessageQueue对象,并且持有当前线程。

    到这里应该明白了为什么一个线程只有一个Looper ,一个Looper只有一个队列。

    初始化完成之后 需要开启循环了(代码太多一个图截不下,分两个图说)

    开启循环图1

    1,首先校验一下,必须prepare才能调用loop();,

    2,第二部拿到MessageQueue对象,

    3,开启for(;;)死循环

    loop循环图2

    1,拿到Message

    2,messge的target的回调方法,这个方法剧透一下最终会回调到Handler内部,因为这里是Main线程的循环,所以后面的操作都是在Main线程中顺序执行

    第二部分Handler部分源码

    先看一下构造函数

    handler最终构造

    无论那种构造方法,最终都会调用到这个

    1,前边说过容器单例,根据线程去拿looper对象,然后校验,这个异常相信很多人遇到过,必须首先Looper的Prepare之后才能创建handler。

    2,把当前线程的Looper和MessageQueue都复制给handler对象,handler以后所有消息都会放到这个队列当中,所以在这里赋值。

    3,赋值回调,(如果不设置回调,默认回调HandleMessage,设置回调就会走我们的回调,下边会看到)

    接下来看发送消息

    无论调用post还是send 几个参数的最终都会执行这里

    1,taget的赋值,(打上当前handler 标记)

    2,添加到队列

    发送的流程就结束了,这样就把一个Message从一个线程发送到另一个线程的队列当中了,然后等待looper去循环就会切换到对应的线程执行 dispatchMessage方法了,(当然我们大多数默认的都是主线程。)

    消息分发方法

    这个方法都是在looper的循环中调用,这个时候已经切换了线程,然后进行回调的分发,最后handleMessage()优先级最低;

    还有队列的源码就不上了,我更看重流程,队列也无非就是上一个对象持有下一个对象,这样一直连接下去,更像一个链表。

    总结一下

    1,looper的创建跟当前线程绑定,一个线程只有一个looper,一个looper只有一个MessageQueue,需要调用prepare方法初始化,(主线程默认开启了,子线程需要手动调用,)然后调用Looper.loop()开启循环。

    2,handler 创建会和当前的线程绑定,拿到当前线程的Looper和MessageQueue对象,如果Looper为空就会抛异常

    3,handler send 和post一系列方法目的就是把Message放到创建Handler线程的队列当中。

    4,looper的循环中会把handler发送的message分发回来,但是这里已经切换了线程。

    最后一张图结合上下文再看一下。

    ​​​

    拓展一下

    Q上次我去面试,问我子线程怎么开启线程通信,如果不使用了需要关闭吗?

    A,在子线程looper.Prepare() 方法之后就创建了looper和MessageQueue ,然后looper.loop()开启我们的循环,这个时候我们在使用handler,才能达到线程通信的效果。如果不需要使用了一定要关闭,(凡是消耗性能的东西确定不需要使用了就关闭)Looper.quit()可以退出looper;

    还有使用handler 的时候防止内存泄漏,这里不多做介绍 。

    相关文章

      网友评论

          本文标题:Android Handler机制不一样角度去理解

          本文链接:https://www.haomeiwen.com/subject/dtbedqtx.html