litchi litchi blog 2021-02-13T09:09:26.067Z / litchi Hexo String_StringBuilder_and_StringBuffer /2021/02/13/String-StringBuilder-and-StringBuffer/ 2021-02-13T09:05:46.000Z 2021-02-13T09:09:26.067Z <![CDATA[

  • String是最基本最常被用到,它是不可变的,每次字符串变化都会重新分配空间,少量字符串操作可以使用String。
  • StringBuilder是弥补String字符串不可变,它提供对字符串的操作,提供append、insert、delete和replace方法对字符串增删改查。如果是单线程存在大量字符串操作时使用StringBuilder。
  • StringBuffer进一步弥补StringBuffer,在StringBuilder的基础上增加线程安全能力。如果字符串存在多线程的大量操作,可以使用StringBuffer。
]]> <p><img src="peitu.jpg" alt=""></p> <ul> <li>String是最基本最常被用到,它是不可变的,每次字符串变化都会重新分配空间,少量字符串操作可以使用String。 </li> <li>StringBuilder是弥补String字符串不可变,它提供对字符串的操作,提供append、insert、delete和replace方法对字符串增删改查。如果是单线程存在大量字符串操作时使用StringBuilder。 </li> <li>StringBuffer进一步弥补StringBuffer,在StringBuilder的基础上增加线程安全能力。如果字符串存在多线程的大量操作,可以使用StringBuffer。</li> </ul> 策略模式 /2021/02/11/策略模式/ 2021-02-11T04:57:56.000Z 2021-02-12T03:01:59.508Z <![CDATA[

一个类的行为或者算法可以在运行时更改,策略模式改变对象的执行算法。属于行为型模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class StrategyPattern {

public static void main(String[] args) {

Context context = new Context();
// context.setStrategy(new AndStrategy());
context.setStrategy(new OrStrategy());
context.deploy(2,1);

}

//环境类,算法执行的上下文环境
static class Context {
private Strategy strategy;

public Context(Strategy strategy) {
this.strategy = strategy;
}

public Context() {
}

public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}

public void deploy(int a, int b) {
if (strategy != null) {
int result = strategy.deploy(a, b);
System.out.println("result:" + result);
}
}
}

//抽象策略类
interface Strategy {
int deploy(int a, int b);
}

//具体策略类
static class AndStrategy implements Strategy {

public int deploy(int a, int b) {
return a & b;
}
}
//具体策略类
static class OrStrategy implements Strategy {

public int deploy(int a, int b) {
return a | b;
}
}

}

策略模式解决的问题

1.可以防止硬编码,if_else构成的多种case的行为;
2.系统需要动态的在几种算法中选择一种;
3.不希望提供具体的算法逻辑。

优点:

1.算法可以灵活切换;
2.避免复杂的条件判断;
3.算法扩展性好。

优点:

1.具体策略类会越来越多;

实际应用场景:
三方应用的初始化,有友盟(区分线上线下版本内部会日志打印上报时机)、BlockCanary;
源码中RecyclerView或者LisView的Adapter,每个具体的列表就是一种策略,构建列表时使用具体的策略来完成展示。

感谢

https://www.runoob.com/design-pattern/strategy-pattern.html

]]>
<p><img src="peitu.jpg" alt=""></p> <p>一个类的行为或者算法可以在运行时更改,策略模式改变对象的执行算法。属于行为型模式。<br>
反射 /2021/02/10/反射/ 2021-02-10T12:41:08.000Z 2021-02-11T03:17:20.360Z <![CDATA[

反射就是在程序运行时动态加载类、方法或属性,在coding阶段是直接不知道对象是谁
普通场景创建一个类的过程是先判断类的Class对象是否加载到了内存中,Class对象已经加载到内存(字节码已经加载)就为实例对象分配内存,根据不同的垃圾收集器分配内存,GC使用复制算法或者标记整理算法内存规整场景下,只需要将指针向空闲的一边移动,这种分配方式称指针碰撞,另外一种内存存在碎片,碎片内存会保存在一个“空闲列表”中,从空闲列表中取出一块可容纳目标对象的内存区域来存储对象。如果Clas对象没有加载到内存中,就执行类的加载过程,经过加载、连接和初始化完成类的加载,再为实例对象分配内存。
反射通过Class对象创建实例对象,并获取类的属性和方法。
通过new对象,经过编译器安全校验,确认具体对象创建叫静态编译;反射这种方式叫做动态编译。

获取字节码Class对象方式:

1
2
//通过forName方法获得(带有包名的真实类路径)
Class<?> mClass = Class.forName("java.util.ArrayList");
1
2
//直接通过类.class
Class<?> mClass = ArrayList.class;
1
2
//通过对象.getClass()
Class<?> mClass = list.getClass();

获取构造函数:

1
2
3
4
5
//得到所有构造函数
Constructor<?>[] constructors = mClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
1
2
//获得特定构造函数
Constructor<ArrayList> constructor = mClass.getConstructor(null);

获取方法:

1
2
//得到共有的public方法 包括继承的方法
Method[] methods= mClass.getMethods();
1
2
//得到类所有方法 包括public private protect
Method[] methods = mClass.getDeclaredMethods();
1
2
3
4
5
6
7
8
9
10
11
//得到特定对象,并执行实例该方法
Method method = mClass.getMethod("add", Object.class);
if (method != null) {
try {
method.invoke(list, 1245);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}

获取属性:

1
2
3
4
5
//获取所有共有的public变量,包括继承的属性
Field[] fields = mClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
1
2
3
4
5
//获取所有申明的变量 public private protect
Field[] fields = mClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
1
2
//设置变量值
field.set(list,12l);

创建实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//通过反射得到的构造函数创建ArrayList实例,添加了两个元素ok和12
Constructor<?>[] constructors = mClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}

try {
ArrayList l = (ArrayList) constructors[1].newInstance();
l.add("ok");
l.add(12);
System.out.println(l.size());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}

反射忽略了权限检查、语法检查(上面ArrayList变量l可以添加ok字符串也可以添加整型数12),破坏了封装性。

反射的用途:

1.用在IOC容器,用来创建对象;
2.编译阶段跨模块无法调用的场景;
3.Gson中变量复制(不需要get set方法的原因)。

]]>
<p><img src="peitu.jpg" alt=""></p> <p><em>反射就是在程序运行时动态加载类、方法或属性,在coding阶段是直接不知道对象是谁</em><br>普通场景创建一个类的过程是先判断类的Class对象是否加载到了内存中,Class对象已经加载到内存(字节码已经加载)就为实例对象分配内存,根据不同的垃圾收集器分配内存,GC使用复制算法或者标记整理算法内存规整场景下,只需要将指针向空闲的一边移动,这种分配方式称指针碰撞,另外一种内存存在碎片,碎片内存会保存在一个“空闲列表”中,从空闲列表中取出一块可容纳目标对象的内存区域来存储对象。如果Clas对象没有加载到内存中,就执行类的加载过程,经过加载、连接和初始化完成类的加载,再为实例对象分配内存。<br>反射通过Class对象创建实例对象,并获取类的属性和方法。<br>通过new对象,经过编译器安全校验,确认具体对象创建叫静态编译;反射这种方式叫做动态编译。 </p>
泛型 /2021/02/08/泛型/ 2021-02-08T06:35:41.000Z 2021-02-13T07:48:57.866Z <![CDATA[

JAVA中泛型简单讲就是将类型参数化,可以用在类、接口和方法。

泛型类:

1
2
3
4
5
6
public class ClassA<T> {

public void print(T t) {
System.out.println("class is" + t.getClass());
}
}

泛型接口:

1
2
3
4
interface IA<T> {

T getThing();
}

泛型方法:

1
2
3
public <T> void print(T t) {
System.out.println("class is" + t.getClass());
}

泛型只在编译期有效,编译后生成的字节码中不包含泛型信息,也就是在编译结束后泛型被去除了。即泛型擦除。泛型被擦除后变成了什么呢?

1
2
3
ArrayList<Integer> listA = new ArrayList<>();
listA.add(12);
System.out.println("listA type is" + (listA.get(0).getClass()));

运行结果:

listA type isclass java.lang.Integer

listA编译后泛型转化成了实际对象类型Integer,也就是自动类型转换。

  • 有界通配符

MyClass<? extends Number> 有上限,须继承Number类;

MyClass<? super String>有下限,须是String类的父类,如Object

  • 静态方法不能使用类定义的泛型,因为静态方法调用时,类还没有初始化,且方法在栈中,类的实例化在堆区。

泛型带来的问题:

1.泛型会经过先类型检查再编译,类型检查针对的是引用;

1
2
3
4
5
6
7
ArrayList<String> list = new ArrayList<>();
list.add(12);//报错 编译不能通过
String s = list.get(0);//返回的是String类型

ArrayList list1 = new ArrayList<String>();
list1.add(12);//编译通过
Object ob1 = list1.get(0);//返回的是Object

2.引用传递;

1
2
3
4
ArrayList<String> list = new ArrayList<>();
t1(list);//编译不通过,类型检测ArrayList<String>与ArrayList<Object>数组结构不一样
private void t1(ArrayList<Object> l) {
}

泛型类型继承规则与普通类继承关系不一样的一点。
3.类型擦除与多态的冲突,如在父类中泛型方法,子类实现的对应方法泛型换作具体类型,在该过程中系统为我们生成了桥方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//创建接口IFanSheDuoTai,申明方法,方法参数类型是泛型T;
public interface IFanSheDuoTai<T> {

public void test(T t);

}

//实现类FanSheDuoTai 重写方法test
public class FanSheDuoTai implements IFanSheDuoTai<String> {

public static void main(String[] args) {

}

@Override
public void test(String s) {

}
}

反编译FanSheDuoTai.class

javap -p FanSheDuoTai.class

1
2
3
4
5
6
public class com.litchi.demo.FanSheDuoTai implements com.litchi.demo.IFanSheDuoTai<java.lang.String> {
public com.litchi.demo.FanSheDuoTai();
public static void main(java.lang.String[]);
public void test(java.lang.String);
public void test(java.lang.Object);
}

FanSheDuoTai反编译得到的方法可以看到有test(String)和test(Object),test(Object)就是在泛型编译完成后的桥方法

4.泛型不能是基本数据类型;

5.Gson在运行时使用常量池中泛型标签信息解析数据到泛型;

1
Type componentType = ((GenericArrayType)type).getGenericComponentType();

6.静态方法不能使用类定义的泛型,因为在静态方法调用时,类对象可能还没有实例化,类定义泛型是在类实例化时执行,所在的内存区域也是不一样的。

感谢

https://www.cnblogs.com/huansky/p/8043149.html
https://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java-class-files

]]>
<p>JAVA中泛型简单讲就是将类型参数化,可以用在类、接口和方法。 </p> <p>泛型类: </p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">
控制反转和依赖注入 /2021/02/07/控制反转和依赖注入/ 2021-02-07T13:29:42.000Z 2021-02-07T13:55:04.995Z <![CDATA[

控制反转(Inversion Of Control):
控制和反转,连个问题谁控制谁?谁反转了谁?
对象控制它内部创建的对象,反转是创建对象的方式被反转,传统是对象直接new一个需要对象,这种形式可以叫正转或不转,控制反转就是将依赖对象的创建交给三方完成,而不需要直接new所需对象。依赖注入就是IOC的一种具体表现。
依赖注入:
对象A依赖对象B,对象B不会在对象A中直接创建,而是将对象B引用传给对象A,这种形式就叫依赖注入。也就是对象A对对象B的依赖,通过注入形式获得持有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class A{

IB b;

public A(IB b) {
}

public void doA(){
if (b != null) {
b.doThing();
}
}
}

class B implements IB{

@Override
public void doThing() {
System.out.println("B do some thing");
}
}

interface IB{
void doThing();
}

依赖注入在实际开发中非常常见,比如设置事件监听、网络回调等,策略模式也是一种依赖注入的思想。

]]>
<p>控制反转(Inversion Of Control):<br>控制和反转,连个问题谁控制谁?谁反转了谁?<br>对象控制它内部创建的对象,反转是创建对象的方式被反转,传统是对象直接new一个需要对象,这种形式可以叫正转或不转,控制反转就是将依赖对象的创建交给三方完成,而不需
一问一答之okhttp篇 /2021/02/05/一问一答之okhttp篇/ 2021-02-05T13:18:01.000Z 2021-02-07T12:48:08.426Z <![CDATA[

  • 简单说下OkHttp。

    OkHttp默认支持
    1共享一个scoket完成相同主机的请求,支持Http2.0,头部压缩、连接复用、服务端push,同一主机所有请求共用一个scoket连接;
    2.连接池,减少请求延迟(https http协议下)
    3.透明gzip压缩
    4.响应缓冲,减少不必要的网络请求。

网络请求调用流程大致是:
首先创建request对象,设置请求地址,方法,header信息,接着OkHttp调用newCall方法传入创建好的request对象,newCall返回RealCall,RealCall执行enqueue执行异步请求,同时传入回调, 请求完成拿到数据,同步请求执行execute方法并返回response对象。

  • OKHttp中线程池是怎么设置的?
    Dispatcher.ececuteService()创建了线程池。
1
2
3
4
5
6
7
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}

ThreadPoolExecutor参数含义:

corePoolSize:0 –核心线程数 即一直保持在线程池中的线程数,即使它们闲置也不会被回收,除非设置了核心线程数超时时间allowCoreThreadTimeOut,设置allowCoreThreadTimeOut后核心线程在闲置时达到超时时间就会被回收。
maximumPoolSize:Integer.MAX_VALUE–线程数池中允许的最大线程数;
keepAliveTime:60–线程池中非核心线程闲置等待任务时的超时时间;
unit:TimeUnit.SECONDS– keepAliveTime的时间单位;
workQueue:AsynchronousQueue –工作队列是任务在执行前的容器,任务通过execute方法提交;
threadFactory:Util.threadFactory(“OkHttp Dispatcher”, false) –该工厂在创建线程时被使用;

所以OkHttp中的线程池创建时没有核心线程,不限制线程的数量,线程在闲置60s后会被回收,那么是不是说线程池中的线程并行是不做限制的增长,显然不是,Dispatcher还维护了异步请求的两个队列(runningAsyncCalls和readyAsyncCalls),在向runningAsyncCalls队列中添加call时最大请求数是64,这样保证同一时刻最多只有64个请求正在执行,使线程池中线程的增量是可控的。另外还可以自定义线程池配置设置给Dispatcher。

  • Dispatcher(分发器)主要负责什么?
    Dispatcher内部维护一个线程池,和三个请求队列(runningAsyncCalls、readyAsyncCalls and runningSyncCalls),负责请求的执行和管理。

  • 拦截器(Interceptor)主要负责什么?拦截器的好处?
    拦截器是OkHttp中最重要的部分,它负责网络重试、重定向、网络监控、缓冲等功能。拦截器的调用流程是从getResponseWithInterceptorChain方法开始,getResponseWithInterceptorChain方法中创建ApplicatinInterceptorChain,接着调用chain.proceed(request),proceed方法中继续创建ApplicatipnInterceptorChain,将chain传入拦截器interceptor方法中,该方法中会执行chain.proceed(),接着每个拦截器依次都会被调用到,最后在CallServerIntercptor拦截器中返回请求服务器得到的response对象。拦截器调用过程中我们可以自定义应用类拦截器和网络类拦截器,应用类拦截器最先被执行,网络类拦截器会在建立连接后被执行,也就是ConnectInterceptor拦截器被调用之后。 拦截器的这种设计使复杂的网络请求操作分层完成,每一层的拦截器完成自身对请求和响应的任务(各司其职)。

  • OkHttp内置哪些拦截器,具体负责哪个任务?
    内置拦截器包括:
    RetryAndFollowupInterceptor:负责网络重试和重定向;
    BridgeInterceptor:负责将应用层码转化为网络层码,将header中缺失的网络层特有header补充完整(例如User-Agent,keep-alive);
    CacheInterceptor:管理缓冲,缓冲读取和更新;
    ConnectInterceptor:建立网络连接,是请求服务的基本;
    CallServerInterceptor:真正发送请求和获取响应数据(将请求写入IO流中,从IO流中读取响应);

  • 实际业务中用自定义拦截器可以做什么?
    自定义应用类拦截器:
    添加请求header 签名(Id 参数 md5) 版本 ;
    验签失败重试(返回内容中会有重试策略);
    自定义日志打印。

自定义网络层拦截器:
如果需要打印网络重试、重定向等信息可以使用网络

  • OkHttp中用到了哪些设计模式?
    1.建造者模式(Builder模式)适合构造函数入参多而杂的情况下使用:
    说明:使用一个Builder类一步步构建最终的对象。目标对象是由多个“配置”组合构造出来的。
    OkHttpClient、Request、Response、Headers、HttpUrl都是用了构造者模式。
    2.工厂模式:
    说明:创建目标对象不需要对对象使用者暴露创建逻辑,对外屏蔽对象具体实现,使用者只需关注其接口。
    CacheStrategy.Factory、ThreadFactory、EventListener.Factory(调用create()根据call创建EventListener)。
    3.外观模式:
    说明:统一一个类对调用者提供一系列接口,隐藏内部系统的复杂性。
    OkHttpClient类 调用者的调用接口都是通过OkHttpClient完成。
    4.责任链模式:
    说明:一个请求需要经过多个处理者进行责任处理才能完成。没有处理者都有一个特点:前一个处理者对象会持有下一个处理者对象的引用形成一条链,请求发生时,沿着这条链传递。
    拦截器链。
    5.单例模式:
    说明:自己创建自己的唯一实例。
    OkHttpClient在项目中使用需要用单例。

  • OkHttp在项目中做了哪些封装?
    OkHttpClient做了单例

感谢:
https://www.jianshu.com/p/fa0dcbfe05cd
https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html

]]>
自解疑
OkHttp3 /2021/02/01/OkHttp3/ 2021-02-01T04:07:22.000Z 2021-02-07T12:56:37.202Z <![CDATA[

本文主要以源码形式解读OkHttp内部实现,源码基于okhttp:3.10.0。

同步请求

异步请求的例子

先看一个异步请求的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//(1) builder模式配置参数构建request对象
Request request = new Request.Builder()
.url("http://baidu.com?key=values")
.get()
.build();
//(2)builder构建OkHttpClient对象
new OkHttpClient.Builder().build()
.newCall(request)//(3)request入参返回RealCall
.enqueue(new okhttp3.Callback() {//(4)请求回调
@Override
public void onFailure(Call call, IOException e) {
System.out.println(e.getMessage());
}

@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println("thread:"+Thread.currentThread());
if (response.isSuccessful()) {
ResponseBody responseBody = response.body();
data.setText(responseBody.string());
}

}
});

上面是一个OkHttp异步请求的代码,先构建一个Request对象设置请求地址、请求方式、header以及非GET请求还可设置body,然后创建OkHttpClient对象调用newCall设置request对象得到RealCall,RealCall调用enqueue发起异步请求并设置请求回调完成了一个简单的异步请求,OkHttpClient在实际开发中需要单例,原因会在后面的内容中有答案。

(1)接下来先看下Request内部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself
* immutable.
*/
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;

private volatile CacheControl cacheControl; // Lazily initialized.

Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}
/**
* Attaches {@code tag} to the request. It can be used later to cancel the request. If the tag
* is unspecified or null, the request is canceled by using the request itself as the tag.
*/
public Builder tag(Object tag) {
this.tag = tag;
return this;
}

public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
}
}

Request包含请求的参数url请求地址、method请求方法、header请求头数据、请求body以及tag标签。

(2)OkHttpClient主要暴露给外部调用,OkHttpClient对象的创建也通过builder模式,这里主要关注它的构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;//(1)分发器
this.proxy = builder.proxy;//(2)代理类
this.protocols = builder.protocols;//(3)协议类
this.connectionSpecs = builder.connectionSpecs;//(4)连接规模 确定TLS版本和密码套件
this.interceptors = Util.immutableList(builder.interceptors);//(5)自定义应用拦截器
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);//(6)自定义网络拦截器
this.eventListenerFactory = builder.eventListenerFactory;//(7)事件监听工厂
this.proxySelector = builder.proxySelector;//(8)代理选择器
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;//(9)缓冲类
this.internalCache = builder.internalCache;//(10)
this.socketFactory = builder.socketFactory;//(11)socket工厂

boolean isTLS = false;
for (ConnectionSpec spec : connectionSpecs) {
isTLS = isTLS || spec.isTls();
}

if (builder.sslSocketFactory != null || !isTLS) {
this.sslSocketFactory = builder.sslSocketFactory;
this.certificateChainCleaner = builder.certificateChainCleaner;
} else {
X509TrustManager trustManager = systemDefaultTrustManager();
this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
}

this.hostnameVerifier = builder.hostnameVerifier;
this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
certificateChainCleaner);
this.proxyAuthenticator = builder.proxyAuthenticator;
this.authenticator = builder.authenticator;
this.connectionPool = builder.connectionPool;//(12)连接池
this.dns = builder.dns;
this.followSslRedirects = builder.followSslRedirects;
this.followRedirects = builder.followRedirects;
this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.writeTimeout = builder.writeTimeout;
}

(12)OkHttpClient中创建了连接池,还维护了线程池(dispatcher中)和响应缓冲,所以在使用过程中要用单例。

(3)接着调用了OkHttpClient.newCall(req):

1
2
3
4
5
6
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}

newCall方法内部调用RealCall.newRealCall方法并返回Call对象:

1
2
3
4
5
6
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);//(1)
call.eventListener = client.eventListenerFactory().create(call);//(2)
return call;
}

(1)创建RealCall对象;RealCall对象持有OkHttpClient和Request,构造方法中还创建了重试/重定向拦截器RetryAndFollowInterceptor;
(2)从OkHttpClient得到evenListener对象,

(4)调用异步请求RealCall.enqueue方法:

1
2
3
4
5
6
7
8
9
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");(1
executed = true;
}
captureCallStackTrace();(2
eventListener.callStart(this);(3
client.dispatcher().enqueue(new AsyncCall(responseCallback));//(4)
}

(1)如果call已经被执行,抛出异常;
(2) 捕获RealCall类的栈轨迹;
(3)触发监听方法callStart(),表示请求开始;
(4)执行dispatcher分发器enqueue方法,创建了AsyncCall类,AsyncCall传入responseCallback。

下面查看dispatcher的enqueue方法:

1
2
3
4
5
6
7
8
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {//(1)
runningAsyncCalls.add(call);//(2)
executorService().execute(call);//(3)
} else {
readyAsyncCalls.add(call);//(4)
}
}

(1)把call添加到正在运行的队列的判断依据:如果正在执行的异步请求数小于最大请求数(默认64),并且同一个主机执行的异步请求小于单个主机运行的最大请求数(默认5)否则添加到准备队列;
(2)根据(1)把call添加到正在运行的队列;
(3)将call交线程池执行;
(4)不满足(1)把call添加到准备队列。
AsyncCall是Runnable实现类,execute方法完成请求和返回的执行。
AsyncCall. execute():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();//(1)
if (retryAndFollowUpInterceptor.isCanceled()) {//(2)
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);//(3)
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);//(4)
responseCallback.onFailure(RealCall.this, e);//(5)
}
} finally {
client.dispatcher().finished(this);//(6)
}
}

(1)getResponseWithInterceptorChain方法得到response对象,getResponseWithInterceptorChain是核心实现,后边专门展开说明;
(2)请求如果取消返回,调用responseCallback.onFailure通知处理请求失败;
(3)否则正常请求返回,调用responseCallback.onResponse返回response对象,调用方就可以拿到请求的数据返回,做具体业务处理;
(4)(5)回调执行eventListener.callFailed和responseCallback.onFailure;
(6)dispatcher执行finished方法,finish内部会调用promoteCalls方法从readyAsyncCalls队列中取出call 添加到runningAsyncCalls中,executorService().execute(call)加入线程池中执行call。添加到runningAsyncCalls中的条件是小于运行runningAsyncCalls最大call数并且同一主机call数小于maxRequestsPerHost(即同一主机最大请求数)。

  • 接下来分析核心方法getResponseWithInterceptorChain():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());//(1)
interceptors.add(retryAndFollowUpInterceptor);//(2)
interceptors.add(new BridgeInterceptor(client.cookieJar()));//(3)
interceptors.add(new CacheInterceptor(client.internalCache()));//(4)
interceptors.add(new ConnectInterceptor(client));//(5)
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());//(6)
}
interceptors.add(new CallServerInterceptor(forWebSocket));//(7)

Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);//(8)
}

(1)构建全部拦截器list,先添加自定义应用层拦截器;
(2)添加重试/重定向拦截器;
(3)添加桥接拦截器;
(4)添加缓冲拦截器;
(5)添加连接拦截器;
(6)如果不是websocket,添加自定义网络拦截器;
(7)添加请求服务拦截器;
(8)传入拦截器list,请求,call对象,事件监听,连接超时时间以及读写超时时间生成Interceptor.Chain链对象,执行chain.proceed(originalRequest)。

chain.proceed(originalRequest):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();

calls++;

// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}

// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}

// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);//(1)

// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}

// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}

if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}

return response;
}

(1)创建下一个RealInterceptorChain,将chain传入interceptor.intercept方法,intercept中会执行nextchain.proceed方法,然后再继续创建下一个RealInterceptorChain,intercept再执行下一个nextchain.proceed方法,这样循环调用所有拦截器,到最后一个拦截器CallServerInterceptor停止遍历,返回response,遍历循环流程如下图:

接下来阅读自带的拦截器代码:

  • RetryAndFollowUpInterceptor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();

StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);//(1)
this.streamAllocation = streamAllocation;

int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();//(2)
throw new IOException("Canceled");
}

Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);//(3)
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();//(4)
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;//(5)
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {//(6)
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}

// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {//(7)
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}

Request followUp = followUpRequest(response, streamAllocation.route());//(8)

if (followUp == null) {//(9)
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}

closeQuietly(response.body());//(10)

if (++followUpCount > MAX_FOLLOW_UPS) {//(11)
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}

if (followUp.body() instanceof UnrepeatableRequestBody) {//(12)
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}

if (!sameConnection(response, followUp.url())) {//(13)
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {//(14)
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}

request = followUp;//(15)
priorResponse = response;//(16)
}
}

(1)StreamAllocation用来协调连接(Connections)、流(Screams)和请求队列(Calls);
(2)如果请求取消,调用screamAllocation.release()。release方法会关闭socket,并回调 eventListener.connectionReleased。
(3)执行realChain.proceed方法,proceed内部会创建下一个chain,再传入下一个拦截器,拦截器intercept继续执行负责的工作,并调用chain.proceed()。
(4)如果realChain.proceed方法抛出RouteException,那么调用recover方法,recover方法返回false则不会重试连接,抛出IOException异常,异常会在Call.execute方法中捕获执行eventListener.callFailed和onFailure方法,返回false的条件如下:

1.应用层禁止重试 ;
2.定义了不可重复发送的请求body ;
3.捕获的异常严重等级属于致命 ;
4.没有更多的路由可重意重试;

如果上述的四种场景,请求会被发起重试。

(5)IOException,同样调用recover方法,按照(4)中逻辑判断是否重连;
(6)如果抛出没有catch的异常则执行StreamAllocation.screamFailed()和StreamAllocation.screamFailed;
(7)priorResponse是先前得到的响应数据,如果已经先前响应不为空,response会结合先前响应;
(8)根据响应码确认请求是否需要重定向,返回null表示不需要;
(9)不需要重定向就streamAllocation.release()释放连接并返回response,否则执行下面逻辑;
(10)释放response.body对象;
(11)当前重定向数大于最大可重定向数,则释放连接,抛出异常;
(12)请求不允许重复连接,则释放连接,抛出异常;
(13)检查是否是相同的连接,不是就释放当前连接,重新创建ScreamAllocation;
(14)codec为空抛出异常;
(15)重定向request赋值request,准备执行while循环;
(16)保存当前的response。

  • BridgeInterceptor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();

RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());//(1)
}

long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}

if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}

if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}

// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}

List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}

if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}

Response networkResponse = chain.proceed(requestBuilder.build());

HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);

if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}

return responseBuilder.build();
}

桥接拦截器主要功能:
1.将应用码转为网络码;
2.用户请求转为网络请求

  • CacheInterceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;

long now = System.currentTimeMillis();

//(1)
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;

if (cache != null) {
cache.trackResponse(strategy);
}

if (cacheCandidate != null && cacheResponse == null) {//(2)
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}

// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {//(3)
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}

// If we don't need the network, we're done.
if (networkRequest == null) {//(4)
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}

Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());//(5)
}
}

// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {//(6)
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();

// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}

//(7)
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();

if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);//(8)
return cacheWritingResponse(cacheRequest, response);
}

if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}

return response;
}

(1)获取缓冲策略;
(2)缓冲策略不为空,缓冲响应为空时,关闭缓冲策略;
(3)网络被禁止,缓冲不存在时,返回失败;
(4)不需要网络,返回缓冲响应,缓冲生效;
(5)执行chain.proceed方法抛出异常时,关闭缓冲;
(6)有缓冲时根据条件使用缓冲响应;
(7)使用网络响应;
(8)给予本请求缓冲(添加到缓冲中);

  • ConnectInterceptor
1
2
3
4
5
6
7
8
9
10
11
12
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();

// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();//(1)

return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

(1)获得RealConnection对象,调用下一个chain.proceed。

  • CallServerInterceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
 @Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();

long sentRequestMillis = System.currentTimeMillis();

realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);//(1)
realChain.eventListener().requestHeadersEnd(realChain.call(), request);

Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}

if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

request.body().writeTo(bufferedRequestBody);//(2)
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
streamAllocation.noNewStreams();
}
}

httpCodec.finishRequest();

if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);//(3)
}

Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();

int code = response.code();
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
responseBuilder = httpCodec.readResponseHeaders(false);

response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();

code = response.code();
}

realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);

//(4)
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}

if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}

if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}

return response;
}

(1)写入请求头数据;
(2)写入请求body数据;
(3)读取响应头数据;
(4)读取响应body数据。

完整异步请求调用流程:

]]>
源码
Android组件化 /2021/01/30/Android组件化/ 2021-01-30T11:32:57.000Z 2021-02-01T05:50:03.754Z <![CDATA[

Android应用达到一定规模后业务会越来越多,所有业务模块在同一module下耦合性会越来越强,这种强耦合带来开发维护成本增加,开发调试效率也会变低,项目组件化就势在必行。

组件化带来的优点:

1.项目模块清晰,组件向内高聚合,对外低耦合;
2.项目成员可以专注于具体组件内业务逻辑,降低开发成本;
3.可以独立调试,提升开发调试效率;
4.可以产出全局功能组件,服务于整个团队,快速响应新项目,达到功能重用。

要实现组件化需要解决问题:

1.组件间页面如何跳转?
2.组件间如何实现功能调用?
3.如何独立组件或随意组合组件进行调试?
4.如何在各个组件间获取Application实例?
5.如何实现组件间代码隔离?

解决方式

针对以上五点问题找到以下五个解决方式:
1.利用路由框架实现页面也跳转,如ARoute,或者自行定义的路有跳转协议;
2.利用依赖注入实现组件间通信,调用方利用接口申明实现调用;
3.Android gradle提供两种插件,com.android.application表示App Module,com.android.library表示库Module,通过在gradle.properties中设置参数控制Module的插件类型制定App module,同时配置module的Application和manifest文件,这样就可以独立运行需要调整的module。
4.设计module_common组件创建BaseApplication,App壳module工程Application继承至BaseApplication,其他组件依赖module_common这样就拿到了Application实例。
5.组件之间不存在相互依赖,不能直接调用,它们都依赖到App壳module,这样就完成了组件间的隔离。

组件化框架结构:

]]>
组件化实战
Handler /2021/01/27/Handler/ 2021-01-27T13:34:12.000Z 2021-01-30T05:53:34.485Z <![CDATA[


本篇分为三个部分记录Handler消息机制,第一部分逐一对“四件套”源码(基于Android-29)进行解读;第二部分根据源码总结它们之间的关联;第三部分对Handler涉及到的问题进行解答。

四件套

Message-消息

Message的作用是消息载体本身,Message类内部主要是关键参数和创建方法。官方推荐使用Mesage.obtain()或者Handler.obtainMessage()来创建Message对象,利用缓冲中Message,避免了重复创建对象。这两个方法的内法调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
* creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
* If you don't want that facility, just call Message.obtain() instead.
*/
@NonNull
public final Message obtainMessage()
{
return Message.obtain(this);
}

//Handler.obtainMessage()内部又调用了Message.obtain(handler)
/**
* Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
* @param h Handler to assign to the returned Message object's <em>target</em> member.
* @return A Message object from the global pool.
*/
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;

return m;
}

Handler.obtainMessage()其实是调用到了Message中的obtain()区别只是入参handler重新给message的target赋值,所以只需要关注Message.obtain(),sPool以链表形式存储了缓冲的Message对象,缓冲Message的链表不为空时就从尾部取出一个Message,为空直接创建。至于这里sPool中的Message是在什么时机缓冲起来,它其实是在looper的loop方法处理Message后调用msg.recycleUnchecked()加入到sPool缓冲池。

接下来说明Message中几个关键变量。
what是Message的唯一标示,来区分发送的Message,what值在不同handler是不需要考虑code冲突。

1
2
3
4
5
6
7
/**
* User-defined message code so that the recipient can identify
* what this message is about. Each {@link Handler} has its own name-space
* for message codes, so you do not need to worry about yours conflicting
* with other handlers.
*/
public int what;

arg1和arg2用来传递数据是int类型的数据,obj是Object类型,可以传递所有继承Object的数据类型,如果Message用在跨进程通信时obj需要进行序列化;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg1;

/**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg2;

/**
* An arbitrary object to send to the recipient. When using
* {@link Messenger} to send the message across processes this can only
* be non-null if it contains a Parcelable of a framework class (not one
* implemented by the application). For other data transfer use
* {@link #setData}.
*
* <p>Note that Parcelable objects here are not supported prior to
* the {@link android.os.Build.VERSION_CODES#FROYO} release.
*/
public Object obj;

when字段表示发送消息的时间字段,基准时间是SystemClock.uptimeMillis(),如果设置了发送延时时间when的值就是基准时间加延时时间;

1
2
3
4
5
6
/**
* The targeted delivery time of this message. The time-base is
* {@link SystemClock#uptimeMillis}.
* @hide Only for use within the tests.
*/
public long when;

data类型同样用来传递数据,data是Bundle类型,可以像Activity间传递消息一样设置key-values键值对交换数据;

1
Bundle data;

target是handler的引用, target的主要作用是在Looper的loop方法中用来分发Message;

1
Runnable callback;

Message还可以设置callback参数,在消息被Looper处理时调用Message的Runnable.run(),用在Message需要一一对应不同的Runnable。

MessageQueue-消息队列维护Message链表

MessageQueue是以链表形式存储Message对象,enqueueMessage()插入对象并返回操作结果。next()主要作用是从Queue读取Message并从链表中移除,postSyncBarrier()用于在Queue中添加同步屏障。

MessageQueue.enqueueMessage() :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}

synchronized (this) {
if (mQuitting) {//(1)
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}

msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}

// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

(1)Looper调用quit()后停止Message插入。
接下来的执行流程主要是链表的插入操作还有是否需要唤醒Looper轮询,判断依据是mBlocked(阻塞)和p.isAsynchronous()两个因素。

MessageQueue.next():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {(1)
return null;
}

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

nativePollOnce(ptr, nextPollTimeoutMillis);//(2)

synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {//(3)
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;//(4)
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}

// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}

if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}

//(5)
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}

if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}

// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;

// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}

(1)Looper调用quit()后停止执行。
(2)nativePollOnce()用于“等待”消息,知道下一条Message可用为止。
(3)这里是一种同步屏障机制,target==null作为同步屏障开启的标志循环找到一个异步消息,下面的操作中优先执行异步消息。同步屏障具体深入会在后面部分说明。
(4)链表中删除目标Message并返回。
(5)MessageQueue中没有Message时执行Idlehandler(闲时机制),Idlehandler.queueIdle()返回true,Idlehandler回调会在mIdleHandlers中保持存在,false回调执行完成后从mIdleHandlers中移除。

MessageQueue.postSyncBarrier():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;

Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}

方法内部就是创建一个target为null的Message,在next方法中利用target等于null作为依据来判断是否设置同步屏障。同样对应removeSyncBarrier就是利用Message的token字段找到设置的同步屏障Message移除。

Looper-消息循环器

Looper主要职责是创建MessageQueue和处理Message。prepare方法创建looper对象添加到sThreadLocal中,Looper构造方法中同时创建MessageQueue。loop方法通过调用MessageQueue.next方法取出Message,调用msg.target的handleMessage方法交给对应handler处理Message。

prepare()和构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
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));
}

//构造方法
private Looper(boolean quitAllowed) {(1
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

(1)quitAllowed 为true表示允许停止,这里构造方法是私有的,public修饰的无参构造调用这里时传入true,所以我们在日常使用中,在工作线程创建的looper都是允许停止的,只有UI线程的looper是不允许停止。也就是我们不能通过Looper.quit()来停止主线程的looper,可以这样理解,如果主线的looper停止也就相当于应用程序停止工作。

loop():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);

boolean slowDeliveryDetected = false;

for (;;) {//(1)
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;

final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;

if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}

final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);//(2)
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}

if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}

msg.recycleUnchecked();//(3)
}
}

(1)无限循环中queue调用next读取Message对象;
(2)调用hanlder.dispachMessage方法将Message交给handler处理;
(3)回收Message缓冲池未满时重置数据将Message添加到缓冲池。

Hndler-消息处理器

Hndler负责发送Message和处理回调的各种Message,enqueueMessage方法发送消息,将消息添加到queue中,handleMessage方法处理回调回来Message,Handler中的方法在开发中经常会直接使用。removeMessages方法移除当前handler下的Message,内部调用MessageQueue.removeMessages,同样的方法还有removeCallbacks()和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();

if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* Remove any pending posts of callbacks and sent messages whose
* <var>obj</var> is <var>token</var>. If <var>token</var> is null,
* all callbacks and messages will be removed.
*/
public final void removeCallbacksAndMessages(@Nullable Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}

//MessageQueue.removeCallbacksAndMessages
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}

synchronized (this) {
Message p = mMessages;

// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}

// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}

hanlder中的发送Message和移除Message都是调用MessageQueue对链表做操作。

关联

上面对源码的解读,大致对各自的职责有了了解,这里对它们之间的联系做说明。

MessageQueue是以Message作为内容的链表结构,可以将Message插入到MessageQueue中,也可以将Message从MessageQueue中删除。
Looper负责循环读取MessageQueue中的Message,将Message调用给handler完成Message消息传递。
Handler负责将创建好的Message发送给MessageQueue,MessageQueue将其插入到链表中,同时Handler最终接收Looper从MessageQueue中读取的Message执行它的处理方法handleMessage。

为什么?

  • Handler消息机制的作用?
    用于跨线程通信,Android中因为UI线程不能执行耗时操作,所以需要将耗时任务在子线程执行,子线程又不能更新UI,此时就需要handler通知UI线程执行UI操作。同样子线程与子线程也可以通过Handler通信。
  • 什么是闲时机制?
    IdleHandler是一个回调接口,它存储在一个数组中,当MessageQueue中的Message任务暂时停止处理(没有新任务或者下一任务延迟在后),这个时候就会调用这个接口的queueIdle(),方法返回false则会从list中移除,返回true在下次MessageQueue暂停处理时继续调用这个接口的queueIdle方法(代码在MessageQueue.next())。
  • 什么是同步屏障?
    同步屏障就是把同步消息先屏蔽优先处理异步消息,调用MessageQueue.postSyncBarrier方法可以将一个target为空的Message插入到MessageQueue中,当Looper调用MessageQueue.next方法读取Message时首先会通过target为空来判断是否设置同步屏障,若存在,会先遍历消息链表跳过同步消息找到异步消息优先将异步消息返回给Looper执行调用。这是一种优先机制,把异步消息的优先级高于同步消息,ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保证UI绘制优先执行(performTraversals())。
1
2
3
4
5
6
7
8
9
10
11
12
13
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

mTraversalRunnable是一个Runnable对象,run方法中会调用performTraversals()执行UI绘制。

  • Looper.loop()为什么不会阻塞主线程?
    loop方法会调用MessageQueue中next方法,next()调用本地方法nativePollOnce,没有新消息时会阻塞到nativePollOnce方法,nativePollOnce方法内部是基于Linux epoll机制实现阻塞,此时主线程会进入休眠状态,不会消耗cpu资源(pip管道当有数据写入时再唤醒主线程工作)。
    引起ANR的原因是由于执行事件耗时太长,不能及时完成,而loop方法本身只是不停的循环读取消息,当有消息时loop是不会阻塞的,只有loop发送的事件耗时太长才会导致ANR。Activity的onCreate onResume 等生命周期回调方法操作时间太长才会导致卡死主线程掉帧甚至发生ANR(Activity一般超过5s就会发生ANR)。
  • Thread、Hanlder、Looper和MessageQueue的数量级关系?
    一个Thread可以有多个Handler,只有有一个Looper和MessageQueue,多个Handler创建的Message都添加到同一个MessageQueue中,Looper从MessageQueue中拿到Message通过Hadnler对象target分发到对应Handler处理回调。
    Thread(1)==>Looper(1)==>MessageQueue(1)==>Handler(N)

  • 引起内存泄漏的原因以及解决方案?
    原因:由于java的特性,内部类会持有外部类的引用,所以Handler会持有Activity,Message中target对象又是Handler的引用,所以Message就持有了Activity,Activity调用销毁后如果当前HandlerMessage还在MessageQueue中,导致Activity不能被及时回收。
    解决方案:
    将Handler定义为静态内部类,持有Activity的弱引用并在Activity销毁时调用removeCallBacksAndMessages(null)移除所有消息。
  • Handler发送一个延时消息是怎么更新MessageQueue?
    如果此时MessageQueue中只有这个延时消息,消息不会被马上发送,而是计算唤醒时间让Looper阻塞,到唤醒时间时再唤醒Looper执行消息发送。如果MessageQueue中还有Message,延时消息被插入到时间排序的对应位置,MessageQueue中对头的when值最小,越往队尾值越大。
]]>
源码解读
java局部内部类访问局部变量时为什么需要final来修饰? /2021/01/12/java局部内部类访问局部变量时为什么需要final来修饰?/ 2021-01-12T10:10:41.000Z 2021-02-13T08:26:37.453Z <![CDATA[
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void test() {
final String str = "23";
Thread t= new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}


System.out.println("str:"+str);

}
});

t.start();
}

test()是一个类的方法,Runnable是它的匿名内部类,局部变量str用了final修饰符,如果没有final修饰符将会报错,为什么呢?

为了保证数据的一致。
从虚拟机运行内存角度:
test()方法对应虚拟机栈中的一个栈帧,当该栈帧出栈后其局部变量也就销毁,然这时存在内部类的方法需要访问str,就出现了生命周期不一致的情况,所以需要使用final将str变为常量来保证一致性。

匿名内部类编译后会另外单独生成一个Class,外部参数会以构造函数参数传入,传入参数可能在内部被修改,这会导致了数据的不一致。

]]>
JAVA基础
2020结 /2020/12/23/2020结/ 2020-12-23T09:55:44.000Z 2021-01-12T11:19:48.508Z <![CDATA[

2020年对于社会这个大环境来说是艰难的一年,对于我个人来说更是变动的一年,离开了从实习到如今四年的老东家,开始了一年的漂泊,到了年底还没有稳定下来,不过一年过来庆幸自己没有妥协,一直还在不断调整和思考。

2020就这样写下了记号,工作上这一年是没有成果的,物质和精神都没有,当然这些也不是年初计划太过于关注的点。2020如果说有一点点收获的话那是在生活状态和心理变化上,之前一直唯利,如今停下这个脚步也该看看走的稳不稳,走的累不累,当然也要感谢那几年的累积,才能让现在看的更明了一些,如果比作爬山,我觉得过去的四年我是前两年用力很猛,后两年有些疲软,这一年停下了脚步歇歇脚,有点冷热不均的意思,接下来该怎么走呢?平衡,生活与工作、休息与忙碌、亲情和还没来的爱情,2021年我要尝试去平衡这些,当前生命健康是第一。

过去决定现在,现在影响将来,2021我的生活会是不同以往的,预感会有很大的变化,且拭目以待。

2021伊始,感谢将出现在我生命里的可爱的人们。3q!

]]>
<p>2020年对于社会这个大环境来说是艰难的一年,对于我个人来说更是变动的一年,离开了从实习到如今四年的老东家,开始了一年的漂泊,到了年底还没有稳定下来,不过一年过来庆幸自己没有妥协,一直还在不断调整和思考。</p> <p>2020就这样写下了记号,工作上这一年是没有成果的,物
View绘制 /2020/06/02/View绘制/ 2020-06-02T04:55:22.000Z 2020-12-29T13:29:10.206Z <![CDATA[
  • View的绘制流程

View的绘制是从ViewRootImpl类的performTraversals方法开始经过measure、layout、draw三个过程将View绘制出来的,measure方法用来测量ViewGroup/View的宽高,layout用来确定View的最终宽高和在容器内的位置,draw绘制View到屏幕上。

performTraversals方法会依次调用perfomrMeasure,performLayout和performDraw,这三个方法依次调用底层View的绘制流程,也就是调用onMeasure、onLayout和onDraw,三个方法通过递归方式完成整个布局的绘制。

  • MeasureSpec
    MeasureSpec可以理解为“测量规则”或“测量标准”。MeasureSpec用一个32位的int值来表示,高2位代表SpecMode(测量模式),低30位代表SpecSize(规格大小)。代码定义这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

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(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}

@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

代码里可以看到MeasureSpec将SpecMode和SpecSize打包成一个int值,这样可以避免过多的内存分配,getMode和getSize方法利用MODE_MASK与操作来得到需要的mode和size值。
SpecMode有三种模式:

  1. UNSPECIFIED:[ʌnˈspesɪfaɪd]未说明的,没有明确的。父容器对于View没有任何限制,想要多大就给多大。
  2. EXACTLY:[ɪɡˈzæktli] 明确的。它对应LayoutParams中的match_parent或指定大小值的两种场景。父容器给出了一个确定的范围,如果view设置了具体值这个具体没有超出给定的范围那么就是这个具体值,如果超出了那view的大小就是父容器给定的限制值或者直接就指定限制值也就是match_parent的场景
  3. AT_MOST:view想要多大就是多大,至到达到指定的值。对应LayoutParams中的wrap_content。

这三个值的设置是站在父容器的角度衡量view的,UNSPECIFIED是父容器没有对view做限制,EXACTLY是父容器给出了一个明确限制值(最大边界)来约束view,AT_MOST则是按照view的想要的来展示,但也有指定值来限定。

对于一个普通的View,它的MeasureSpec是由父容器的MeasureSpec和自身设置的LayoutParam参数来决定的,比如View设置了固定的宽或者高那么它在宽或者高方向上的SpecMode就是EXCATLY,无论它的父容器设置什么测量模式

View的绘制流程和activity的生命周期是不同步的,所以在onresume中直接获得view宽高是错误的,可以痛殴VIewTreeObser监听绘制过程或者通过View.post方法投递一个消息队列到尾部,等待looper调用该runnable时view已经初始化

postscript:getMeasureWidth和getWidth的区别

]]>
Android基础
View事件传递 /2020/06/01/View事件传递/ 2020-06-01T03:52:08.000Z 2020-06-01T05:44:28.286Z <![CDATA[
  • MotionEvent
    MotionEvent是View的动作事件参数,包含集中常见的时间类型:
  1. ACTION_DOWN:手指刚接触到屏幕时;
  2. ACTION_MOVE:手指在屏幕上移动;
  3. ACTION_UP:手指从屏幕上离开的瞬间;
    MotionEvent的两组位置方式:
    getX()/getY():表示动作相对于View左上角位置的x、y坐标;
    getRawX()/getRawY():表示动作相对于屏幕左上角位置的x、y坐标;
  • 事件分发
    public boolean dispatchTouchEvent(MotionEvent event)
    用于事件的分发,如果时间传递到了当前的View,那么该方法一定会被调用,boolean类型的返回值受View的onTouchEvent(MotionEvent event)和其子View的dispatchTouchEvent(MotionEvnet event)的影响,返回true表示消耗当前时间。
    public boolean onInterceptTouchEvent(MotionEvent event)
    用来判断是否拦截当前事件,返回结果表示是否拦截当前事件。
    public boolean onTouchEvent(MotionEvent event)
    在dispatchTouchEvent方法中被调用,用来处理touch事件,返回结果表示是否消耗当前事件。


说明:

  1. 一个事件序列表示手指从接触屏幕(ACTION_DOWN)经历一些列动作事件到手指刚离开屏幕(ACTION_UP)。
  2. 某个View拦截事件,那么接下来的一个事件序列都有它来处理(时间能到到达的情况下),并且它的onIntercept不会再被调用。
  3. 如果View再处理事件过程中,不再消耗ACTION_DOWN事件(onTouch返回false),那么事件序列的其他事件将会被其父元素进行消耗。
  4. ViewGroup默认不拦截事件。
  5. VIew没有onInterveptTouchEvent方法,View默认的onTouchEvent都会消耗事件(默认返回true)。
  6. 事件传递是由外向内的,即事件总是先传递给父元素,然后通过父元素分发给子View,通过requestDisallowInterceptTouchEvnet方法来干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
]]>
Android基础
Android系统架构 /2020/05/31/Android系统架构/ 2020-05-31T12:13:09.000Z 2020-05-31T13:29:00.323Z <![CDATA[

Android采用分层架构,从上到下分别为应用程序层(apps、System apps)、应用程序框架层(Java API Framework)、系统运行库和运行环境层(Libraries+Android Runtime)和Linux核心层(HAL+Linux Kernel)

  • System apps
    可以理解为内置的系统应用,可以像调用Java APIFramework一样区调用系统应用,例如我们调用日历区添加一个日常提醒。

  • Java ApI Framewor
    android中常用的组件

  • Native c/c++ Libraries
    本地库比Java API Framework更加偏低层,这里包含OpenGl、多媒体框架等内容。

  • Android Runtime
    Android运行时环境,也就是Android虚拟机。Android5.0之前是使用Dalvik虚拟机,Dalvik虚拟机是基于JIT(Just in TIme)及时编译的引擎。Android5.0之后采用ART虚拟机,ART虚拟机是基于AOT(Ahead Of Time)作为编译引擎

  • Hardware Abstrction Layer
    硬件抽象层主要是媒体、蓝牙、传感器的库模块。

  • Linux Kernel
    Android平台的最底层,直接与硬件交互,负责硬件驱动、进程管理、内存管理、网络管理等功能。

什么是JIT和AOT?
在Android中Java代码会被转换成DEX字节码文件,DEX字节码文件是Android虚拟机可以识别的,Android虚拟机把字节码在转化为机器能识别的机器码。
Dalvik虚拟机给予JIT编译,JIT也叫及时编译器,JIT工作原理是在应用运行时,首先将一部分DEX字节码转化为机器码,在程序执行的过程中再陆续将更多代码编译并缓冲,这样做的好处是内存占用少,但CPU再应用运行期间相对消耗大。
AOT叫做提前编译器,它是在应用的安装期间就将DEX字节码转化为了机器码,并将其存储在设备上。这样做的好处是在应用运行时占用CPu资源少一些,因为已经转化成了机器码,相对内存占用上多一些。

]]>
Android基础
图片加载如何避免OOM /2020/05/30/图片加载如何避免OOM/ 2020-05-30T14:47:26.000Z 2020-05-30T15:19:51.691Z <![CDATA[

内存中Bitmap大小的计算公式:
长占用的像素 宽占用的像素 每个像素占用的内存
避免OOM也就是要减小图片在内存中的大小,有两种方式等比缩小长宽和减少每个像素占用的内存。

  • 等比缩小长宽:
    Bitmap的创建是通过BitmapFactory的工厂方法decodeFile()、decodeStream()、decodeByteArray()、decodeResource()。这些方法在创建Bitmap使都一个参数Options,Options中的属性inSampleSize用来对图片进行长宽的设置,inSampleSize的值是2的幂次方,通过设置合适的值来对图片进行缩放操作。

  • 减少像素内存:
    Options中的属性inPreferredConfig,通过调整这个值的属性来改变每个像素所占的内存,默认值是ARGB_8888,修改为RGB_565或ARGB_4444可以减少一半的内存。

含义
ARGB_8888A(Alpha)、R(Red)、G(Green)、B(Blue)各占8位的精度,加起来32位的精度(即4个字节),也就是一个像素占4个字节的长度
ARGB_4444A(Alpha)、R(Red)、G(Green)、B(Blue)各占4位的精度,加起来16位的精度(即2个字节),也就是一个像素占2个字节的长度
RGB_565R(Red)占5位精度、G(Green)占6位精度、B(Blue)占5位精度,一共16位精度,也就是2个字节,不携带透明度信息
ALPHA_8每个像素占位(即一个字节),只办函透明度值,不携带颜色信息
]]>
Android基础
IPC机制 /2020/05/29/IPC机制/ 2020-05-29T12:33:15.000Z 2020-05-30T14:25:20.618Z <![CDATA[
  • 进程与线程
    进程一般指一个执行的单元,也可以称作正在运行的程序实例线程是CPU进行运算调度的最小单元,它的系统资源是有限的,它是包含在进程之中。所以进程与线程是包含与被包含的关系。
  • 什么是IPC?
    IPC全称Inter-Process Communication,即进程间通信。一般应用采用多进程有是为了使应用能够获取更多的内存空间。由于一个进程对应分配一个虚拟机,进程与虚拟机的一一对应,造成我们对同一个类的对象在不同进程中就产生了对个副本,例如有processOne和processTwo两个进程,它们都有一个类A.class,那么在两个进程中的A.class是互不干扰的。总结起来对进程会造成一下几个问题:
    1. 静态成员和单例模式失效;
    2. 线程同步机制生效;
    3. SharedPreferences可靠性降低(进程并发写可能异常)
    4. Application多次创建(可以理解成Application的创建和进程的创建是一致的)

  • Binder机制
    Binder是android进程间通信的方式,Binder时基于C/S架构,主要有四部分组成:
    1. Client(客户端进程)
    2. Server(服务端进程)
    3. ServiceManager(提供注册、查询和返回代理服务对象的功能)
    4. Binder驱动(主要进程间的连接,进程间数据交互等基础底层操作)

    服务器通过Binder驱动在ServiceManager中注册服务
    客户端通过Binder驱动查询Servicemanager中注册的服务
    SreviceManager通过Binder驱动返回服务器代理对象
    客户端拿到服务器的代理对象后即可进行进程间的通信

  • IPC方式比较

适用场景
Bundle使用简单传输Bundle支持的数据类型四大组件间的数据传递
文件共享使用简单不适合高并发,无法进行即时通信用于实时性不高的场景
AIDL功能强大 支持一对多并发下的通信,支持实时使用较复杂,需要做好线程同步问题一对多通信且有RPC需求
Messenger功能一般,支持一对多串行通信,支持实时通信不支持RPC、不能很好处理高并发、数据通过Message进行传输,只能传输Bundle支持的数据类型低并发的一对多即时通信
ContentProvider在数据源访问上功能强大,支持一对多的并发数据共享,可通过call操作扩展其他操作提供数据源的CRUD操作进程一对多的数据共享
Socket功能强大,支持网络传输数据流、支持一对多的实时通信实现较繁琐网络数据共享
]]>
Android基础
Android性能优化 /2020/05/27/Android性能优化/ 2020-05-27T05:01:02.000Z 2020-05-30T14:48:46.004Z <![CDATA[

Android的性能优化主要从四方面入手,内存优化、布局优化、网络优化和安装包优化。

  • 常用检查工具
    LeakCanary是一个三方检测内存泄漏的工具库 ,集成后会自动检测应用运行期间的内存泄漏,并直观的输出。
    Android自带的Android Profiler ,可以检测CPU、MEMERY、NETWORK三方的性能。
    BlockCanary是一个三方用来检测UI卡顿的工具库,像LeakCanary一样集成后当发生UI卡顿现象时会输出卡顿的信息,通过输出的信息可以很方便的来定位导致卡顿的原因。

  • 内存优化
    内存优化的方式时避免内存泄漏(节流)、增加内存(开源)。
    常见的内存泄露:

  1. 单例导致的泄漏。在创建单例时传入了具体页面的Context,由于单例的静态属性,导致单例一致持有该Context引用,一直到应用程序结束。正确的做法是创建单例时应用传入应用级别的Context,也就是Application的Context,这样就保证了单例生命周期与传入Context的统一。
  2. 静态变量导致的内存泄漏。JVM中可以知道静态变量是在方法区的,它的生命周期是从类加载到应用程序结束,如果静态变量中持有了具体页面的Context就会导致页面finish后Context不会被GC,这样就导致了内存泄露。
  3. 非静态内部类导致的内存泄露。非静态内部类创建呢的Handle或Thread在执行延时操作时一直持有当前的Activity引用,这时候在Activity finish后就会导致内存泄漏。解决方式是使用静态内部类并用弱引用调用Activity;另外还可以在onDestroy中调用handler.removeCallbacksAndMessages取消所有事件消息。
  4. 使用资源没有及时关闭。常见操作有:数据流操作完没有关闭、bitmap没及时Recycle等
  5. 三方的没及时接绑。EventBus(unregister)和Rxjava中(dispose)。
  6. 动画执行导致的。在Activity finish时动画执行还在进行。解决就是在onDestroy时cacel动画。
  7. WebView导致的泄漏。 WebView即使调用了destroy也会导致内存泄漏。解决方式时使用弱引用WeekReference或者将WebView所在的Activity置于另外一个进程中。

增加内存方案一般会在Application下添加largeHeap=”true”,活着新开进程来时应用的内存总空间增大。

  • 布局优化 16ms
  1. 使用RelativeLayout减少View层级
  2. 利用抽离常用的布局
  3. 使用加载不常用的布局
  4. 使用减少布局嵌套
  5. 减少多余的背景绘制
  6. canvas.clipRect()裁剪可见区域,其他区域就不会被多余绘制。
  • 网络优化
  1. 合理合并网络请求
  2. 网络缓存
  3. 大数据分页请求
  4. 网络数据传输压缩(Gzip)
  5. 图片压缩、缩略图
  • 安装包优化
    apk的构成:
  1. assets文件夹:配置文件、资源文件 文件夹内的资源时通过AssetManager类内方法获取。
  2. res:资源文件,文件名会自动生成对应ID映射到.R文件中。
  3. META-INF:保存呢应用的签名信息,保证APK的完整性。
  4. AndroidManifest.xml:描述应用的配置信息。
  5. classes.dex:Dalvik虚拟机可执行的字节码文件,SDK下dx工具将JAVA字节码转化为Dalvik字节码。
  6. resources.arsc:资源文件和资源ID间的映射关系。

根据spk的构成做一下几方面的优化:

  1. 混淆:使用proGuard代码混淆工具,它包含了代码的压缩、优化、混淆等功能。
  2. 资源优化:利用lint工具删除冗余代码,资源文件的最小化等。
  3. 重复功能库:去除一些功能重复库的引用。
  4. 插件化:功能模块放在服务器上,按需下载。
    5.so库优化:保留v7版本,删除armeabi和v8版本的SO库,v7库可以按足市场上绝大多树手机的要求。
]]>
Android基础
Android动画分类 /2020/05/26/Android动画分类/ 2020-05-26T05:26:27.000Z 2020-05-26T07:04:55.073Z <![CDATA[
  • 帧动画
    通过在xml中的一组图片的播放来实现。
  • 补间动画
    分为旋转、移动、缩放、透明度四类动画,以及他们的组合来实现动画效果。
  • 属性动画
    属性动画目前使用场景最多,分类两种ViewPropertyAnimator和ObjectAnimator。前者是通用的动画,例如旋转、透明度、位移和缩放,简单通过View.animator()就可得到ViewPropertyAnimator。后者通过ObjectAnimator.of属性()来返回一个ObjectAnimator,ObjectAnimator需要重绘View所以要调用invalidate()来刷新绘制,最后通过start()方法启动动画。

  • 补间动画和属性动画的区别?

补间动画只是在父VIew层利用Matrix不多绘制View,达到移动的效果,其实View并没有发生变化,还在动画之前的位置。
属性动画是真正改变了View的属性值,真正的改变View的具体属性值的。

]]>
Android基础
TCP和UDP /2020/05/25/TCP和UDP/ 2020-05-25T09:18:41.000Z 2020-05-25T14:35:50.210Z <![CDATA[
  • TCP((Transmission Control Protocol,传输控制协议))

TCP协议是面向连接、可靠的字节流传输服务。TCP协议在C/S间数据交换前,需要先在上方建立一个TCP连接,之后才开始传输数据,并提供超时、重发、丢弃重复数据、数据校验和流量控制等功能。
特点:面向连接、可靠通信、面向字节流
应用层协议:HTTP、HTTPS、SSH、FTP、SMTP

  • UDP(User Data Protocol,用户数据报协议)

UDP是一个面向数据报的传输层协议,不具有可靠性,只是把数据发出去,不保证数据是否能到达S端。因为UDP在传输数据前不需要建立一个连接,所以它的传输效率很快,不能保证数据的可靠。
特点:无连接、不可靠、面向数据报
应用层协议:DHCP、DNS

  • TCP协议建立连接的三次握手

第一次握手(请求建立连接)C端发送建立连接请求,携带序列号seq=x 标示SYN=1,此时C端处于SYN_SEND状态;
第二次握手(确认请求)S端收到后,发出确认信息,确认信息ACK=x+1,同时携带自己的序列号SYN=1,seq=y;
第三次握手(建立连接)C端收到S端的确认请求后,向S端发送确认ACK=y+1,S端收到请求后两端都处于Established状态,表示当前的一次TCP连接成功。

  • TCP断开连接的四次挥手

第一次挥手(请求释放)C端发送释放连接的请求信号FIN=1,seq=u,此时C端处于FIN WAIT状态,不再发送数据给S端;
第二次挥手(确认请求)S端收到释放请求后,发送确认收到请求释放,ACK=1,ack=u+1,此时S度啊处于CLOSE EAIT状态,不再接收C端数据,但是需要发送给C端的数据可继续发送;
第三次挥手(确认释放)当S端不再有数据需要发送给C端时,发送却是释放连接,携带FIN=1,seq=w,ACK=1,ack=u+1,此时S端处于LAST ACK状态,等待C端的最终确认;
第四次挥手(最终释放确认)当C端收到了确认释放后,随即发送最终释放确认,ACK=1,ack=w+1,seq=u+1;此时C端在等待2MSL后关闭连接,S端收到请求后同样关闭。

  • 为什么需要三次握手?
    如果是两次握手,服务端确认请求后,不知道客户端是否能收到了消息,服务端的消息得不到确认。(服务端消息等不到确认)
    如果客户端发送的请求网络延迟了,超时后有客户端重新发起请求,倘若在重发请求正常进行完毕后,再收到之前网络拥塞的请求,再和服务端建立连接,这个时候就可能服务端一直等待,导致服务端连接资源浪费。

  • 为什么第三次握手是seq=x+1,而不是x+2?
    从握手的规律可以看出来seq在确认请求中会变做确认表示即ack,ack会在seq值的基础上加1,同时TCP规定,SYN不携带数据,但会消耗掉一个序列号,SYN=1时会消耗seq的一个值(即加1),第三次握手ACK=1、SYN不等于1,而ACK=1不消耗seq所以seq=x+1而不是x+2,挥手中FIN也和SYN一样

  • TCP建立连接后客户端出现故障会怎样?
    服务端有个计数器,一般两小时,两小时如果没有收到任何数据,会发送探测报文段,发了几个报文段如果还是没反应,服务端就会关闭连接。

]]>
计算机网络
HTTP、HTTPS和HTTP2.0 /2020/05/23/HTTP、HTTPS和HTTP2-0/ 2020-05-23T10:14:51.000Z 2020-05-25T09:18:10.142Z <![CDATA[

* 什么是HTTP

HTTP(HyperText Transfer Protocol)中文全名超文本传输协议,HTTP是用于客户端与服务器间请求响应的协议。HTTP是应用层的协议,和其他该层协议一样,它是服务于某一类具体应用的协议。

* 什么是HTTPS(Hyper Text Transfer Protocol over SecureSocket Layer)

HTTPS是在HTTP的基础上加上了SSL/TLS层
TLS(Transport Layer Security,传输层安全协议)、SSL(Secure Sockets Layer 安全套接层)

SSL由NetScape公司设计,共有三个版本1、2、3。SSL 3.0得到大规模使用,而TLS是SSL标准化后的升级版

SSL/TLS握手阶段工作流程:

  1. 客户端向服务端发出加密通信请求(ClientHello)

    携带支持的协议,例如TLS 1.0版
    客户端生成的随机数random1,后面用于生成“对话密钥”
    支持的加密方法,比如RSA公钥加密
    支持的压缩的方法

  2. 服务端收到请求,做出响应(ServerHello)

    确认加密通信协议,例如TLS 1.0版。如果协议与服务端支持的不一致,则关闭加密通信。
    服务端生成一个随机数random2,后面用于生成“对话密钥”
    确认加密方法,例如RSA公钥加密
    携带服务端证书给客户端

  3. 客服端做证书验证和公钥对随机数加密发送给服务端(ClientResponse)

    验证证书的安全性
    验证通过后,客户端生成随机数pre-master secret,然后使用证书中的公钥进行加密,发送给服务端

  4. 服务端私钥解密获得随机数(ServerResponse)

    服务端收到公钥加密的内容,在服务端使用私钥解密后得到pre-master secret,然后根据random1、random2和pre-master secret通过一定的算法得出对称加密的秘钥,作为后面交互过程中的对称密钥。同时客户端也使用random1、random2和pre-master secret,同样的算法生成对称密钥。

  5. 后续操作使用上一步生成的对称密钥对传输的内容加密和解密。

如何保证公钥不被篡改?

将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。

什么是对称加密?

加密和解密用的都是相同的密钥,优点是速度快,缺点是安全性低,常见的对称加密DES、AES等

什么是非对称加密?

非对称加密由一对密钥对,分为公钥和私钥。一般私钥自己持有,公钥可以公开给对方,优点是安全性比对称密钥高,缺点是数据传输效率比对称密钥低。采用公钥加密的数据只能由对应的私钥来解密。常见的非对称加密算法RSA。

HTTPS在传输过程中采用了对称加密和非对称加密结合使用,使用非对称加密传递密钥,然后使用对称密钥进行数据传输的加密和解密。二者的结合既保证了传输的安全性,也保证了数据传输的效率

* HTTP 2.0

  1. (二进制格式)HTTP 2.0采用二进制格式传输,而HTTP1.x使用纯文本的形式传输数据
  2. (Header压缩)HTTP 2.0对已经发送的Header使用键值对建立索引表(两端维护的索引表),相同的Header使用索引表示,即Header压缩。
  3. (服务器推送)服务端可以进行主动推送
  4. (多路复用)一个TCP连接被划分为多个流,客户端借助流和服务端建立全双工通信,且流具有优先级。
]]>
计算机网络