阅读此篇文章至少需要会使用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 部分源码,(我喜欢截图因为简单)
第一个会调用到第二个,第三个也会调用到第二个。
1,sThreadLocal这样理解,类似一个和线程有关的HasMap(当然他不是集合结构,作用类似),就是为了保证一个线程只有一个looper对象,也叫容器单例(设计模式中就看到这个词,今天我拿出来装一下,以后别人问你单例模式你可以拿出去装一下),
2,多次调用prepare 会抛一个异常,也就是为了保证一个looper双重措施吧。
3,如果looper第一次为空,就创建并且放到sThreadLocal容器当中。
1,创建一个MessageQueue对象,并且持有当前线程。
到这里应该明白了为什么一个线程只有一个Looper ,一个Looper只有一个队列。
初始化完成之后 需要开启循环了(代码太多一个图截不下,分两个图说)
1,首先校验一下,必须prepare才能调用loop();,
2,第二部拿到MessageQueue对象,
3,开启for(;;)死循环
1,拿到Message
2,messge的target的回调方法,这个方法剧透一下最终会回调到Handler内部,因为这里是Main线程的循环,所以后面的操作都是在Main线程中顺序执行
第二部分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 的时候防止内存泄漏,这里不多做介绍 。