阅读此篇文章至少需要会使用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 的时候防止内存泄漏,这里不多做介绍 。


更多相关文章

  1. Android开发——实现子线程更新UI
  2. android 面试 android 知识点 提高篇
  3. Handler初探
  4. Android并发编程1-----多线程
  5. Android(安卓)深入讲解Handler机制
  6. Android同步类:Mutex和Condition
  7. 面试时总被面试官挖的Android基础题掉坑里?整理了26道面试题 ,牢固
  8. Android线程池-学习总结
  9. Android多线程的实现方式及使用场景

随机推荐

  1. Android之SQLite 学习一
  2. Android进阶(二十二)设置TextView文字水
  3. Android 安全加密:对称加密详解
  4. 【Android Basics】Android是什么? (What
  5. Android手机上关于viewport的理解
  6. Android 漫游之路------Android电话拨号
  7. Android中minSdkVersion、targetSdkVersi
  8. Android窗体悬浮在另一个窗体
  9. android 设置app版本号
  10. Android 聊天软件实现