最近找工作,复习了下java相关的知识。发现已经对很多概念模糊了。记录一下。部分是往年面试题重新整理,部分是自己面试遇到的问题。持续更新中~
封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。
| 数据类型 | 位数 | 默认值 | 取值范围 | 示例 |
|---|---|---|---|---|
| byte | 1字节 | 0 | -2(128~127) | byte a = 10; |
| short | 2字节 | 0 | -2(-32768~32767) | short a = 10; |
| int | 4字节 | 0 | (-2147483648~2147483647) | int a = 10; |
| float | 4字节 | 0.0 | -2(-2147483648~2147483647) | float a = 10; |
| long | 8字节 | 0 | -2(-9223372036854774808~9223372036854774807) | long a = 10; |
| double | 8字节 | 0.0 | -2(-9223372036854774808~9223372036854774807) | char a = 10; |
| char | 2字节 | 空 | -2(128~127) | char a = ‘a’; |
| boolean | 1字节 | false | true、false | booleana = true; |
| 特殊类型void |
对应的包装类
byte——Byte
short——Short
int ——Integer
float——Float
long——Long
double——Double
char——Character
boolean——Boolean
void——Void
包装类出现的原因是Java语言是面对对象的编程语言,而基本数据类型声明的变量并不是对象,为其提供包装类,增强了Java面向对象的性质。
void是一个特殊的类型,有人把它归到Java基本数据类型中,是因为可以通过Class.getPrimitiveClass(“void”)获取对应的原生类型。
void有个对应的类型Void,可以把它看做是void的包装类,Void的作用主要作用有以下2个:
Method[] methods = String.class.getMethods();
for (Method method : methods) {
if(method.getGenericReturnType().equals(Void.TYPE)){
System.out.println(method.getName());
}
}
//输出:
//getBytes
//getChars
//wait
//wait
//wait
//notify
//notifyAll
将基本数据类型转化为包装类就叫做装箱;
调用 包装类.valueOf()方法
int a = 22;
//装箱 在实例化时候进行装箱
Integer inter1 = new Integer(a);
//装箱 调用valueOf方法进行装箱
Integer inter2 = Integer.valueOf(a);
valueOf 方法是一个静态方法,直接通过类进行调用
拆箱
将包装类转化为基本数据类型;
调用 包装类.parseXXX()方法
int a = Integer.parseInt("3");
String不是基本类型,不可以被继承。因为String被final关键字修饰,不可以被继承
public final class String implements Serializable, Comparable<String>, CharSequence {
String 大小固定数不可变的。
因为String是字符串常量,是不可变的。实际的拼接操作最后都是产生了一个新的对象并存储这个结果
String str1 = "123";
String str2 = "123"//实际上 "123"这个字符串在常量池中只有一份,str1,str2 两个对象都是指向"123"
str2 = str2 + "45";
实际上是产生了个新的String对象存放"12345"这个结果
查看字节码实际代码是
0 ldc #2 <123>
2 astore_1
3 new #3 <java/lang/StringBuilder>
6 dup
7 invokespecial #4 <java/lang/StringBuilder.<init> : ()V>
10 aload_1
11 invokevirtual #5 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
14 ldc #6 <45>
16 invokevirtual #5 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
19 invokevirtual #7 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
22 astore_1
23 return
//实际也是通过 StringBuilder.append方法实现拼接,然后toString的性能比较低
//所以字符串拼接直接使用StringBuilder实现效率更高
StringBuffer 大小可变,线程安全(有锁),同步,效率低,适用于多线程,低并发。
StringBuilder 大小可变,线程不安全(无锁),不同步,效率高,适用于单线程,高并发。
对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择
HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。
jdk1.7以前使用数组+链表实现HashMap,jdk1.8以后用数组+红黑树实现。当链表长度较短时使用链表,长度达到阈值时自动转换为红黑树,提高查询效率。
如果类中没有重写equals方法,比较地址值是否相等(是否指向同一个地址值)。
Student stu1 = new Student(11, "张三");
Student stu2 = new Student(11,"张三");
System.out.println(stu1.equals(stu2));//false
既然equals比较的是内容是否相同,为什么结果还是false呢?
回顾知识:
在Java中我们知道任何类的超类都是Object类,Student类也继承Object类。
查看Object类中的equals方法也是 == 比较(也就是比较地址值),因此结果当然是false。
public boolean equals(Object obj) {
return (this == obj);
}
既然这样我们如何保证两个对象内容相同呢?
这里就需要我们去重写equals方法?
@Override
public boolean equals(Object obj){
if (this == obj){
return true;
}
if (obj instanceof Student) {
Student stu = (Student)obj;
return this.age == stu.age && this.name.equals(stu.name);
}
return false;
}
jdk 1.7以前结构是segment数组 + HashEntry数组 + 链表,使用分段式锁,实现线程安全。容器中有多把锁,每一把锁锁一段数据,这样在多线程访问时不同段的数据时,就不会存在锁竞争了,这 样便可以有效地提高并发效率。这就是ConcurrentHashMap所采用的”分段锁”思想,见下图:

get()操作:
HashEntry中的value属性和next指针是用volatile修饰的,保证了可见性,所以每次获取的都是最新值,get过程不需要加锁。
1.将key传入get方法中,先根据key的hashcode的值找到对应的segment段。
2.再根据segment中的get方法再次hash,找到HashEntry数组中的位置。
3.最后在链表中根据hash值和equals方法进行查找。
ConcurrentHashMap的get操作跟HashMap类似,只是ConcurrentHashMap第一次需要经过一次hash定位到Segment的位置,然后再hash定位到指定的HashEntry,遍历该HashEntry下的链表进行对比,成功就返回,不成功就返回null。
put()操作:
1.将key传入put方法中,先根据key的hashcode的值找到对应的segment段
2.再根据segment中的put方法,加锁lock()。
3.再次hash确定存放的hashEntry数组中的位置
4.在链表中根据hash值和equals方法进行比较,如果相同就直接覆盖,如果不同就插入在链表中。
jdk1.8以后结构是 数组+Node+红黑树实现,采用**Synchronized + CAS(自旋锁)**保证线程安全。Node的val和next都用volatile保证,保证可见性,查找,替换,赋值操作都使用CAS
为什么在有Synchronized 的情况下还要使用CAS
因为CAS是乐观锁,在一些场景中(并发不激烈的情况下)它比Synchronized和ReentrentLock的效率要高,当CAS保障不了线程安全的情况下(扩容或者hash冲突的情况下)转成Synchronized
来保证线程安全,大大提高了低并发下的性能.
锁 :
锁是锁的链表的head的节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作(因为扩容的时候使用的是Synchronized锁,锁全表),并发扩容.
读操作无锁 :
Node的val和next使用volatile修饰,读写线程对该变量互相可见
数组用volatile修饰,保证扩容时被读线程感知
get()操作:
get操作全程无锁。get操作可以无锁是由于Node元素的val和指针next是用volatile修饰的。
在多线程环境下线程A修改节点的val或者新增节点的时候是对线程B可见的。
1.计算hash值,定位到Node数组中的位置
2.如果该位置为null,则直接返回null
3.如果该位置不为null,再判断该节点是红黑树节点还是链表节点
如果是红黑树节点,使用红黑树的查找方式来进行查找
如果是链表节点,遍历链表进行查找
put()操作:
1.先判断Node数组有没有初始化,如果没有初始化先初始化initTable();
2.根据key的进行hash操作,找到Node数组中的位置,如果不存在hash冲突,即该位置是null,直接用CAS插入
3.如果存在hash冲突,就先对链表的头节点或者红黑树的头节点加synchronized锁
4.如果是链表,就遍历链表,如果key相同就执行覆盖操作,如果不同就将元素插入到链表的尾部, 并且在链表长度大于8, Node数组的长度超过64时,会将链表的转化为红黑树。
5.如果是红黑树,就按照红黑树的结构进行插入。
线程的状态:

线程池创建有七种方式,最核心的是最后一种:
临时线程数 = 最大线程数 - 核心线程数
ThreadPoolExecutor 也提供了 4 种默认的拒绝策略:


synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。
主要区别如下:
Example example = new Example();
// 1.
Example example = Example.class.newInstance();
// 2.Constructor.newInstance
// 本方法和Class类的newInstance方法很像,但是比它强大很多。 java.lang.relect.Constructor类里也有一个newInstance方法可以
//创建对象。我们可以通过这个newInstance方法调用有参数(不再必须是无参)的和私有的构造函数(不再必须是public)。
Constructor<?>[] declaredConstructors = test.class.getDeclaredConstructors();
Constructor<?> noArgsConstructor = declaredConstructors[0];
noArgsConstructor.setAccessible(true); // 非public的构造必须设置true才能用于创建实例
Object test = noArgsConstructor.newInstance();
public class Test {
String b = "123";
@Override
public Test clone() throws CloneNotSupportedException {
return (Test) super.clone();
}
public Test() {
Log.d("TAGGG", "print: init ");
}
}
public class Main {
public static void main(String[] args) throws Exception {
Test test= new Test();
Object clone = Test.clone();
System.out.println(test);
System.out.println(clone);
System.out.println(test == clone); //false
}
}
public class Main {
public static void main(String[] args) throws Exception {
Test test= new Test();
byte[] bytes = SerializationUtils.serialize(test);
// 字节数组:可以来自网络、可以来自文件(本处直接本地模拟)
Object deserTest = SerializationUtils.deserialize(bytes);
System.out.println(test);
System.out.println(deserTest);
System.out.println(test == deserTest);
}
}
分为三个部分

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。
类加载流程
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载,验证,准备,解析,初始化,使用,卸载这7个阶段,其中其中验证、准备、解析3个部分统称为连接。JVM没有规定类加载的时机,但却严格规定了五种情况下必须立即对类进行初始化,加载自然要在此之前。
1、加载
在加载的过程中,虚拟机会完成以下三件事情:
2、验证
这一步的目的是为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。具体验证的东西如下:
3、准备
为类变量(static修饰的变量)分配内存并且设置该类变量的默认初始值,即零值,初始化阶段才会设置代码中的初始值
这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示初始化
这里不会为实例变量分配初始化,类变量会分配在方法区,而实例变量是会随着对象一起分配给Java堆中。
4、解析
解析阶段是虚拟机将常量池内的符号引用(类、变量、方法等的描述符 [名称])替换为直接引用(直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄 [地址])的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
5、初始化
初始化阶段编译器会将类文件声明的静态赋值变量和静态代码块合并生成方法并进行调用。
类加载器分类
JVM层面支持两种类加载器:启动类加载器和自定义类加载器,启动类加载器由C++编写,属于虚拟机自身的一部分;继承自java.lang.ClassLoader的类加载器都属于自定义类加载器,由Java编写。逻辑上我们可以根据各加载器的不同功能继续划分为:扩展类加载器、应用程序类加载器和自定义类加载器。
类加载器写协作方式
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成Class对象,当触发类加载时,JVM并不知道当前类具体由哪个加载器加载,都是先给到默认类加载器(应用程序类加载器),默认类加载器怎么分配到具体的加载器呢,这边使用了一种叫双亲委派模型的加载机制。
1、双亲委派模型
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。举例如下:
2、全盘负责
当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
3、缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。
按照是否线程私有可分为
1.方法区

方法区,也称非堆(Non-Heap),是一个被线程共享的内存区域。其中主要存储类的类型信息,方法信息,域信息,JIT代码缓存,运行时常量池等。
2. 堆 Java堆是Java虚拟机所管理的内存最大的一块区域,Java堆是线程共享的,在虚拟机启动时创建。
几乎所有的对象实例都在这里分配内存。
字符串常量池(String Table),静态变量也在这里分配内存。
Java堆是垃圾收集器管理的内存区域,有些资料称为GC堆,当对象不再使用了,被当做垃圾回收掉后,这些为对象分配的内存又重新回到堆内存中。
Java堆在逻辑上应该认为是连续的,但是在具体的物理实现上,可以是不连续的。
Java堆可以是固定大小的,也可以是可扩展的。现在主流Java虚拟机都是可扩展的。
-Xmx 最大堆内存
-Xms 最小堆内存
如果Java堆没有足够的内存给分配实例,并且也无法继续扩展,则抛出 OutOfMemoryError 异常。

3.Java虚拟机栈 Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用,是线程私有的,因此也是线程安全的。


4.程序计数器 程序计数器的英文全称是Program Counter Register,又叫程序计数寄存器。Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。JVM中的PC寄存器是对 物理PC寄存器的一种抽象模拟。

5.本地方法栈 本地方法栈与虚拟机栈所发挥的作用是非常相似的。只不过虚拟机栈为虚拟机执行的Java方法(即字节码)服务,本地方法栈为虚拟机执行的本地方法(Native方法、C/C++ 实现)服务。
负责执行class文件中包含的字节码指令;

JVM的主要任务之一是负责装载字节码到其内部(运行时数据区),但字节码并不能够直接运行在操作系统之上,因为字节码指令并非等价于本地机器指令,它内部包含的仅仅只 是一些能够被 JVM 所识别的字节码指令、符号表,以及其他辅助信息。
那么,如果想要让一个 Java 程序运行起来,执行引擎(Execution Engine) 的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。简单来说,JVM 中的执行引擎充当了将高级语言翻译为机器语言的译者。
1. 解释器
当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令然后执行。
JVM 设计者们的初衷仅仅只是单纯地为了满足 Java 程序实现跨平台特性,因此避免采用静态编译的方式由高级语言直接生成本地机器指令,从而诞生了实现解释器在运行时采用逐行解释字节码执行程序的想法。
解释器真正意义上所承担的角色就是一个运行时“翻译者”,将字节码文件中的内容“翻译”为对应平台的本地机器指令执行,执行效率低。
在Java的发展历史里,一共有两套解释执行器,即古老的字节码解释器、现在普遍使用的模板解释器。字节码解释器在执行时通过纯软件代码模拟字节码的执行,效率非常低效。而模板解释器将每一条字节码和一个模板函数性关联,模板函数中直接产生这条字节码执行时的机器码,从而很大程度上提高了解释器的性能。
在Hotspot VM中,解释器主要由Interpreter模块和Code模块构成。
由于解释器在设计和实现上非常简单,因此除了 Java 语言之外,还有许多高级语言同样也是基于解释器执行的,比如:Python、Perl、Ruby等。但就是因为多了中间这一“翻译”过程,导致代码执行效率低下。
为了解决这个问题,JVM平台支持一种叫做即时编译的的技术。即时编译的目的是为了避免函数被解释执行,而是将整个函数编译成机器码,每次函数执行时,只执行编译后的机器码即可,这种方式可以使执行效率大幅度提升。
2. 即时(JIT)编译器
就是虚拟机将Java字节码一次性整体编译成和本地机器平台相关的机器语言,但并不是马上执行。JIT 编译器将字节码翻译成本地机器指令后,就可以做一个缓存操作,存储在方法区 的 JIT 代码缓存中。JVM真正执行程序时将直接从缓存中获取本地指令去执行,省去了解释器的工作,提高了执行效率高。
HotSpot VM 是目前市面上高性能虚拟机的代表作之一。它采用解释器与及时编辑器并行的结构。在 Java 虚拟机运行时,解释器和即时编译器能够相互协作,各自取长补短,尽力去选择最合适的方式来权衡编译本地代码的时间和直接解释执行代码的时间。
JIT 编译器执行效率高为什么还需要解释器?
一个被多次调用的方法,或者是一个方法体内部循环次数较多的循环体,都可以被称为热点代码。因此都可以通过JIT编译器编译成本地机器指令。由于这种编译方式发生在方法执行的过程中,因此也被称为栈上替换,或者简称为OSR编译。
一个方法究竟要被调用多少次,或者一个循环体究竟需要执行多少次循环才可以达到这个标准,必然需要一个明确的阈值。JIT编译器才会将这些热点代码编译成本地机器码执行。
GC是JVM中的垃圾回收机制。主要作用于Java堆区,用于将不再使用的对象回收,释放内存。简单的说垃圾就是内存中不再使用的对象,所谓使用中的对象(已引用对象),指的是程序中有指针指向的对象;而不再使用的对象(未引用对象),则没有被任何指针指向。如果这些不再使用的对象不被清除掉,我们内存里面的对象会越来越多,而可使用的内存空间会越来越少,最后导致无空间可用。
垃圾回收的基本步骤分两步:
1.对象存活判断 即内存中不再使用的对象,判断对象存活一般有两种方式:引用计数算法和可达性分析法
1. 引用计数算法 给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器+1,当引用失效时,计数器-1,任何时候当计数器为0的时候,该对象不再被引用。
2. 可达性分析算法 可达性分析算法是目前主流的虚拟机都采用的算法,程序把所有的引用关系看作一张图,从所有的GC Roots节点开始,寻找该节点所引用的节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。

在Java语言中,可作为GC Roots的对象包括下面几种:
- 虚拟机栈中引用的对象(局部变量);
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中JNI(Native方法)引用的对象
- 所有被同步锁持有的对象;
- 虚拟机的内部引用如类加载器、异常管理对象;
- 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
2.垃圾回收算法
标记-清除算法 标记-清除算法的基本思想就跟它的名字一样,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
标记阶段 标记的过程其实就是前面介绍的可达性分析算法的过程,遍历所有的 GC Roots 对象,对从 GCRoots 对象可达的对象都打上一个标识,一般是在对象的 header 中,将其记录为可达对象;
清除阶段清除的过程是对堆内存进行遍历,如果发现某个对象没有被标记为可达对象(通过读取对象header 信息),则将其回收。

标记-清除算法缺点
2. 复制算法
复制算法是将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉。

复制算法每次都是对整个半区进行内存回收,这样就减少了标记对象遍历的时间,在清除使用区域对象时,不用进行遍历,直接清空整个区域内存,而且在将存活对象复制到保留区域时也是按地址顺序存储的,这样就解决了内存碎片的问题,在分配对象内存时不用考虑内存碎片等复杂问题,只需要按顺序分配内存即可。
复制算法优点
复制算法缺点
3. 标记-整理算法
标记-整理算法算法与标记-清除算法很像,事实上,标记-整理算法的标记过程任然与标记-清除算法一样,但后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存。

可以看到,回收后可回收对象被清理掉了,存活的对象按规则排列存放在内存中。这样一来,当我们给新对象分配内存时,JVM只需要持有内存的起始地址即可。标记/整理算法弥补了标记/清除算法存在内存碎片的问题消除了复制算法内存减半的高额代价,可谓一举两得。
标记-整理缺点
4. 分代收集算法
前文介绍JVM堆内存时已经说过了分代概念和对象在分代中的转移,垃圾回收伴随了对象的转移,其中新生代的回收算法以复制算法为主,老年代的回收算法以标记-清除以及标记-整理为主。
5. 方法区的垃圾回收
方法区主要回收的内容有:废弃常量和无用的类。Full GC(Major GC)的时候会触发方法区的垃圾回收。
//场景1,m1方法接收一个不可能为null的字符串
//在其方法体中我们获取了传入字符串的长度
fun m1(str: String) {
str.length
}
//场景2,m2方法接收一个可能为null的字符串
//在其方法体中我们采用了安全调用操作符 ?. 来获取传入字符串的长度
fun m2(str: String?) {
str?.length
}
//场景3,m3方法接收一个可能为null的字符串
//在其方法体中我们采用了 !! 来获取传入字符串的长度
fun m3(str: String?) {
str!!.length
}
public final static m1(Ljava/lang/String;)V
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "str"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 6 L1
ALOAD 0
INVOKEVIRTUAL java/lang/String.length ()I
POP
L2
LINENUMBER 7 L2
RETURN
L3
LOCALVARIABLE str Ljava/lang/String; L0 L3 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x19
public final static m2(Ljava/lang/String;)V
@Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0
L0
LINENUMBER 10 L0
ALOAD 0
DUP
IFNULL L1
INVOKEVIRTUAL java/lang/String.length ()I
POP
GOTO L2
L1
POP
L2
L3
LINENUMBER 11 L3
RETURN
L4
LOCALVARIABLE str Ljava/lang/String; L0 L4 0
MAXSTACK = 2
MAXLOCALS = 1
public final static m3(Ljava/lang/String;)V
@Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0
L0
LINENUMBER 15 L0
ALOAD 0
DUP
IFNONNULL L1
INVOKESTATIC kotlin/jvm/internal/Intrinsics.throwNpe ()V
L1
INVOKEVIRTUAL java/lang/String.length ()I
POP
L2
LINENUMBER 16 L2
RETURN
L3
LOCALVARIABLE str Ljava/lang/String; L0 L3 0
MAXSTACK = 3
MAXLOCALS = 1
协程可以看做是官方封装的轻量级线程框架。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
可以理解成是Enum枚举类的加强版
sealed class SealedClass{
class SealedClass1():SealedClass()
class SealedClass2():SealedClass()
fun hello(){
println("Hello World ... ")
}
}
fun main(args:Array<String>){
var sc:SealedClass = SealedClass()//这里直接编译报错
}
fun main(args:Array<String>){
var sc:SealedClass = SealedClass.SealedClass1()//只能通过密封类内部的子类实例化对象,这时就可以执行里面的方法了
sc.hello()
}
使用场景:与when表达式搭配
// Result.kt
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
when(result) {
is Result.Success -> { }
is Result.Error -> { }
}
但是如果有人为 Result 类添加了一个新的类型: InProgress:
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object InProgress : Result<Nothing>()
}
如果想要防止遗漏对新类型的处理,并不一定需要依赖我们自己去记忆或者使用 IDE 的搜索功能确认新添加的类型。使用 when 语句处理密封类时,如果没有覆盖所有情况,可以让编译器给我们一个错误提示。和 if 语句一样,when 语句在作为表达式使用时,会通过编译器报错来强制要求必须覆盖所有选项 (也就是说要穷举):
val action = when(result) {
is Result.Success -> { }
is Result.Error -> { }
}
当表达式必须覆盖所有选项时,添加 “is inProgress” 或者 “else” 分支。
如果想要在使用 when 语句时获得相同的编译器提示,可以添加下面的扩展属性:
val <T> T.exhaustive: T
get() = this
这样一来,只要给 when 语句添加 “.exhaustive”,如果有分支未被覆盖,编译器就会给出之前一样的错误。
when(result){
is Result.Success -> { }
is Result.Error -> { }
}.exhaustive
IDE 自动补全
由于一个密封类的所有子类型都是已知的,所以 IDE 可以帮我们补全 when 语句下的所有分支:

当涉及到一个层级复杂的密封类时,这个功能会显得更加好用,因为 IDE 依然可以识别所有的分支:

sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
sealed class Error(val exception: Exception) : Result<Nothing>() {
class RecoverableError(exception: Exception) : Error(exception)
class NonRecoverableError(exception: Exception) : Error(exception)
}
object InProgress : Result<Nothing>()
}
在Kotlin中@JvmOverloads注解的作用就是:在有默认参数值的方法中使用@JvmOverloadsi注解,则Kotlin就会暴露多个重载方法。如果没有加注解@JvmOverloads则只有一个方法,kotlini调用的话如果没有传入的参数用的是默认值。
@JvmOverloads fun f(a: String, b: Int=0, c:String="abc"){
}
// 相当于Java三个方法 不加这个注解就只能当作第三个方法这唯一一种方法
void f(String a)
void f(String a, int b)
// 加不加注解,都会生成这个方法
void f(String a, int b, String c)
//Java实现
public class SingletonDemo {
private static SingletonDemo instance=new SingletonDemo();
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
return instance;
}
}
//Kotlin实现
object SingletonDemo
//Java实现
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){}
public static SingletonDemo getInstance(){
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
//Kotlin实现
class SingletonDemo private constructor() {
companion object {
private var instance: SingletonDemo? = null
get() {
if (field == null) {
field = SingletonDemo()
}
return field
}
fun get(): SingletonDemo{
//细心的小伙伴肯定发现了,这里不用getInstance作为为方法名,是因为在伴生对象声明时,内部已有getInstance方法,所以只能取其他名字
return instance!!
}
}
}
线程安全的懒汉式
//Java实现
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){}
public static synchronized SingletonDemo getInstance(){//使用同步锁
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
//Kotlin实现
class SingletonDemo private constructor() {
companion object {
private var instance: SingletonDemo? = null
get() {
if (field == null) {
field = SingletonDemo()
}
return field
}
@Synchronized
fun get(): SingletonDemo{
return instance!!
}
}
}
//Java实现
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo(){}
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
}
//kotlin实现
class SingletonDemo private constructor() {
companion object {
val instance: SingletonDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
SingletonDemo() }
}
}
//Java实现
public class SingletonDemo {
private static class SingletonHolder{
private static SingletonDemo instance=new SingletonDemo();
}
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
return SingletonHolder.instance;
}
}
//kotlin实现
class SingletonDemo private constructor() {
companion object {
val instance = SingletonHolder.holder
}
private object SingletonHolder {
val holder= SingletonDemo()
}
}
数据类,相当于MWM模式下的model类,相当于java自动重写了equals/hashCode方法、get()方法、set()方法(如果是可写入的)、
toString方法、componentN方法、copy()方法,注意get/set方法是kotlin中的类都会为属性自动生成的方法,和数据类没关系。
注意: 在Kotlin中有== 和 ===,==比较的对象内容,===比较的是对象的引用地址
var girl1:Girl=Girl("嫚嫚",29,160,"廊坊")
var (a,b,c,d)=girl1
println("$a,$b,c,$d")
在Kotlin中所谓的解构就是将一个类对象中的参数拆开来,成为一个一个单独的变量,从而来使用这些单独的变量进行操作。
copy:复制对象使用,当要复制一个对象,只改变一些属性,但其余不变,copy就是为此而生
使用场景:
扩展函数原理:
扩展函数实际上就是一个对应Jva中的静态函数,这个静态函数参数为接收者类型的对象,然后利用这个对象就可以访问这个类
中的成员属性和方法了,并且最后返回一个这个接收者类型对象本身。这样在外部感觉和使用类的成员函数是一样的。
class TestMain(var a: Int, var b: String) {
data class User(var age: Int, var sex: String);
fun main() {
calculate {
System.out.println("调用方法体")
}
}
fun calculate(method: () -> Unit) {
System.out.println("calculate 前")
method()
System.out.println("calculate 后")
}
查看字节码
LINENUMBER 43 L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "calculate \u524d"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L2
LINENUMBER 44 L2
ALOAD 1
INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object; (itf) //多了一次方法调用
POP
L3
LINENUMBER 45 L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "calculate \u540e"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L4
LINENUMBER 46 L4
RETURN
L5
改为inline后
inline fun calculate(method: () -> Unit) {
System.out.println("calculate 前")
method()
System.out.println("calculate 后")
}
public final main()V
L0
LINENUMBER 15 L0
ALOAD 0
ASTORE 1
L1
ICONST_0
ISTORE 2
L2
LINENUMBER 56 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "calculate \u524d"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L3
LINENUMBER 57 L3
L4
ICONST_0
ISTORE 3
L5
LINENUMBER 16 L5 //直接打印没有方法调用
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "\u8c03\u7528\u65b9\u6cd5\u4f53"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L6
LINENUMBER 17 L6
NOP
L7
L8
LINENUMBER 58 L8
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "calculate \u540e"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L9
LINENUMBER 59 L9
NOP
L10
LINENUMBER 18 L10
RETURN
L11
LOCALVARIABLE $i$a$-calculate-TestMain$main$1 I L5 L7 3
LOCALVARIABLE this_$iv Lcom/example/test/TestMain; L1 L10 1
LOCALVARIABLE $i$f$calculate I L2 L10 2
LOCALVARIABLE this Lcom/example/test/TestMain; L0 L11 0
MAXSTACK = 2
MAXLOCALS = 4


// 错误示例
inline fun hello(block: () -> Unit) {
println("Say Hello!")
runOnUiThread {
block()
}
}
fun main() {
hello {
println("Bye!")
return
}
println("Continue")
}
这其实就是在内联函数中间接调用函数类型参数,说“间接”是因为本来 block 的控制权是在 hello() 中,其外层是 hello(),这么一写,控制权就被 runOnUiThread 夺走了,外层变成了 runOnUiThread。而如果此时,我们在 hello() 的 Lambda 表达式中加个 return,那么就会出现一个问题, return 无法结束 main()。因为遵循 inline 规则,最后编译出的代码大致是这样的:

非常明显,return 结束的是 runOnUiThread 中的 Runnable 对象,而不是 main()。那这样与之前的规则不就冲突了吗?所以事实上,这种间接调用写法是不被允许的,IDE 会给你一个刺眼的红线。那如果我们一定要间接调用该怎么做呢?这时就轮到 crossinline 出场了,正确示例如下:
// 正确示例
inline fun hello(crossinline block: () -> Unit) {
println("Say Hello!")
runOnUiThread {
block()
}
}
fun main() {
hello {
println("Bye!")
}
}
我们直接给 block 加上关键字 crossinline,这样就允许间接调用了。
1.概要简述
2.详细说明
init初始化代码块
特殊类

我们思考下,为什么Kotlin设计了一个Any?
当我们需要和Java互操作的时候,Kotlin把Java方法参数和返回类型中用到的Object类型看作Any,这个Any的设计是Kotlin兼容
Java时的一种权衡设计。
所有Java引用类型在Kotlin中都表现为平台类型。当在Kotlin中处理平台类型的值的时候,它既可以被当做可空类型来处理,也可以被当做非空类型来操作。
试想下,如果所有来自Jva的值都被看成非空,那么就容易写出比较危险的代码。反之,如果Java值都强制当做可空,则会导致大量的null检查。综合考量,平台类型是一种折中的设计方案。
fun main() = runBlocking {
val time = measureTimeMillis {
// equeneFlow() //同步 1秒左右
asyncFlow() //异步700多毫秒
}
print("cost $time")
}
//异步的
private suspend fun asyncFlow() {
channelFlow {
for (i in 1..5) {
delay(100)
send(i)
}
}.collect {
delay(100)
println(it)
}
}
//同步的
private suspend fun equeneFlow() {
flow<Int> {
for (i in 1..5) {
delay(100)
emit(i)
}
}.collect {
delay(100)
println(it)
}
}
集合操作低效在哪?
处理集合时性能损耗的最大原因是循环。集合元素迭代的次数越少性能越好。
list
.map { it ++ }
.filter { it % 2 == 0 }
.count { it < 3 }
反编译一下,你会发现:Kotlin编译器会创建三个while循环。
Sequences减少了循环次数
Sequences提高性能的秘密在于这三个操作可以共享同一个迭代器(iterator),只需要一次循环即可完成。Sequences允许map转换一个元素后,立马将这个元素传递给filter操作,而不是像集合(lists)那样,等待所有的元素都循环完成了map操作后,用一个新的集合存储起来,然后又遍历循环从新的集合取出元素完成filter操作。
Sequences是懒惰的
上面的代码示例,map、filter.、count都是属于中间操作,只有等待到一个终端操作,如打印、sum()、average()、first()时才会开始工作
val list = listOf(1, 2, 3, 4, 5, 6)
val result = list.asSequence()
.map{ println("--map"); it * 2 }
.filter { println("--filter");it % 3 == 0 }
println("go~")
println(result.average())
Kotlin 的扩展函数可以毫无副作用给原有库的类增加属性和方法,比如例子中 TextView,我们根本没有去动 TextView 源码,但是却给它增加一个扩展函数。
// 扩展函数的定义
fun TextView.isBold() = this.apply {
paint.isFakeBoldText = true
}
// 扩展函数的调用
activity.find<TextView>(R.id.course_comment_tv_score).isBold()
// 这个类名就是顶层文件名+“Kt”后缀
public final class ExtendsionTextViewKt {
// 扩展函数 isBold 对应实际上是 Java 中的静态函数,并且传入一个接收者类型对象作为参数
@NotNull
public static final TextView isBold(@NotNull TextView $receiver) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
$receiver.getPaint().setFakeBoldText(true); // 设置加粗
return $receiver; // 最后返回这个接收者对象自身,以致于我们在Kotlin中完全可以使用 this 替代接收者对象或者直接不写。
}
}
fun num1AndNum2(num1:Int,num2:Int,operator:(Int,Int)->Int):Int{
val result=operation(num1,num2)
return result
}
fun main(){
val num1=100
val num2=80
val result=num1AndNum2(num1,num2){n1,n2 ->
n1+n2
}
查看编译后的java代码
public static int num1AndNum2(int num1,int num2,Function operation){
int result=(int) operation.invoke(num1,num2);
return result;
}
public static void main(){
int num1=100;
int num2=80;
int result=num1AndNum2(num1,num2,new Function(){
@Override
public Integer invoke(Integer n1,Integer n2){
return n1+n2;
}
});
}
num1AndNum2()函数的第三个参数变成了一个Function接口,这是一种Kotlin内置的接口,里面有一个待实现的invoke()函数。而num1AndNum2()函数其实就是调用了Function接口的invoke()函数,并把num1和num2参数传了进去。
在调用num1AndNum2()函数的时候,之前的Lambda表达式在这里变成了Function接口的匿名类实现,然后在invoke()函数实现了n1+n2的逻辑,并将结果返回。

Activity的生命周期

Activity横竖屏切换生命周期
横竖屏切换涉及到的是Activity的android:configChanges属性;
android:configChanges可以设置的属性值有:
orientation:消除横竖屏的影响
keyboardHidden:消除键盘的影响
screenSize:消除屏幕大小的影响
Service一般用于没有ui界面的长期服务。
Service有两种启动方式
如何保证Service不被杀死
可分为标准广播(无序广播)和有序广播
按照作用范围可分为全局广播和本地广播
按照注册方式可分为静态广播和动态广播
<receiver
android:name=".broadcast.MyHaveBroadcastReceiver02"
android:exported="true">
<intent-filter android:priority="003">
<action android:name="my_have_broadcast_receiver"></action>
</intent-filter>
</receiver>
/**
* 有序广播
*/
public class MyHaveBroadcastReceiver02 extends BroadcastReceiver {
private static final String TAG = "MyHaveBroadcastReceiver02";
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
Bundle extras = intent.getExtras();
String data = "";
if (extras != null) {
data = extras.getString("name3");
/**
* 2.1有序广播可以通过setResultExtras向下游广播接收器传递数据
*/
extras.putString("name2", "喊你速速到龙阳路集合");
setResultExtras(extras);
/**
* 2.2有序广播可以通过setResultData向下游广播接收器传递字符串
*/
setResultData("2号口的信息");
}
Log.d(TAG, "MyHaveBroadcastReceiver02 onReceive" + data);
/**
* 1.有序广播可以通过abortBroadcast();方法对广播进行截断
*/
// abortBroadcast();
}
}
}
Handler是Android用来解决线程间通讯问题的消息机制。Handler消息机制分为四个部分。
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> mTarget;
public MyHandler(MainActivity activity) {
mTarget = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
HandlerActivity activity = weakReference.get();
super.handleMessage(msg);
if (null != activity) {
//执行业务逻辑
if (msg.what == 0) {
Log.e("myhandler", "change textview");
MainActivity ma = mTarget.get();
ma.textView.setText("hahah");
}
Toast.makeText(activity,"handleMessage",Toast.LENGTH_SHORT).show();
}
}
}
private Handler handler1 = new MyHandler(this);
new Thread(new Runnable() {
@Override
public void run() {
handler1.sendEmptyMessage(0);
}
}).start();
private static class MyHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == 0) {
Log.e("child thread", "receive msg from main thread");
}
}
}
private Handler handler1;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); //准备Looper环境
handler1 = new MyHandler();
Looper.loop(); //启动Looper
Log.e("child thread", "child thread end");
}
}).start();
handler1.sendEmptyMessage(0);
handler1.getLooper().quitSafely();//子线程Handler不用时,退出Looper
}
因为在app启动时,ActivityThread的Main方法里帮我们调用了Looper.prepareMainLooper()。并且最后调用了Looper.loop()
启动了主线程。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// Install selective syscall interception
AndroidOs.install();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
// Call per-process mainline module initialization.
initializeMainlineModules();
Process.setArgV0("" );
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
public static void prepare() {
prepare(true);
}
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));
}
线程其实就是一段可执行的代码,当可执行的代码执行完成后,线程的生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出
主线程的死循环并不消耗 CPU 资源,这里就涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
同步屏障是为了保证异步消息的优先执行,一般是用于UI绘制消息,避免主线程消息太多,无法及时处理UI绘制消息,导致卡顿。
public static Handler createAsync(@NonNull Looper looper) {
if (looper == null) throw new NullPointerException("looper must not be null");
return new Handler(looper, null, true);
}
当 Looper轮循MessageQueue 遍历 Message发现建立了同步屏障的时候,会去跳过其他Message,读取下个 async 的 Message 并执行,屏障移除之前同步 Message 都会被阻塞。
比如屏幕刷新 Choreographer 就使用到了同步屏障 ,确保屏幕刷新事件不会因为队列负荷影响屏幕及时刷新。
注意: 同步屏障的添加或移除 API MessageQueue.postSyncBarrier并未对外公开,App 需要使用的话需要依赖反射机制
try {
MessageQueue queue=handler.getLooper().getQueue();
Method method=MessageQueue.class.getDeclaredMethod("postSyncBarrier");
method.setAccessible(true);
token= (int) method.invoke(queue);
} catch (Exception e) {
e.printStackTrace();
}
try {
MessageQueue queue=handler.getLooper().getQueue();
Method method=MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);
method.setAccessible(true);
method.invoke(queue,token);
} catch (Exception e) {
e.printStackTrace();
}
持有 Activity 实例的匿名内部类或内部类的 生命周期 应当和 Activity 保持一致,否则产生内存泄露的风险。
如果 Handler 使用不当,将造成不一致,表现为:匿名内部类或内部类写法的 Handler、Handler$Callback、Runnable,或者Activity 结束时仍有活跃的 Thread 线程或 Looper 子线程
具体在于:异步任务仍然活跃或通过发送的 Message 尚未处理完毕,将使得内部类实例的 生命周期被错误地延长 。造成本该回收的 Activity 实例 被别的 Thread 或 Main Looper 占据而无法及时回收 (活跃的 Thread 或 静态属性 sMainLooper 是 GC Root 对象)
建议的做法:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看出优先级是Message.CallBack->Handler.callback->Handler.handleMessage
有时候面试官也会问Runnable->Callable->handleMessage
post方法就是runnable
Handler构造传入Callback就是Callable
send方法是handleMessage
Message使用享元设计模式,里面有一个spool指向一个Message对象,还有一个next指向下一个Message,维护了一个链表实现的对象池,obtain的时候在表头头取Message,在Message回收的时候在表头添加一个Message。
Android 中 UI 非线程安全,并发访问的话会造成数据和显示错乱。
此限制的检查始于ViewRootImpl#checkThread(),其会在刷新等多个访问 UI 的时机被调用,去检查当前线程,非主线程的话抛出异常。(实际上并不是检查主线程。而是检查UI的更新线程是否与UI的创建线程一致,因为UI是在主线程创建的,所以也只能在主线程更新)
而 ViewRootImpl 的创建在 onResume() 之后,也就是说如果在 onResume() 执行前启动线程访问 UI 的话是不会报错的。

ExecutorService es;
ThreadLocal tl;
es.execute(()->{
//ThreadLocal增加变量
tl.set(obj);
try{
//业务代冯
}finally{
//于动消ThreadLocal
tl.remove();}
});
读取合适 Message 的 MessageQueue#next() 会因为 Message 尚无或执行条件尚未满足进行两种等的等待:
无限等待
尚无 Message(队列中没有 Message 或建立了同步屏障但尚无异步 Message)的时候,调用 Natvie 侧的 pollOnce() 会传入参数 -1 。
Linux 执行 epoll_wait() 将进入无限等待,其等待合适的 Message 插入后调用 Native 侧的 wake() 唤醒 fd 写入事件触发唤醒 MessageQueue 读取的下一次循环
有限等待
有限等待的场合将下一个 Message 剩余时长作为参数 交给 epoll_wait(),epoll 将等待一段时间之后 自动返回 ,接着回到 MessageQueue 读取的下一次循环。
Looper 准备和开启轮循:
尚无 Message 的话,调用 Native 侧的 pollOnce() 进入 无限等待
存在 Message,但执行时间 when 尚未满足的话,调用 pollOnce() 时传入剩余时长参数进入 有限等待
Looper#prepare() 初始化线程独有的 Looper 以及 MessageQueue
Looper#loop() 开启 死循环 读取 MessageQueue 中下一个满足执行时间的 Message
Message 发送、入队和出队:
Native 侧如果处于无限等待的话:任意线程向 Handler 发送 Message 或 Runnable 后,Message 将按照 when 条件的先后,被插入 Handler 持有的 Looper 实例所对应的 MessageQueue 中 适当的位置 。MessageQueue 发现有合适的 Message 插入后将调用 Native 侧的 wake() 唤醒无限等待的线程。这将促使 MessageQueue 的读取继续 进入下一次循环 ,此刻 Queue 中已有满足条件的 Message 则出队返回给 Looper
Native 侧如果处于有限等待的话:在等待指定时长后 epoll_wait 将返回。线程继续读取 MessageQueue,此刻因为时长条件将满足将其出队
handler处理 Message 的实现:
Looper 得到 Message 后回调 Message 的 callback 属性即 Runnable,或依据 target 属性即 Handler,去执行 Handler 的回调。存在 mCallback 属性的话回调 Handler$Callback反之,回调 handleMessage()
Activity启动走完onResume方法后,在ActivityThread#handleResumeActivity会进行window的添加。
window添加过程会调用 ViewRootImpl的setView() 方法,setView()方法会调用 requestLayout() 方法来请求绘制布局,requestLayout()方法内部又会走到 scheduleTraversals() 方法,最后会走到 performTraversals() 方法,接着到了我们熟知的测量、布局、绘制三大流程了。
//ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
//此字段保证同时间多次更改只会刷新一次,例如TextView连续两次setText(),也只会走一次绘制流程
mTraversalScheduled = true;
//添加同步屏障,屏蔽同步消息,保证VSync到来立即执行绘制
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//mTraversalRunnable是TraversalRunnable实例,最终走到run(),也即doTraversal();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
//开始三大绘制流程
performTraversals();
...
}
}
mChoreographer,是在ViewRootImpl的构造方法内使用Choreographer.getInstance()创建:
Choreographer mChoreographer;
//ViewRootImpl实例是在添加window时创建
public ViewRootImpl(Context context, Display display) {
...
mChoreographer = Choreographer.getInstance();
...
}
public static Choreographer getInstance() {
return sThreadInstance.get();
}
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
//当前线程要有looper,Choreographer实例需要传入
throw new IllegalStateException("The current thread must have a looper!");
}
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
//使用当前线程looper创建 mHandler
mHandler = new FrameHandler(looper);
//USE_VSYNC 4.1以上默认是true,表示 具备接受VSync的能力,这个接受能力就是FrameDisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
// 计算一帧的时间,Android手机屏幕是60Hz的刷新频率,就是16ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 创建一个链表类型CallbackQueue的数组,大小为5,
//也就是数组中有五个链表,每个链表存相同类型的任务:输入、动画、遍历绘制等任务(CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL)
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
// b/68769804: For low FPS experiments.
setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
}
安排任务—postCallback
回头看mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)方法,注意到第一个参数是CALLBACK_TRAVERSAL,表示回调任务的类型,共有以下5种类型:
//输入事件,首先执行
public static final int CALLBACK_INPUT = 0;
//动画,第二执行
public static final int CALLBACK_ANIMATION = 1;
//插入更新的动画,第三执行
public static final int CALLBACK_INSETS_ANIMATION = 2;
//绘制,第四执行
public static final int CALLBACK_TRAVERSAL = 3;
//提交,最后执行,
public static final int CALLBACK_COMMIT = 4;
五种类型任务对应存入对应的CallbackQueue中,每当收到 VSYNC 信号时,Choreographer 将首先处理 INPUT 类型的任务,然后是 ANIMATION 类型,最后才是 TRAVERSAL 类型。
postCallback()内部调用postCallbackDelayed(),接着又调用postCallbackDelayedInternal()
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
// 当前时间
final long now = SystemClock.uptimeMillis();
// 加上延迟时间
final long dueTime = now + delayMillis;
//取对应类型的CallbackQueue添加任务
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
//立即执行
scheduleFrameLocked(now);
} else {
//延迟运行,最终也会走到scheduleFrameLocked()
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
首先取对应类型的CallbackQueue添加任务,action就是mTraversalRunnable,token是null。CallbackQueue的addCallbackLocked()就是把 dueTime、action、token组装成CallbackRecord后 存入CallbackQueue的下一个节点
然后注意到如果没有延迟会执行scheduleFrameLocked()方法,有延迟就会使用 mHandler发送MSG_DO_SCHEDULE_CALLBACK消息,并且注意到 使用msg.setAsynchronous(true)把消息设置成异步,这是因为前面设置了同步屏障,只有异步消息才会执行。我们看下mHandler的对这个消息的处理:
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
// 执行doFrame,即绘制过程
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
//申请VSYNC信号,例如当前需要绘制任务时
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
//需要延迟的任务,最终还是执行上述两个事件
doScheduleCallback(msg.arg1);
break;
}
}
}
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
//开启了VSYNC
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
//当前执行的线程,是否是mLooper所在线程
if (isRunningOnLooperThreadLocked()) {
//申请 VSYNC 信号
scheduleVsyncLocked();
} else {
// 若不在,就用mHandler发送消息到原线程,最后还是调用scheduleVsyncLocked方法
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);//异步
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
// 如果未开启VSYNC则直接doFrame方法(4.1后默认开启)
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);//异步
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
FrameHandler的作用很明显里了:发送异步消息(因为前面设置了同步屏障)。有延迟的任务发延迟消息、不在原线程的发到原线程、没开启VSYNC的直接走 doFrame 方法取执行绘制。
申请和接受VSync
scheduleVsyncLocked 方法是如何申请 VSYNC 信号的。申请 VSYNC 信号后,信号到来时也是走doFrame() 方法:
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
调用mDisplayEventReceiver的scheduleVsync()方法,mDisplayEventReceiver是Choreographer构造方法中创建,是FrameDisplayEventReceiver 的实例。 FrameDisplayEventReceiver是 DisplayEventReceiver 的子类,DisplayEventReceiver 是一个 abstract class:
public DisplayEventReceiver(Looper looper, int vsyncSource) {
if (looper == null) {
throw new IllegalArgumentException("looper must not be null");
}
mMessageQueue = looper.getQueue();
// 注册VSYNC信号监听者
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource);
mCloseGuard.open("dispose");
}
在 DisplayEventReceiver 的构造方法会通过 JNI 创建一个 IDisplayEventConnection 的 VSYNC 的监听者。
FrameDisplayEventReceiver的scheduleVsync()就是在 DisplayEventReceiver中:
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
// 申请VSYNC中断信号,会回调onVsync方法
nativeScheduleVsync(mReceiverPtr);
}
}
scheduleVsync()就是使用native方法nativeScheduleVsync()去申请VSYNC信号。这个native方法就看不了了,只需要知道VSYNC信号的接受回调是onVsync()
/**
* 接收到VSync脉冲时 回调
* @param timestampNanos VSync脉冲的时间戳
* @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
* @param frame 帧号码,自增
*/
@UnsupportedAppUsage
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
}
具体实现是在FrameDisplayEventReceiver中:
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
timestampNanos = now;
}
if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be "
+ "one at a time.");
} else {
mHavePendingVsync = true;
}
mTimestampNanos = timestampNanos;
mFrame = frame;
//将本身作为runnable传入msg, 发消息后 会走run(),即doFrame(),也是异步消息
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
onVsync()中,将接收器本身作为runnable传入异步消息msg,并使用mHandler发送msg,最终执行的就是doFrame()方法了。
注意一点是,onVsync()方法中只是使用mHandler发送消息到MessageQueue中,不一定是立刻执行,如何MessageQueue中前面有较为耗时的操作,那么就要等完成,才会执行本次的doFrame()。
doFrame
VSync信号接收到后确实是走 doFrame()方法,那么就来看看Choreographer的doFrame()
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
...
// 预期执行时间
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
// 超时时间是否超过一帧的时间(这是因为MessageQueue虽然添加了同步屏障,但是还是有正在执行的同步任务,导致doFrame延迟执行了)
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
// 计算掉帧数
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
// 掉帧超过30帧打印Log提示
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
...
frameTimeNanos = startNanos - lastFrameOffset;
}
...
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
// Frame标志位恢复
mFrameScheduled = false;
// 记录最后一帧时间
mLastFrameTimeNanos = frameTimeNanos;
}
try {
// 按类型顺序 执行任务
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 根据指定的类型CallbackkQueue中查找到达执行时间的CallbackRecord
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
//提交任务类型
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ " ms which is more than twice the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ " ms in the past.");
mDebugPrintNextFrameTimeDelta = true;
}
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
try {
// 迭代执行队列所有任务
for (CallbackRecord c = callbacks; c != null; c = c.next) {
// 回调CallbackRecord的run,其内部回调Callback的run
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
//回收CallbackRecord
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
}
}
主要内容就是取对应任务类型的队列,遍历队列执行所有任务,执行任务是 CallbackRecord的 run 方法:
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
@UnsupportedAppUsage
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
// 通过postFrameCallback 或 postFrameCallbackDelayed,会执行这里
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
//取出Runnable执行run()
((Runnable)action).run();
}
}
}
前面看到mChoreographer.postCallback传的token是null,所以取出action,就是Runnable,执行run(),这里的action就是 ViewRootImpl 发起的绘制任务mTraversalRunnable了,那么这样整个逻辑就闭环了。
那么 啥时候 token == FRAME_CALLBACK_TOKEN 呢?答案是Choreographer的postFrameCallback()方法:
public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
//也是走到是postCallbackDelayedInternal,并且注意是CALLBACK_ANIMATION类型,
//token是FRAME_CALLBACK_TOKEN,action就是FrameCallback
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
public interface FrameCallback {
public void doFrame(long frameTimeNanos);
}
可以看到postFrameCallback()传入的是FrameCallback实例,接口FrameCallback只有一个doFrame()方法。并且也是走到postCallbackDelayedInternal,FrameCallback实例作为action传入,token则是FRAME_CALLBACK_TOKEN,并且任务是CALLBACK_ANIMATION类型。
Choreographer的postFrameCallback()通常用来计算丢帧情况,使用方式如下:
//Application.java
public void onCreate() {
super.onCreate();
//在Application中使用postFrameCallback
Choreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));
}
public class FPSFrameCallback implements Choreographer.FrameCallback {
private static final String TAG = "FPS_TEST";
private long mLastFrameTimeNanos = 0;
private long mFrameIntervalNanos;
public FPSFrameCallback(long lastFrameTimeNanos) {
mLastFrameTimeNanos = lastFrameTimeNanos;
mFrameIntervalNanos = (long)(1000000000 / 60.0);
}
@Override
public void doFrame(long frameTimeNanos) {
//初始化时间
if (mLastFrameTimeNanos == 0) {
mLastFrameTimeNanos = frameTimeNanos;
}
final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if(skippedFrames>30){
//丢帧30以上打印日志
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
}
mLastFrameTimeNanos=frameTimeNanos;
//注册下一帧回调
Choreographer.getInstance().postFrameCallback(this);
}
}
Choreographer的postCallback()、postFrameCallback() 作用理解:发送任务 存队列中,监听VSync信号,当前VSync到来时 会使用mHandler发送异步message,这个message的Runnable就是队列中的所有任务。
一般需要重写onMeasure,onLayout和onDraw方法。
MeasureSpec
MeasureSpec是View的一个公有静态内部类,它是一个 32 位的int值,高 2 位表示 SpecMode(测量模式),低 30 位表示 SpecSize(测量尺寸/测量大小)。
MeasureSpec将两个数据打包到一个int值上,可以减少对象内存分配,并且其提供了相应的工具方法可以很方便地让我们从一个int值中抽取出 View 的 SpecMode 和 SpecSize。
// frameworks/base/core/java/android/view/View.java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
// 生成测量规格
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
// 获取测量模式
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
// 获取测量大小
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
...
}
LayoutParams
对 View 进行测量,最关键的一步就是计算得到 View 的MeasureSpec,子View 在创建时,可以指定不同的LayoutParams(布局参数),LayoutParams的源码主要内容如下所示:
// frameworks/base/core/java/android/view/ViewGroup.java
public static class LayoutParams {
...
/**
* Special value for the height or width requested by a View.
* MATCH_PARENT means that the view wants to be as big as its parent,
* minus the parent's padding, if any. Introduced in API Level 8.
*/
public static final int MATCH_PARENT = -1;
/**
* Special value for the height or width requested by a View.
* WRAP_CONTENT means that the view wants to be just large enough to fit
* its own internal content, taking its own padding into account.
*/
public static final int WRAP_CONTENT = -2;
/**
* Information about how wide the view wants to be. Can be one of the
* constants FILL_PARENT (replaced by MATCH_PARENT
* in API Level 8) or WRAP_CONTENT, or an exact size.
*/
public int width;
/**
* Information about how tall the view wants to be. Can be one of the
* constants FILL_PARENT (replaced by MATCH_PARENT
* in API Level 8) or WRAP_CONTENT, or an exact size.
*/
public int height;
...
}
LayoutParams会受到父容器的MeasureSpec的影响,测量过程会依据两者之间的相互约束最终生成子View 的MeasureSpec,完成 View 的测量规格。
总之。View 的MeasureSpec受自身的LayoutParams和父容器的MeasureSpec共同决定(DecorView的MeasureSpec是由自身的LayoutParams和屏幕尺寸共同决定,参考后文)。也因此,如果要求取子View 的MeasureSpec,那么首先就需要知道父容器的MeasureSpec,层层逆推而上,即最终就是需要知道顶层View(即DecorView)的MeasureSpec,这样才能一层层传递下来,这整个过程需要结合Activity的启动过程进行分析。
总结
View 的绘制主要有以下一些核心内容:
三大流程:View 绘制主要包含如下三大流程:
注:不同于 measure 流程首先对 子View 进行测量,最后才测量自己,layout 流程首先是先定位自己的布局位置,然后才处理放置 子View 的布局位置。
注:通常自定义View 覆写onDraw(…)方法,完成自己的绘制即可,ViewGroup 一般充当容器使用,因此通常无需覆写onDraw(…)。
总结一下 View 绘制的整个流程:
注:ViewRootImpl#setView(…)内容通过将其成员属性ViewRootImpl#mView指向DecorView,完成两者之间的关联。
事件分发机制主要靠三个方法完成dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;//事件是否被消费
if (onInterceptTouchEvent(ev)){//调用onInterceptTouchEvent判断是否拦截事件
consume = onTouchEvent(ev);//如果拦截则调用自身的onTouchEvent方法
}else{
consume = child.dispatchTouchEvent(ev);//不拦截调用子View的dispatchTouchEvent方法
}
return consume;//返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
}
整个事件分发到处理可以用一个U形图来表示

简述流程
从顶层DecorView开始分析
ListView优化需要自定义ViewHolder和判断convertView是否为null。 而RecyclerView是存在规定好的ViewHolder。
对于ListView,只能在垂直的方向滚动。而对于RecyclerView,他里面的LayoutManager中制定了一套可以扩展的布局排列接口,所以我们可以重写LayoutManager来定制自己需要的布局。RecycleView可以根据LayoutManger有横向,瀑布和表格布局
recycleView可以支持在添加,删除或者移动Item的时候,RecyclerView.ItemAnimator添加动画效果,而listview不支持。而且RecyclerView有四重缓存,而ListView只有二重缓存。ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是"一锅端",将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改
标志位,区分是否重新bindView。
ListView的适配器继承ArrayAdapter;RecycleView的适配器继承RecyclerAdapter,并将范类指定为子项对象类.ViewHolder(内部类)。
ListView是在主方法中ListView对象的setOnItemClickListener方法;RecyclerView则是在子项具体的View中去注册事件。
recycleView以下几个部分
recycleView会将测量 onMeasure 和布局 onLayout 的工作委托给 LayoutManager 来执行,不同的 LayoutManager 会有不同风格的布局显示,这是一种策略模式。用一张图来描述这段过程如下:

recycleView分为四级缓存
一级缓存 mAttachedScrap&mChangedScrap
这两者主要用来缓存屏幕内的 ViewHolder。主要作用是数据更新时直接复用旧的ViewHolder。
例如下拉刷新时,只需要在原有的 ViewHolder 基础上进行重新绑定新的数据 data 即可,而这些旧的 ViewHolder 就是被保存在 mAttachedScrap 和 mChangedScrap 中。实际上当我们调用 RV 的 notifyXXX 方法时,就会向这两个列表进行填充,将旧 ViewHolder 缓存起来。
二级缓存 mCachedViews
它用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存个数是 2个,不过可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则将旧 ViewHolder 抛弃,然后添加新的 ViewHolder,如下所示:

三级缓存 ViewCacheExtension
这是 RV 预留给开发人员的一个抽象类,在这个类中只有一个抽象方法,如下:
public abstract static class viewCacheExtension{
@Nullable
public abstract view getviewForPositionAndType(@NonNull Recycler recycler,
int position,int type);
}
开发人员可以通过继承 ViewCacheExtension,并复写抽象方法 getViewForPositionAndType 来实现自己的缓存机制。只是一般情况下我们不会自己实现也不建议自己去添加缓存逻辑,因为这个类的使用门槛较高,需要开发人员对 RV 的源码非常熟悉。
第一次布局时,并不会触发pre-layout。pre-layout只会在每次notify change时才会被触发,目的是通过saveOldPosition方法将屏幕中各位置上的ViewHolder的坐标记录下来,并在重新布局之后,通过对比实现Item的动画效果。
在RecyclerView的dispatchLayoutStep1阶段,会调用自定义LayoutManager的 supportsPredictiveItemAnimations 方法判断在某些状态下是否展示predictive animation。以下LinearLayoutManager的实现:
@Override
public boolean supportsPredictiveItemAnimations() {
return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd;
}
如果 supportsPredictiveItemAnimations 返回true,则LayoutManager中复写onLayoutChildren方法会被调用2次:一次是在pre-layout,另一次是real-layout。
因为会有pre-layout和real-layout,所有在自定义LayoutManager中,需要根据RecyclerView.State中的isPreLayout方法的返回值,在这两次布局中做区分。比如LinearLayoutManager中的onLayoutChildren中有如下判断:
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
......
if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
&& mPendingScrollPositionOffset != INVALID_OFFSET) {
// if the child is visible and we are going to move it around, we should layout
// extra items in the opposite direction to make sure new items animate nicely
// instead of just fading in
final View existing = findViewByPosition(mPendingScrollPosition);
if (existing != null) {
final int current;
final int upcomingOffset;
if (mShouldReverseLayout) {
current = mOrientationHelper.getEndAfterPadding()
- mOrientationHelper.getDecoratedEnd(existing);
upcomingOffset = current - mPendingScrollPositionOffset;
} else {
current = mOrientationHelper.getDecoratedStart(existing)
- mOrientationHelper.getStartAfterPadding();
upcomingOffset = mPendingScrollPositionOffset - current;
}
if (upcomingOffset > 0) {
extraForStart += upcomingOffset;
} else {
extraForEnd -= upcomingOffset;
}
}
}
......
}
意思就是如果当前正在update的item是可见状态,则需要在pre-layout阶段额外填充一个item,目的是为了保证处于不可见状态的item可以平滑的滑动到屏幕内。


如果自定义LayoutManager并没有实现pre-layout,或者实现不合理,则当item2移出屏幕时,只会将item3和item4进行平滑移动,而item5只是单纯的appear到屏幕中,如下所示:

缓存到CachedView中的ViewHolder并不会清理相关信息(比如position、state等),因此刚移出屏幕的ViewHolder,再次被移回屏幕时,只要从CachedView中查找并显示即可,不需要重新绑定(bindViewHolder)。
而缓存到RecycledViewPool中的ViewHolder会被清理状态和位置信息,因此从RecycledViewPool查找到ViewHolder,需要重新调用bindViewHolder绑定数据。
IPC 是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。
Android中的IPC方式主要有以下几种:
使用Bundle
四大组件中的三大组件(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程间传输,这是一种最简单的进程间通信方式,
使用文件共享
文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。
使用Messenger
实现一个Messenger有如下几个步骤,分为服务端和客户端:
1.服务端进程
首先,我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。
2.客户端进程
客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Message对象。如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。
使用AIDL
使用ContentProvider
ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式。和Messenger一样,ContentProvider的底层实现同样也是Binder。虽然ContentProvider的底层实现是Binder,但是它的使用过程要比AIDL简单许多,这是因为系统已经为我们做了封装,使得我们无须关心底层细节即可轻松实现IPC。
使用Socket
Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中的TCP和UDP协议。
TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性;
而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能。在性能上,UDP具有更好的效率,其缺点是不保证数据一定能够正确传输,尤其是在网络拥塞的情况下。

Binder 是一种进程间通信机制,基于开源的 OpenBinder 实现。优点是高性能、稳定性和安全性。

传统 IPC 通信原理
这种传统的 IPC 通信方式有两个问题:
Binder 跨进程通信原理
跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分,可以通过内核支持来实现进程间通信。但是 Binder 并不是 Linux 系统内核的一部分。如何实现呢?利用了Linux系统的动态内核可加载模块和内存映射
一次完整的 Binder IPC 通信过程通常是这样:

MVC是 模型 - 视图 - 控制器 MVC的目的就是将M和V的代码分离,且MVC是单向通信,必须通过Controller来承上启下。

MVP是 模型 - 视图 - 表示器

MVVM是 模型 - 视图 - 视图模型 MVVM与MVP框架区别在于:MVVM采用双向绑定:View的变动,自动反映在ViewModel,反之亦然。
Model:数据模型(数据处理业务),指的是后端传递的数据。
View:视图,将Model的数据以某种方式展示出来。
ViewModel:视图模型,数据的双向绑定(当Model中的数据发生改变时View就感知到,当View中的数据发生变化时Model也能感知到),是MVVM模式的核心。ViewModel 层把 Model 层和 View 层的数据同步自动化了,解决了 MVP 框架中数据同步比较麻烦的问题,不仅减轻了 ViewModel 层的压力,同时使得数据处理更加方便——只需告诉 View 层展示的数据是 Model 层中的哪一部分即可。

Lifecycle是一个管理生命周期的工具类,Lifecycle是一个抽象类,它通过Event枚举类型维护了生命周期分别对应的状态,通过State维护了执行了某一个生命周期函数后和在执行下一个生命周期函数前被观察者(Activity或Fragment)处于什么状态
Lifecycle是通过在Activity中绑定了一个空的fragment来实现监听Activity的生命周期的,因为如果在Activity中添加一个fragment 那在fragment的生命周期执行的时候 就能知道宿主对应的生命周期执行了
在ComponentActivity的onCreate方法中添加一个名字为ReportFragment空Fragment
ComponentActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSavedStateRegistryController.performRestore(savedInstanceState);
ReportFragment.injectIfNeededIn(this);
if (mContentLayoutId != 0) {
setContentView(mContentLayoutId);
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
dispatchCreate(mProcessListener);
dispatch(Lifecycle.Event.ON_CREATE);
}
@Override
public void onStart() {
super.onStart();
dispatchStart(mProcessListener);
dispatch(Lifecycle.Event.ON_START);
}
@Override
public void onResume() {
super.onResume();
dispatchResume(mProcessListener);
dispatch(Lifecycle.Event.ON_RESUME);
}
@Override
public void onPause() {
super.onPause();
dispatch(Lifecycle.Event.ON_PAUSE);
}
@Override
public void onStop() {
super.onStop();
dispatch(Lifecycle.Event.ON_STOP);
}
@Override
public void onDestroy() {
super.onDestroy();
dispatch(Lifecycle.Event.ON_DESTROY);
// just want to be sure that we won't leak reference to an activity
mProcessListener = null;
}
private void dispatch(Lifecycle.Event event) {
Activity activity = getActivity();
if (activity instanceof LifecycleRegistryOwner) {
((LifecycleRegistryOwner) activity).getLifecycle().handleLifecycleEvent(event);
return;
}
if (activity instanceof LifecycleOwner) {
Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle();
if (lifecycle instanceof LifecycleRegistry) {
((LifecycleRegistry) lifecycle).handleLifecycleEvent(event);
}
}
}
ComponentActivity 实现了 LifecycleOwner ,所以可以添加观察者,并且自己的onCreate 方法中,创建并添加了一个ReportFragment ,用来感应自身的生命周期的回调, 并进行对应分发。
在分发过程中,Lifecycle 通过内部维护的状态机 将生命周期事件转化为State状态,并进行存储,在 分发过程中,通过状态比较来判断 当前过程是正在可见还是正在不可见,不同过程采用不同策略。最后通过调用 LifecycleEventObserver 的 onStateChanged 方法 来进行回调。
Room 是 Google 官方推出的数据库 ORM 框架。ORM:即 Object Relational Mapping,即对象关系映射为面向对象的语言。
Room 包含三个组件:Entity、DAO 和 Database。
原理分析
Room 在编译期通过 kapt 处理 @Dao 和 @Database 注解,通过注解在运行时生成代码和SQL语句,并生成 DAO 和 Database 的实现类:TestRoomDataBase_Impl 和 UserDao_Impl。
LiveData 是 Jetpack 推出的基于观察者的消息订阅/分发的可观察数据组件,具有宿主(Activity、Fragment)生命周期感知能力,这种感知能力可确保 LiveData 仅分发消息给处于活跃状态的观察者,即只有处于活跃状态的观察者才能收到消息。
事件注册流程
事件分发流程
postValue自带切换主线程的能力
public abstract class LiveData<T> {
static final Object NOT_SET = new Object();
//用于线程切换时,暂存Data用的变量
volatile Object mPendingData = NOT_SET;
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
//1.判断是否为一次有效的postValue
postTask = mPendingData == NOT_SET;
//2.暂存对象到mPendingData中
mPendingData = value;
}
//3.过滤无效postValue
if (!postTask) {
return;
}
//4.这里通过ArchTaskExecutor切换线程,其实ArchTaskExecutor切换线程的核心靠一个掌握着MainLooper的Handler切换
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
//用于给Handler执行切换线程的Runnable
private final Runnable mPostValueRunnable = new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
//5.从缓存中获得Data
newValue = mPendingData;
mPendingData = NOT_SET;
}
//6.通过setValue设置数据
setValue((T) newValue);
}
};
}
setValue(T data)
事件分发的关键函数,主要做了三件事
1、版本+1,LiveData的mVersion变量表示的是发送数据的版本,每次发送一次数据, 它都会+1;
2、将值赋值给全局变量mData;
3、调用dispatchingValue方法分发数据,dispatchingValue方法中主要调用的是considerNotify方法
@MainThread
protected void setValue(T value) {
//1.判断当前是否主线程,如果不是则报错(因为多线程会导致数据问题)
assertMainThread("setValue");
//2.版本号自增
mVersion++;
//3.数据源更新
mData = value;
//4.分发事件逻辑
dispatchingValue(null);
}
//事件分发的函数
@SuppressWarnings("WeakerAccess") /* synthetic access */
void dispatchingValue(@Nullable ObserverWrapper initiator) {
//5.判断是否分发中,如果是的话,忽略这次分发
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
//6.设置表示
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
//7.判断参数中,有没指定Observer,如果有则只通知指定Observer,没有的话则遍历全部Observer通知
if (initiator != null) {
//7.0
considerNotify(initiator);
initiator = null;
} else {
//7.1 遍历全部Observer通知更新
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
//分发函数
private void considerNotify(ObserverWrapper observer) {
//8. 判断如果当前监听器不活跃,则不分发
if (!observer.mActive) {
return;
}
//9. 二次确认监听器所在宿主是否活跃,如果不活跃,则证明Observer中的mActive状态并非最新的,调用activeStateChanged更新状态
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//10. 判断版本号是否一致,如果一致则不需要分发
if (observer.mLastVersion >= mVersion) {
return;
}
//11. 对齐版本号
observer.mLastVersion = mVersion;
//12. 通知监听器
observer.mObserver.onChanged((T) mData);
}
observer会自动监听生命周期,当是STARTED和RESUMED的时候才会触发订阅,而observeForever则是会一直检测数据变化,只要数据变化了就会触发订阅。
@MainThread
public void observeForever(@NonNull Observer<? super T> observer) {
assertMainThread("observeForever");
// 会创建一个总是活动的观察者
LiveData<T>.AlwaysActiveObserver wrapper = new LiveData.AlwaysActiveObserver(observer);
......
如源码所示,observeForever会创建一个总是活动的观察者AlwaysActiveObserver,改类继承ObserverWrapper,并实现了shouldBeActive方法,而且始终返回true,所以只要数据一改变,会触发onChanged方法。
private class AlwaysActiveObserver extends LiveData<T>.ObserverWrapper {
AlwaysActiveObserver(Observer<? super T> observer) {
super(observer);
}
// 一直返回true
boolean shouldBeActive() {
return true;
}
}
流程分析
val localSocket = LocalSocket()
val localSocketAddress =
LocalSocketAddress("zygote", LocalSocketAddress.Namespace.RESERVED)
localSocket.connect(localSocketAddress)
val outputStream = localSocket.outputStream
outputStream.write(1)
outputStream.flush()
实验下来LocalSocket也是有权限验证的。

connectLocal在native层的实现是socket_connect_local方法。
socket_connect_local(JNIEnv *env, jobject object,
jobject fileDescriptor, jstring name, jint namespaceId)
{
int ret;
int fd;
if (name == NULL) {
jniThrowNullPointerException(env, NULL);
return;
}
fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
if (env->ExceptionCheck()) {
return;
}
ScopedUtfChars nameUtf8(env, name);
ret = socket_local_client_connect(
fd,
nameUtf8.c_str(),
namespaceId,
SOCK_STREAM);
if (ret < 0) {
jniThrowIOException(env, errno);
return;
}
}
所以最终的校验逻辑应该在Linux层的socket_local_client_connect方法。
socket_connect_local(JNIEnv *env, jobject object,
jobject fileDescriptor, jstring name, jint namespaceId)
{
int ret;
int fd;
if (name == NULL) {
jniThrowNullPointerException(env, NULL);
return;
}
fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
if (env->ExceptionCheck()) {
return;
}
ScopedUtfChars nameUtf8(env, name);
ret = socket_local_client_connect(
fd,
nameUtf8.c_str(),
namespaceId,
SOCK_STREAM);
if (ret < 0) {
jniThrowIOException(env, errno);
return;
}
}
LocalSocket其实也有权限校验,并不意味着可以被所有进程随意调用。
5. Binder拷贝问题
进程的fork,是拷贝一个和原进程一摸一样的进程,其中的各种内存对象自然也会被拷贝。所以用来接收消息去fork进程的binder对象自然也会被拷贝。但是这个拷贝对于APP层有用吗?那自然是没用的,所以就凭白多占用了一块无用的内存区域。
说到这你自然想问,如果通过socket的方式,不也平白无故的多占用一块Socket内存区域吗?是的,确实是,但是fork出APP进程之后,APP进程会去主动的关闭掉这个socket,从而释放这块区域。相关代码在ZygoteConnection的processCommand方法中:
try {
if (pid == 0) {
// in child
zygoteServer.setForkChild();
zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
return handleChildProc(parsedArgs, childPipeFd,
parsedArgs.mStartChildZygote);
} else {
// In the parent. A pid < 0 indicates a failure and will be handled in
// handleParentProc.
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
handleParentProc(pid, serverPipeFd);
return null;
}
}

Context是个抽象类,通过类的结构可以知道:Activity、Service、Application都是Context的子类;从Android系统的角度来理解:Context是一个场景,描述的是一个应用程序环境的信息,即上下文,代表与操作系统的交互的一种过程。
Activity和Application都是Context的子类,他们维护的生命周期不一样。前者维护一个Acitivity的生命周期,所以其对应的Context也只能访问该activity内的各种资源。后者则是维护一个Application的生命周期。
看完以上分析答案显而易见:总Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context实例)
又可以分为页面绘制和 数据处理
常见内存优化问题:
内存泄露
程序在申请内存后,当该内存不需再使用但却无法被释放&归还给程序的现象。容易使程序内存溢出OOM,常见引发内存泄漏的问题有:

内存抖动
程序频繁创建对象,GC频繁回收内存
图片Bitmap相关
使用完毕后 释放图片资源 Bitmap.recycle()
根据分辨率适配 & 缩放图片
按需 选择合适的解码方式 不同的图片解码方式 对应的 内存占用大小 相差很大,具体如下

设置图片缓存


完整分析:https://www.jianshu.com/p/c2144e21deca
getMeasuredWidth 返回值的大小,取决于 setMeasuredDimension ,而 getWidth ,则取决于 layout 。
在大多数情况下,这两个方法返回的大小都是一样的。
但是在下面这样的情况下就不一样了:
在自定义的 ViewGroup 中,你在 XML 中对 子View 写死了宽度,但是这个 ViewGroup 的排版是固定的一行两个(左右各一个,宽度相同),那么这个 子View 的宽度,就会被强制设置成屏幕的一半,而不是你在 XML 中所设置的宽度。
OkHttp 是一套处理 HTTP 网络请求的开源框架
OkHttpclient client new OkHttpclient();
Requestrequest new Request.Builder()
.url(url)
.build();
client.newcall(request).enqueue(new Callback(){
@Override
public void onFailure(Call call,IOException e){}
@Override
public void onResponse(Call call,Response response) throws IOException{};
synchronized void enqueue(AsyncCall call){
if (runningAsyncCalls.size()<maxRequests &runningCallsForHost(call)<maxRequestsPerHost){
runningAsyncCalls.add(call);
executorService().execute(call);
}else{
readyAsyncCalls.add(call);
}
}
线程池执行了一个 AsyncCall,而 AsyncCall 实现了 Runnable 接口,因此整个操作会在一个子线程(非 UI 线程)中执行。
AsyncCall 中的 run
final class AsyncCall extends NamedRunnable {
......
@Override
protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
最后response是通过getResponseWithInterceptorChain()中获取的,内部是一个拦截器的调用链
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
七大拦截器
为什么需要连接池?
频繁的进行建立Sokcet连接和断开Socket是非常消耗网络资源和浪费时间的,所以HTTP中的keepalive连接对于降低延迟和提升速度有非常重要的作用。keepalive机制是什么呢?也就是可以在一次TCP连接中可以持续发送多份数据而不会断开连接。所以连接的多次使用,也就是复用就变得格外重要了,而复用连接就需要对连接进行管理,于是就有了连接池的概念。
OkHttp中使用ConectionPool实现连接池,默认支持5个并发KeepAlive,默认链路生命为5分钟。
怎么实现的?
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.isEligible(address, route)) {
streamAllocation.acquire(connection, true);
return connection;
}
}
return null;
}
public final class ConnectionPool {
void put(RealConnection connection) {
if (!cleanupRunning) {
//没有连接的时候调用
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
}
private final Runnable cleanupRunnable = new Runnable() {
@Override
public void run() {
while (true) {
//执行清理,并返回下次需要清理的时间。
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
//在timeout时间内释放锁
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
也就是当如果空闲连接maxIdleConnections超过5个或者keepalive时间大于5分钟,则将该连接清理掉。
long cleanup(long now) {
synchronized (this) {
//遍历连接
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
//检查连接是否是空闲状态,
//不是,则inUseConnectionCount + 1
//是 ,则idleConnectionCount + 1
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//如果超过keepAliveDurationNs或maxIdleConnections,
//从双端队列connections中移除
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) { //如果空闲连接次数>0,返回将要到期的时间
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// 连接依然在使用中,返回保持连接的周期5分钟
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
return 0;
}
怎样属于空闲连接?
public void acquire(RealConnection connection, boolean reportedAcquired) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
this.reportedAcquired = reportedAcquired;
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
在RealConnection中,有一个StreamAllocation虚引用列表allocations。每创建一个连接,就会把连接对应的StreamAllocationReference添加进该列表中,如果连接关闭以后就将该对象移除。
其实可以这样理解,在上层反复调用acquire和release函数,来增加或减少connection.allocations所维持的集合的大小,到最后如果size大于0,则代表RealConnection还在使用连接,如果size等于0,那就说明已经处于空闲状态了
同步请求
Okhttp 实现缓存功能是通过 CacheInterceptor 拦截器和 Cache 类来实现的。
在创建 OkHttpClient 对象时,可以指定一个 Cache 对象,用于存储缓存的响应。
CacheInterceptor 拦截器会根据请求和响应的 Cache-Control 首部来判断是否使用缓存或网络,以及更新缓存。
Cache-Control 首部是用于控制缓存行为的指令,它有以下几种常见的值:
• CacheInterceptor 拦截器的工作流程大致如下:
通过实现 Interceptor 接口,Interceptor 接口只有一个方法 intercept,该方法接收一个 Chain 参数,表示拦截器链。在 intercept 方法中,可以对请求或响应进行修改或转发,并且可以决定是否继续传递给下一个拦截器。在创建 OkHttpClient 对象时,可以通过 addInterceptor 或 addNetworkInterceptor 方法来添加自定义的应用拦截器或网络拦截器。
我有使用过或编写过自己的拦截器
Okhttp 管理连接池和线程池是通过 ConnectionPool 类和 Dispatcher 类来实现的。
ConnectionPool 类表示一个连接池,它维护了一个双端队列,用于存储空闲的连接。
它有一个清理线程,用于定期检查连接是否过期或超过最大空闲数,并将其移除。
Dispatcher 类表示一个调度器,它维护了三个双端队列,分别用于存储同步任务、异步任务和等待执行的异步任务。
它有一个线程池,用于执行异步任务,并根据最大并发数和主机数来调度任务执行。
Okhttp 复用和回收连接是通过 StreamAllocation 类和 RealConnection 类来实现的。
RealConnection 类表示一个真实的连接,它封装了一个 Socket 对象,用于建立 TCP 连接,并通过 Okio 获取输入流和输出流。
allocationLimit 属性,用于表示该连接可以分配给多少个流。
noNewStreams 属性,用于表示该连接是否可以创建新的流。
onStreamFinished 方法,用于在流结束时减少 allocationLimit 的值,并根据情况释放或回收连接。
Glide是Google推荐的一套快速高效的图片加载框架功能强大且使用方便。
优点
Glide生命周期,**with()**方法就是用于绑定生命周期,
Glide图片缓存

写入磁盘缓存
写入内存缓存
Glide 将图片写入 内存缓存的时机:图片加载完成后 、图片显示出来前
当 acquired 变量 >0 时,说明图片正在使用,即该图片缓存继续存放到activeResources弱引用缓存中
当 acquired变量 = 0,即说明图片已经不再被使用,就将该图片的缓存Key从 activeResources弱引用缓存中移除,并存放到LruResourceCache缓存中实现了:
Glide5大磁盘缓存策略
EventBus是一个开源库,是用于Android开发的 “事件发布—订阅总线”, 用来进行模块间通信、解藕。它可以使用很少的代码,来实现多组件之间的通信。

EventBus的优势
1,简化组件之间的通讯方式
2,对通信双方进行解藕
3,使用ThreadMode灵活切换工作线程
5,库比较小,不占内存
EentBus缺点
1、使用的时候有定义很多event类。
2、event在注册的时候会调用反射去遍历注册对象的方法在其中找出带有@subscriber标签的方法,性能不高。
3、需要自己注册和反注册,如果忘了反注册就会导致内存泄漏。
4、造成代码逻辑不清晰,出现bug不容易溯源
流程
EventBus.getDefault().register(this);
register()方法主要分为查找和注册两部分,
查找 的过程 findSubscriberMethods() 先从缓存中查找,如果找到则直接返回,否则去做下一步的查找过程,然后缓存查找到的集合然后调用findUsingInfo() 会在当前要注册的类以及其父类中查找订阅事件的方法,这里出现了一个FindState类,它是SubscriberMethodFinder的内部类,用来辅助查找订阅事件的方法,具体的查找过程在findUsingReflectionInSingleClass()方法,它主要通过反射查找订阅事件的方法
注册 subscribe()方法主要是得到了subscriptionsByEventType、typesBySubscriber两个 HashMap。我们在发送事件的时候要用到subscriptionsByEventType,完成事件的处理。当取消 EventBus 注册的时候要用到typesBySubscriber、subscriptionsByEventType,完成相关资源的释放。
取消注册
EventBus.getDefault().unregister(this);
public synchronized void unregister(Object subscriber) {
// 得到当前注册类对象 对应的 订阅事件方法的参数类型 的集合
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
// 遍历参数类型集合,释放之前缓存的当前类中的Subscription
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
// 删除以subscriber为key的键值对
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
// 得到当前参数类型对应的Subscription集合
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
// 遍历Subscription集合
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
// 如果当前subscription对象对应的注册类对象 和 要取消注册的注册类对象相同,则删除当前subscription对象
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
unregister()方法中,释放了typesBySubscriber、subscriptionsByEventType中缓存的资源。
发送事件
EventBus.getDefault().post("Hello World!")
public void post(Object event) {
// currentPostingThreadState是一个PostingThreadState类型的ThreadLocal
// PostingThreadState类保存了事件队列和线程模式等信息
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
// 将要发送的事件添加到事件队列
eventQueue.add(event);
// isPosting默认为false
if (!postingState.isPosting) {
// 是否为主线程
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
// 遍历事件队列
while (!eventQueue.isEmpty()) {
// 发送单个事件
// eventQueue.remove(0),从事件队列移除事件
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
所以 post() 方法先将发送的事件保存到事件队列,然后通过循环出队列,将事件交给postSingleEvent()处理:
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
// eventInheritance默认为true,表示是否向上查找事件的父类
if (eventInheritance) {
// 查找当前事件类型的Class,连同当前事件类型的Class保存到集合
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
// 遍历Class集合,继续处理事件
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
postSingleEvent()方法中,根据eventInheritance属性,决定是否向上遍历事件的父类型,然后用postSingleEventForEventType() 方法进一步处理事件:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// 获取事件类型对应的Subscription集合
subscriptions = subscriptionsByEventType.get(eventClass);
}
// 如果已订阅了对应类型的事件
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
// 记录事件
postingState.event = event;
// 记录对应的subscription
postingState.subscription = subscription;
boolean aborted = false;
try {
// 最终的事件处理
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
postSingleEventForEventType() 方法核心就是遍历发送的事件类型对应的Subscription集合,然后调用postToSubscription() 方法处理事件。
处理事件
postToSubscription() 内部会根据订阅事件方法的线程模式,间接或直接的以发送的事件为参数,通过反射执行订阅事件的方法。
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
// 判断订阅事件方法的线程模式
switch (subscription.subscriberMethod.threadMode) {
// 默认的线程模式,在那个线程发送事件就在那个线程处理事件
case POSTING:
invokeSubscriber(subscription, event);
break;
// 在主线程处理事件
case MAIN:
// 如果在主线程发送事件,则直接在主线程通过反射处理事件
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
// 如果是在子线程发送事件,则将事件入队列,通过Handler切换到主线程执行处理事件
// mainThreadPoster 不为空
mainThreadPoster.enqueue(subscription, event);
}
break;
// 无论在那个线程发送事件,都先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。
// mainThreadPoster 不为空
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
// 如果在主线程发送事件,则先将事件入队列,然后通过线程池依次处理事件
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
// 如果在子线程发送事件,则直接在发送事件的线程通过反射处理事件
invokeSubscriber(subscription, event);
}
break;
// 无论在那个线程发送事件,都将事件入队列,然后通过线程池处理。
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
postToSubscription() 方法就是根据订阅事件方法的线程模式、以及发送事件的线程来判断如何处理事件,至于处理方式主要有两种:
一种是在相应线程直接通过invokeSubscriber()方法,用反射来执行订阅事件的方法,这样发送出去的事件就被订阅者接收并做相应处理了:
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
另外一种是先将事件入队列(其实底层是一个List),然后做进一步处理,以**mainThreadPoster.enqueue(subscription, event)**为例简单的分析下,其中mainThreadPoster是HandlerPoster类的一个实例
public class HandlerPoster extends Handler implements Poster {
private final PendingPostQueue queue;
private boolean handlerActive;
......
public void enqueue(Subscription subscription, Object event) {
// 用subscription和event封装一个PendingPost对象
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
// 入队列
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
// 发送开始处理事件的消息,handleMessage()方法将被执行,完成从子线程到主线程的切换
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
// 死循环遍历队列
while (true) {
// 出队列
PendingPost pendingPost = queue.poll();
......
// 进一步处理pendingPost
eventBus.invokeSubscriber(pendingPost);
......
}
} finally {
handlerActive = rescheduled;
}
}
}
HandlerPoster的enqueue()方法主要就是将subscription、event对象封装成一个PendingPost对象,然后保存到队列里,之后通过Handler切换到主线程,在handleMessage()方法将中将PendingPost对象循环出队列,交给invokeSubscriber()方法进一步处理:
void invokeSubscriber(PendingPost pendingPost) {
Object event = pendingPost.event;
Subscription subscription = pendingPost.subscription;
// 释放pendingPost引用的资源
PendingPost.releasePendingPost(pendingPost);
if (subscription.active) {
// 用反射来执行订阅事件的方法
invokeSubscriber(subscription, event);
}
}
从pendingPost中取出之前保存的event、subscription,然后用反射来执行订阅事件的方法,又回到了第一种处理方式。所以mainThreadPoster.enqueue(subscription, event)的核心就是先将将事件入队列,然后通过Handler从子线程切换到主线程中去处理事件。



RxJava是一个响应式编程框架,可以非常方便的实现线程调度以及数据转换,规范代码,并且其用到了观察者模式和装饰者模式。与Retrofit框架配合较多。
有一个泛型类CreateEmitter类,包装了Observer。在Observable调用subscribe传入Observer的时候,会首先把Observer用CreateEmitter包裹起来,然后当有事件发生的时候,通过这个包裹了Observer的发射器CreateEmitter发射事件,最终调用到Observer里面的相应方法。
public class CreateEmitter<T> implements Emitter<T> {
final Observer<T> observer;
CreateEmitter(Observer<T> observer) {
this.observer = observer;
}
@Override
public void onNext(T t) {
observer.onNext(t);
}
@Override
public void onError(Throwable error) {
observer.onError(error);
}
@Override
public void onComplete() {
observer.onComplete();
}
}
public abstract class Observable<T> {
// 实现订阅的逻辑
public void subscribe(Observer<T> observer){
// 通过将传进来的observer包装成CreateEmitter,用于回调
CreateEmitter<T> emitter = new CreateEmitter<T>(observer);
// 回调订阅成功的方法
observer.onSubscribe(emitter);
// 回调发射器emitter
subscribe(emitter);
}
// 订阅成功后,进行回调
public abstract void subscribe(Emitter<T> emitter);
}
原因:使用RxJava发布一个订阅后,当页面被finish,此时订阅逻辑还未完成,如果没有及时取消订阅,就会导致Activity/Fragment无法被回收,从而引发内存泄漏。
解决方式:
因为 RxJava 最终能影响 ObservableOnSubscribe 这个匿名实现接口的运行环境的只能是最后一次运行的 subscribeOn() ,又因为 RxJava 订阅的时候是从下往上订阅,所以从上往下第一个 subscribeOn() 就是最后运行的
subscribeOn是指定上游的observable的线程,那么最终的上游observable发射数据时候的线程也会被紧挨着它的subscribeOn指定的线程有关,并且不设置observeOn指定下游的observer的线程,那么observer的线程跟最上游observable发射数据的线程保持一致。
schedulers 内部封装了各种 Scheduler。每一个 Scheduler 中都封装的有线程池,用于执行后台任务。
Scheduler 是所有调度器实现的抽象父类,子类可以通过复写其 scheduleDirect() 来自行决定如何调度被分配到的任务;同时通过复写其 createWorker() 返回的 Scheduler.Worker 实例来执行具体的某个任务。此处的任务指的是通过 Runnable 封装的可执行代码块。
Rxjava 的 subscribe 方法是由下游一步步向上游进行传递的。会调用上游的 subscribe,直到调用到事件源。
背压是发生在多线程中的问题,因为在单线程中,发送数据和接收数据都是在一个线程中,所以每次发送数据前得等到下游处理完数据才发送数据。
原因:
(1)同步订阅:即在同一线程中,被观察者每发一件事件,必须等到观察者接受处理后,才能发送下一个事件
(2)异步订阅:观察者和被观察者不在同一个线程中,即产生了被观察者发送事件的速度与观察者接受事件的速度不一致,大多数情况是被观察者发送事件的速度大于观察者接受事件的速度,这个就是产生背压的原因.
怎么解决:Flowable ( s.request(Long.MAX_VALUE)😉
背压策略:
RxJava1.0中背压策略,通过设置上游发送过来的数据的接收队列容量来达到背压。
Rxjava2.0
public enum BackpressureStrategy {
MISSION:上游没有限流,在下游里面发现队列满了,给下游发送onError的信息,该信息是io.reactivex.exceptions.MissingBackpressureException: Queue is full?!。
ERROR:在上游通过限流的形式给下游发送数据,在发现数据量到了128个的时候,会给下游发送onError的信息,该信息是create: could not emit value due to lack of requests。
DROP:也是在上游通过限流的形式给下游发送数据,在发现数据量到了128的时候,会等下游处理完这128个数据,等到处理完了,继续处理梳理,所以在等的过程中会有数据丢失的问题。
LATEST:虽然和DROP都是同样的丢数据,但是它两的做法是不一样的,LATEST通过只能放一个数据的容易来给下游发送数据,最开始丢数据基本是一样的,但是LATEST会保留最后一条数据,是因为最后处理数据的时候,容器里面还有一条数据。
BUFFER:这个跟RxJava2.0普通发送数据是一样的,它不支持背压,上游发送多少数据,下游会接收多少数据,直到发生OOM。
}
当观察者不设置接受事件的个数时,被观察者依然会发送事件,只不过该事件会存入缓存区(大小128,这个大小可以查看Flowable源码)当如果发送的事件个数大于缓存区大小,即就会抛出异常.
onComplete是用来控制不能发送数据的,也就是不能onNext了,包括onError也是不能再发送onNext数据了,该方法中也是调用了dispose方法。dispose方法改变了值,onNext时判断值不满足不再发送消息。
用过Hilt,Hilt Google开源的一个 Android 的依赖注入库,其实是基于 Dagger。Hilt 是专门为android打造的 ,可以使我们的代码 尽量的简化。Hilt 创建了一组标准的组件和作用域。这些组件会自动集成到 Android 程序中的生命周期中。在使用的时候可以指定使用的范围,事件作用在对应的生命周期当中。
作用域
一、优点
组件之间数据交互怎么实现?
ARouter是阿里巴巴的一个组件化开源的路由框架。
ARouter通过 APT 技术,生成 保存路径(路由path) 和 被注解(@Router)的组件类 的映射关系的类,利用这些保存了映射关系的类,Arouter根据用户的请求 postcard(明信片) 寻找到要跳转的 目标地址(class) ,使用 Intent跳转。原理很简单,框架的核心是利用APT生成的映射关系。
流程
通过APT技术,寻找到所有带有注解@Router的组件,将其注解值path和对应的Activity保存到一个map。
然后在初始化的时候将这个map加载到内存中,需要的时候直接get(path)就可以了。
这样,两个模块不用相互有任何直接的依赖,就可以进行转跳,模块与模块之间就相互独立了。
优化
利用APT、JavaPoet完成了代码生成的工作,对于一个大型项目,组件数量会很多,可能会有一两百或者更多,把这么多组件都放到这个Map里,显然会对内存造成很大的压力,因此,Arouter采用的方法就是“分组+按需加载”,分组还带来的另一个好处是便于管理。
解决步骤一:分组
Arouter在一层map之外,增加了一层map,WareHouse这个类,里面有两个静态Map:
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
static Map<String, RouteMeta> routes = new HashMap<>();
groupsIndex 保存了 group 名字到 IRouteGroup 类的映射,这一层映射就是Arouter新增的一层映射关系。
routes 保存了路径 path 到 RouteMeta 的映射,其中,RouteMeta是目标地址的包装。这一层映射关系跟我门自己方案里的map是一致的,我们路径跳转也是要用这一层来映射。
IRouteGroup和RouteMeta,后者很简单,就是对跳转目标的封装,我们后续称其为“目标”,其内包含了目标组件的信息,比如Activity的Class。那IRouteGroup是个什么东西?
public interface IRouteGroup {
/**
* Fill the atlas with routes in group.
*/
void loadInto(Map<String, RouteMeta> atlas);
}
Arouter通过apt技术,为每个组生成了一个以Arouter$$Group开头的类,这个类负责向atlas这个Hashmap中填充组内路由数据
IRouteGroup正如其名字,它就是一个能装载该组路由映射数据的类,其实有点像个工具类,为了方便后续讲解,我们姑且称上面这样一个实现了IRouteGroup的类叫做“组加载器”
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR +SUFFIX_ROOT)) {
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
}
}
public interface IRouteRoot {
/**
* Load routes to input
* @param routes input
*/
void loadInto(Map<String, Class<? extends IRouteGroup>> routes);
}
这个类实现了IRouteRoot,在loadInto方法中,他将组名和组对应的“组加载器”保存到了routes这个map中。也就是说,这个类将所有的“组加载器”给索引了下来,通过任意一个组名,可以找到对应的“组加载器”,我们再回到前面讲的初始化Arouter时候的方法中:
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
WareHouse中的groupsIndex保存的是组名到“组加载器”的映射关系
Arouter的分组设计:Arouter在原来path到目标的map外,加了一个新的map,该map保存了组名到“组加载器”的映射关系。其中“组加载器”是一个类,可以加载其组内的path到目标的映射关系。
解决步骤二:按需加载
Arouter.getInstance().build("main/hello").navigation;
最终走到
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
//请关注这一行
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
....//简化代码
}
//调用Intent跳转
return _navigation(context, postcard, requestCode, callback)
//从缓存里取路由信息
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
//如果为空,需要加载该组的路由
if (null == routeMeta) {
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
}
//如果不为空,走后续流程
else {
postcard.setDestination(routeMeta.getDestination());
...
}

APT(Annotation Processing Tool) 即注解处理器 可以在编译期扫描注解帮我们提前生成类
实现流程
1. 注解Lib中创建一个注解类
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface Print {
}
2. 扫描注解的Lib添加依赖
dependencies {
//自动注册,动态生成 META-INF/...文件
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
//依赖apt-annotation
implementation project(path: ':apt-annotation')
}
3. 创建扫描注解的类
添加 @AutoService (Processor.class) 注解
继承AbstractProcessor

4. 解析注解
真正解析注解的地方是在process方法
/**
* 扫描注解回调
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//拿到所有添加Print注解的成员变量
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Print.class);
for (Element element : elements) {
//拿到成员变量名
Name simpleName = element.getSimpleName();
//输出成员变量名
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,simpleName);
}
return false;
}
5. 生成类
用字符串拼出来一个工具类,然后用IO流写到本地就ok了,或者使用JavaPoet提供的方法生成类

总结
Java注解是一种元数据,它们可以在Java源代码中添加额外的信息,这些信息可以被编译器、工具和运行时环境使用。Java注解可以用来提供类、方法、字段或其他程序元素的附加信息,以及在编译时执行某些任务。
Java注解有三种类型:预定义注解、元注解和自定义注解。
预定义注解
Java中有三种内置注解,这些注解用来为编译器提供指令,它们是:
@Deprecated
这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量
可以用来标记类,方法,属性;
@Override
用来修饰对父类进行重写的方法。如果一个并非重写父类的方法使用这个注解,编译器将提示错误。
实际上在子类中重写父类或接口的方法,@Overide并不是必须的。但是还是建议使用这个注解,在某些情况下,假设你修改了父类的方法的名字,那么之前重写的子类方法将不再属于重写,如果没有@Overide,你将不会察觉到这个子类的方法。有了这个注解修饰,编译器则会提示你这些信息
@SuppressWarnings
用来抑制编译器生成警告信息
可以修饰的元素为类,方法,方法参数,属性,局部变量
当我们一个方法调用了弃用的方法或者进行不安全的类型转换,编译器会生成警告。我们可以为这个方法增加@SuppressWarnings注解,来抑制编译器生成警告。
注意:使用@SuppressWarnings注解,采用就近原则,比如一个方法出现警告,我们尽量使用@SuppressWarnings注解这个方法,而不是注解方法所在的类。虽然两个都能抑制编译器生成警告,但是范围越小越好,因为范围大了,不利于我们发现该类下其他方法的警告信息
元注解
java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解).
1.@Documented - 注解是否将包含在JavaDoc中
一个简单的Annotations标记注解,表示是否将注解信息添加在javadoc文档中
2.@Retention –什么时候使用该注解
Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间
3.@Target– 注解用于什么地方
默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括
4.@Inherited – 定义该注释和子类的关系
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解
自定义注解
自定义注解类编写的一些规则:
1. Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
2. 参数成员只能用public或默认(default)这两个访问权修饰
3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
5. 注解也可以没有定义成员, 不过这样注解就没啥用了
@Target(ElementType.FIELD)//可以用来修饰对象,属性
@Retention(RetentionPolicy.RUNTIME)//什么时候都不丢弃
@Documented//用于生成javadoc文档
public @interface FruitName {
String value() default "";
}
@Target(ElementType.FIELD)//可以用来修饰对象,属性
@Retention(RetentionPolicy.RUNTIME)//什么时候都不丢弃
@Documented//用于生成javadoc文档
public @interface FruitColor {
public enum Color{RED,GREEN,BLUE};
Color fc() default Color.GREEN;
}
创建一个工具类用来处理注解
package cn.sz.gl.test07;
import java.lang.reflect.Field;
public class AnnotationUtil {
public static Object findInfo(Class clazz) throws IllegalArgumentException, IllegalAccessException, InstantiationException{
Object obj = clazz.newInstance();
Field fs [] = clazz.getDeclaredFields();
for (int i = 0; i < fs.length; i++) {
//判断该属性上是否有FruitName类型的注解
if(fs[i].isAnnotationPresent(FruitName.class)){
FruitName fn = fs[i].getAnnotation(FruitName.class);
//为属性赋值
fs[i].setAccessible(true);
fs[i].set(obj, fn.value());
}
if(fs[i].isAnnotationPresent(FruitColor.class)){
FruitColor fc = fs[i].getAnnotation(FruitColor.class);
fs[i].setAccessible(true);
fs[i].set(obj, fc.fc().toString());
}
}
return obj;
}
}
所谓插件化,是实现动态化的一种具体的技术手段。
Shadow
通过运用AOP思想,利用字节码编辑工具,在编译期把插件中的所有Activity的父类都改成一个普通类,然后让壳子持有这个普通类型的父类去转调它就不用Hack任何系统实现了。虽然说是非常简单的事,实际上这样修改后还带来一些额外的问题需要解决,比如getActivity()方法返回的也不是Activity了。不过Shadow的实现中都解决了这些问题。

默认是public,如需私有只需要在变量名或者方法名前加_
例如
var user = "小王"; //是public
var _user= "小王"; //是private
一个点 .
是正常的对象访问
两个点 . .
意思是 「级联操作符」,为了方便配置而使用。「…」和「.」不同的是 调用「…」后返回的相当于是 this, 可以实现对一个对象的连续调用
Paint()
..color = thumbColor
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeWidth = tempWidth);
三个点 … 用来拼接集合,如List,Map等
class TestDemo {
TestDemo() {
var list2 = ['d', 'e', 'f'];
var list = ['a', 'b', 'c', ...list2];
// 打印结果:
// 这里组合后 list就变成[ 'a', 'b', 'c','d', 'e', 'f']
var map2 = {'a': 'a', 'b': 'b'};
var map = {...map2, 'c': 'c', 'd': 'd'};
// 打印结果:
// 这里组合后map就变成{'a': 'a', 'b': 'b','c': 'c', 'd': 'd'}
}
}
Dart是单线程模型
Dart在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是 “微任务队列” microtask queue,另一个叫做 “事件队列” event queue。微任务队列的执行优先级高于 事件队列。
Dart大致运行原理:先开启app执行入口函数main(),执行完成之后,消息机制启动,先是会按照先进先出的顺序逐个执行微任务队列中的任务microtask,事件任务eventtask 执行完毕后便会退出,但是,在事件任务执行的过程中也可以插入新的微任务和事件任务,在这种情况下,整个线程的执行过程便是一直在循环,不会退出,而Flutter中,主线程的执行过程正是如此,永不终止。

在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其它任务执行的。
Dart 中事件的执行顺序:Main > MicroTask > EventQueue
多线程语言如Java实现异步的方式是将耗时操作新开子线程去执行。Dart是单线程模型,没有多线程的概念,他是通过Future和Stream来实现异步的。
Future是异步函数,它可以在不阻塞当前任务的情况下执行一个任务,并在任务完成后获得相应的结果。
Future实际上是将其中的事件放入到了Event Queue事件队列中执行。
常与async一起使用
Async是Dart中的一个关键字,用于标记异步函数。async函数返回一个Future对象,并且可以使用await关键字来等待函数的执行结果。例如:
Future<String> getData(String url) async {
var response = await http.get(url);
return response.body;
}
Stream 流是一个异步的事件队列。分为单订阅流和广播订阅
单订阅流常用于数据的传递,广播流用于事件的分发。
StreamController 是流控制器的核心接口,包含了流控制器该有的大多数接口方法。其中
Flutter 与原生交互使用Platform Channel。Flutter定义了三种不同类型的Channel

main函数是程序执行的入口。
runApp是Flutter应用启动的入口。runApp中启动了Flutter FrameWork并且完成根widget树的渲染
在runApp中 初始化了WidgetsFlutterBinding这个类混入了七个BindingBase子类。同时调用WidgetsFlutterBinding 将Flutter Framework绑定到Flutter Engine上面
isolate 意思是隔离。它可以理解为 Dart 中的线程。isolate与线程的区别就是线程与线程之间是共享内存的,而 isolate 和 isolate 之间是不共享的。因此也不存在锁竞争问题,两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,它的资源开销低于线程。
每个 isolate 都拥有自己的事件循环及队列(MicroTask 和 Event)。这意味着在一个 isolate 中运行的代码与另外一个 isolate 不存在任何关联。
isolate之间的通信
由于isolate之间没有共享内存,他们之间的通信唯一方式只能是通过Port进行,而且Dart中的消息传递总是异步的。
Flutter 是Google开发的一款跨平台方案的开源框架。
优点
缺点
Widget会被inflate(填充)到Element,并由Element管理底层渲染树。Widget并不会直接管理状态及渲染,而是通过State这个对象来管理状态。Flutter创建Element的可见树,相对于Widget来说,是可变的,通常界面开发中,我们不用直接操作Element,而是由框架层实现内部逻辑。就如一个UI视图树中,可能包含有多个TextWidget(Widget被使用多次),但是放在内部视图树的视角,这些TextWidget都是填充到一个个独立的Element中。Element会持有renderObject和widget的实例。记住,Widget 只是一个配置,RenderObject 负责管理布局、绘制等操作。
在第一次创建 Widget 的时候,会对应创建一个 Element, 然后将该元素插入树中。如果之后 Widget 发生了变化,则将其与旧的 Widget 进行比较,并且相应地更新 Element。重要的是,Element 不会被重建,只是更新而已。
StatelessWidget: 一旦创建就不关心任何变化,在下次构建之前都不会改变。它们除了依赖于自身的配置信息(在父节点构建时提供)外不再依赖于任何其他信息。比如典型的Text、Row、Column、Container等,都是StatelessWidget。它的生命周期相当简单:初始化、通过build()渲染。
StatefulWidget: 在生命周期内,该类Widget所持有的数据可能会发生变化,这样的数据被称为State,这些拥有动态内部数据的Widget被称为StatefulWidget。比如复选框、Button等。State会与Context相关联,并且此关联是永久性的,State对象将永远不会改变其Context,即使可以在树结构周围移动,也仍将与该context相关联。当state与context关联时,state被视为已挂载。StatefulWidget由两部分组成,在初始化时必须要在createState()时初始化一个与之相关的State对象。
Flutter中的状态和前端React中的状态概念是一致的。React框架的核心思想是组件化,应用由组件搭建而成,组件最重要的概念就是状态,状态是一个组件的UI数据模型,是组件渲染时的数据依据。
Flutter的状态可以分为全局状态和局部状态两种。常用的状态管理有GetX和Provider
Flutter只关心向 GPU提供视图数据,GPU的 VSync信号同步到 UI线程,UI线程使用 Dart来构建抽象的视图结构,这份数据结构在 GPU线程进行图层合成,视图数据提供给 Skia引擎渲染为 GPU数据,这些数据通过 OpenGL或者 Vulkan提供给 GPU。

await for是不断获取stream流中的数据,然后执行循环体中的操作。它一般用在直到stream什么时候完成,并且必须等待传递完成之后才能使用,不然就会一直阻塞。
Stream<String> stream = new Stream<String>.fromIterable(['开心', '面试', '过', '了']);
main() async{
await for(String s in stream){
print(s);
}
Flutter框架自下而上分为Embedder、Engine和Framework三层。

var 和 dynamic 可以用来声明一个可以接受任何类型的变量
Flutter在Debug下使用JIT模式即Just in time(即时编译),在Release下使用AOT模式即Ahead of time(提前编译)
Flutter key子类包含 LocalKey 和 GlobalKey 。在Flutter中,Key是不能重复使用的,所以Key一般用来做唯一标识。组件在更新的时候,其状态的保存主要是通过判断组件的类型或者key值是否一致。
Widget可以通过Key来更新Element
在 Flutter 中有两种处理异步操作的方式 Future 和 Stream,Future 用于处理单个异步操作,Stream 用来处理连续的异步操作。
widget在flutter里基本是一些UI组件
有两种类型的widget,分别是statefulWidget 和 statelessWidget两种
@protected
void setstate(VoidCallback fn){
final dynamic result fn()as dynamic;
_element.markNeedsBuild();
}
void markNeedsBuild(){
if (!_active)
return;
if (dirty)
return;
_dirty true;
owner.scheduleBuildFor(this);
}
final List_dirtyElements [];
......
void scheduleBuildFor(Element element){
......
if (element._inDirtyList){
_dirtyElementsNeedsResorting true;
return;
}
if (!_scheduledFlushDirtyElements && onBuildScheduled !=null){
_scheduledFlushDirtyElements true;
//是一个回调方法
onBuildScheduled();
}
dirtyElements.add(element);
element._inDirtyList true;
}
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
assert(() {
_debugAddStackFilters();
return true;
}());
_buildOwner = BuildOwner();
buildOwner!.onBuildScheduled = _handleBuildScheduled;
platformDispatcher.onLocaleChanged = handleLocaleChanged;
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
assert(() {
FlutterErrorDetails.propertiesTransformers.add(debugTransformDebugCreator);
return true;
}());
platformMenuDelegate = DefaultPlatformMenuDelegate();
}
.....
void _handleBuildScheduled() {
....
ensureVisualUpdate();
}
void ensurevisualUpdate()
switch (schedulerPhase){
//没有正在处理的帧,可能正在执行的是WidgetsBinding.scheduleTask,
//scheduleMicrotask,Timer,事件handlers,或者其他回调等
case SchedulerPhase.idle:
//主要是清理和计划执行下一帧的工作
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();//清求新的贞渲染。
return;
//SchedulerBinding.handleBeginFrame过程,
处理动画状态更新
case SchedulerPhase.transientCallbacks:
//处理transientCal1 backs阶段触发的微任务(Microtasks)
case SchedulerPhase.midFrameMicrotasks:
//WidgetsBinding.drawFrame和SchedulerBinding.handleDrawFrame过程,
//bui1d/1 ayout/paint流水线江作
case SchedulerPhase.persistentCallbacks:
return;
}
void scheduleFrame()
if (_hasScheduledFrame!framesEnabled)
return;
//确保渲染的回调已经被注册
ensureFrameCallbacksRegistered();
//windowi调度帧
window.scheduleFrame();
_hasScheduledFrame true;
}
@protected
void ensureFrameCallbacksRegistered(){
/处理渲染前的任务
window.onBeginFrame ??=_handleBeginFrame;
//核心渲染流程
window.onDrawFrame ??_handleDrawFrame;}
void handleDrawFrame() {
Timeline.finishSync(); // end the "Animate" phase
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
} finally {
_schedulerPhase = SchedulerPhase.idle;
Timeline.finishSync(); // end the Frame
_currentFrameTimeStamp = null;
}
}
@override
void drawFrame() {
TimingsCallback firstFrameCallback;
if (_needToReportFirstFrame) {
firstFrameCallback = (List<FrameTiming> timings) {
if (!kReleaseMode) {
developer.Timeline.instantSync('Rasterized first useful frame');
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
}
SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
firstFrameCallback = null;
_firstFrameCompleter.complete();
};
// Callback is only invoked when [Window.render] is called. When
// [sendFramesToEngine] is set to false during the frame, it will not
// be called and we need to remove the callback (see below).
SchedulerBinding.instance.addTimingsCallback(firstFrameCallback);
}
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
} finally {
}
if (!kReleaseMode) {
if (_needToReportFirstFrame && sendFramesToEngine) {
developer.Timeline.instantSync('Widgets built first useful frame');
}
}
_needToReportFirstFrame = false;
if (firstFrameCallback != null && !sendFramesToEngine) {
SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
}
}
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
Timeline.startSync('Build', arguments: timelineWhitelistArguments);
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
_dirtyElements[index].rebuild();
} catch (e, stack) {
}finally {
for (final Element element in _dirtyElements) {
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
Timeline.finishSync();
}
}
}
_dirtyElements列表,先从深到浅排序,之后遍历,遍历的过程中执行rebuild方法,此时将所有标记为dirty的Element节点依次执行rebuild,preformRebuild,build,updateChild,update方法,执行界面更新。
这些build,update操作完成之后,后续会将需要绘制的RenderObject添加到需要layout的列表中,等待绘制渲染。
所有绘制完成之后将_dirtyElements列表清空,_inDirtyList标记位置为false。
10. 调用super.drawFrame(),这里就执行到了RendererBinding类中,开始渲染
void drawFrame() {
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
if (sendFramesToEngine) {
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true;
}
}
TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议) TCP/IP协议是指能够在多个不同网络间实现信息传输的协议簇。也称作网络通讯协议,对互联网中各部分进行通信的标准和方法进行了规定。使不同型号、不同厂家、运行不同操作系统的计算机之间通过TCP/IP协议栈实现相互间的通信。


主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。
IEEE 802.1A,IEEE802.2到IEEE802.11
应用程序数据在发送到物理网络上之前,将沿着协议栈从上往下依次传递。每层协议都在上层数据的基础上加上自己的头部信息(有时也包含尾部信息),以实现该层的功能,这个过程就称为封装(原始数据在系统中通过层层封装描述的过程)。
数据接收解析是个反过程,接收方从下往上层层解析数据。

IP层传输单位是IP分组,属于点到点的传输;TCP层传输单位是TCP段,属于端到端的传输
可分为TCP(有连接)和UDP(无连接)两种

面向有连接型 需要在收发主机间建立一条通讯线路在通信传输前后,专门进行建立和断开连接的处理,如果与对端之间无法通信,可避免发送无谓的数据。

面向无连接型



TCP进行拥塞控制的算法有四种,即慢开始、拥塞避免、快重传和快恢复。
慢开始: 当主机开始发送数据时,由于并不清楚网路的负荷情况,所以如果立即把大量数据字节注入到网络,那么就有可能引发网络发生拥塞。所以最好的方法是先探测一下,即由小到大逐渐增大发送窗口,由小到达逐渐增大拥塞窗口值。每经过一个传输轮次(transmission round),拥塞窗口cwnd就加倍 (1 -> 2 -> 4 -> 8 -> n)
为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量,sshthresh的用法如下。
当cwnd < ssthresh时,使用上述的慢开始算法。
当cwnd > ssthresh时,停止使用慢开始算法而改用拥塞避免算法。
当cwnd = ssthresh时,既可使用慢开始算法,也可使用拥塞避免算法。
让拥塞窗口cwnd缓慢的增大,每经过一个往返时间RTT就把发送方的拥塞窗口+1,即加法增大。
假设初始ssthresh为16,现在开始执行慢开始,
当到cwnd = 16时,改为拥塞避免算法,即加法增大
假设当cwnd = 24时,出现了网络拥塞,那么执行ssthresh乘法减小,即 ssthresh = cwnd / 2 = 12,
重新开始执行慢开始(cwnd = 1),然后cwnd = 12时开始执行拥塞避免。。。

为什么需要快重传 有时,个别报文段会在网络中丢失,但实际上网络并未发生拥塞,如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞,就导致了上图中重新开始了慢开始,将拥塞窗口cwnd又设置为1,因而降低了传输效率。
快重传执行流程
快重传算法规定,发送方只要一连收到3个重复确认,就知道接收方确实没有收到报文段M3,因而应该立即进行重传(即“快重传”),这样就不会出现超时 (如果没有快重传,只要发生超时就执行门限ssthresh / 2,然后慢开始) ,发送方也不就会误认为出现了网络拥塞。使用快重传可以使整个网络的吞吐量提高约20%。

快恢复执行过程
因此,在下图中的点④,发送方知道现在只是丢失了个别的报文段,于是不启动慢开始,而是执行快恢复算法
调整门限值ssthresh = cwnd / 2 = 16 / 2 = 8, 同时设置cwnd = ssthresh = 8,开始拥塞避免算法(注意是拥塞避免,非慢开始)。

建立连接时,ACK和SYN可以放在一个报文里来发送。
而关闭连接时,被动关闭方可能还需要发送一些数据后,再发送 FIN报文 表示同意现在可以关闭连接了。
所以它这里的 ACK报文 和 FIN报文 多数情况下都是分开发送的。
服务器的资源宝贵不能浪费 !
TCP 建立连接时,通过 三次握手 能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。
不使用「两次握手」和「四次握手」的原因:
三次握手 是为了保证双方都准备好了资源。
四次挥手 是为了保证双方把资源都释放掉了。
第二次挥手的目的是 被动关闭方 告诉 主动关闭方 ,我被动关闭方 已经收到你的发送包。
第三次挥手的目标是 被动关闭方 通知 主动关闭方,我被动关闭方 的数据也发送完了,不会再给你发数据了 。
因为,第二次的挥手 是 对第一次挥手响应,第三次是 被动关闭方 通知 主动关闭方 ,我已经没有消息发送了,
但是在第二次挥手和第三次挥手中间,被动关闭方 还是可以向 主动关闭方 发送数据的,所以第二次和第三次无法合并。
被动关闭方 调用了close之后才能发。
不会,即便服务器调用了close(),仍然会保留资源,保留到客户端发来第4次挥手的数据包到了为止。 什么时候收到,什么时候才释放资源。
无法保证最后发送的ACK报文会一定被对方收到,所以需要重发可能丢失的ACK报文。
关闭链接一段时间后可能会在相同的IP地址和端口建立新的连接,为了防止旧连接的重复分组在新连接已经终止后再现。2MSL足以让分组最多存活msl秒 被丢弃。
在 WebSocket 协议中,使用一系列帧传输数据。为避免混淆网络中间人(比如拦截代理),以及出于安全考虑,客户端必须对发送给服务端的所有帧进行掩码(Mask)处理。
基础帧协议定义了一种帧类型,包括操作码(Opcode)、有效载荷长度,以及 “扩展数据” 和 “应用数据” 的指定位置,它们一起定义“有效载荷数据”。一些位和操作码被保留,以供未来扩展协议。
