引子

2022 年 3 月辞职,没多久上海迸发疫情,蜗居在家预备面试。在阅历 1 个月的闭关和 40+ 场 Android 面试后,拿到一些 offer。

整体上说,有如下几种面试题型:

  1. 基础常识
  2. 算法题
  3. 项目阅历
  4. 场景题

场景题,即“就事务场景给出处理方案”,考察运用常识处理问题的才干。这类题取决于临场应变、长期积累、命运。

项目阅历题取决于对工作内容的总结提炼、进步升华、命运

  1. 争取到什么样的资源
  2. 安排了怎么样的分工
  3. 搭建了什么样的架构
  4. 运用了什么形式
  5. 做了什么样的取舍
  6. 选用了什么战略
  7. 做了什么样的优化
  8. 处理了什么问题

力争把默默无闻的“拧螺丝”说成惊天动地的“造火箭”。(这是一门技术活)

但也不可防止地会产生“有些人觉得这是高大上的火箭,有些人觉得不过是矮小下的零件”。面试就比方相亲,甲之蜜糖乙之砒霜是常有的事。除非你优异到处理了某个业界的难题。

算法题取决于刷题,命运,相较于前两类题,算法题可“突击”的成分就更多了。只需刷题足够多,胜算就足够大。很多刷,反复刷。

基础常识题是一切题型中最能“突击”的,它取决于对“考纲”的收拾温习、归纳总结、背诵、命运。Android 的常识体系是庞杂的,关于有限的个人精力来说,考纲是无穷大的。

这不是一篇面经,把面试题公布是不讲武德的。但能够同享整个温习稿,它是我依照自己划定的考纲收拾出的悉数答案。

整个温习稿分为如下几大部分:

  1. Android
  2. Java & Kotlin
  3. 规划形式 & 架构
  4. 多线程
  5. 网络
  6. OkHttp & Retrofit
  7. Glide

由于篇幅太长,决议把悉数内容分红两篇同享给我们。其间,Android 和 Java & Kotlin 现已在榜首篇同享过,这一篇的内容是剩余的加粗部分。

规划形式/准则 & 架构

规划准则

  • 单一职责准则:关于内聚的准则。高内聚、低耦合的指导方针,类或许办法单纯,只做一件事情
  • 接口隔离准则:关于内聚的准则。要求规划小而单纯的接口(将过大的接口拆分),或许说只露出必要的接口
  • 最少常识规律
    • 关于耦合的准则。要求类不要和其他类产生太多相关,抵达解耦的作用
    • 从依靠者的角度来说,只依靠应该依靠的方针。
    • 从被依靠者的角度说,只露出应该露出的办法。
    • 方针办法的拜访范围应该受到束缚:
      1. 方针本身的办法
      2. 方针成员变量的办法
      3. 被当做参数传入方针的办法
      4. 在办法体内被创立方针的办法
      5. 不能调用从另一个调用回来的方针的办法
  • 开闭准则:关于扩展的准则。对扩展开放对修正封闭,做合理的笼统就能抵达增加新功用的时分不修正老代码(能用父类的地方都用父类,在运转时才承认用什么样的子类来替换父类),开闭准则是方针,里氏代换准则是基础,依靠倒转准则是手法
  • 里氏替换准则
    • 为了防止承继的副作用,若承继是为了复用,则子类不应改动父类行为,这姿态类就能够无副作用地替换父类实例,若承继是为了多态,则由于将父类的完成笼统化,
  • 依靠倒置准则:便是面向接口编程,面向笼统编程,高层模块不应依靠底层模块,而是依靠笼统(比萨店不应该依靠详细的至尊披萨,而应该依靠笼统的披萨接口,至尊披萨也应该依靠披萨接口)

单例形式

意图:在单进程内确保类仅有实例

  1. 静态内部类:虚拟机确保一个类的初始化操作是线程安全的,而且只要运用到的时分才会去初始化,缺陷是没办法传递参数
  2. 双重校验:榜首校验处于功用考虑,若方针存在直接回来,不需求加锁。第二次校验是为了防止重复构建方针。方针引证有必要声明为 volatile,经过确保可见性和防止重排序,确保单例线程安全。由于INSTANCE = new instance()不是原子操作,由三个进程完成1.分配内存2.初始化方针3.将INSTANCE指向新内存,当重排序为1,3,2时,或许让另一个线程在榜首个判空处回来未经实例化的单例。

工厂形式

  • 意图:解耦。将方针的运用和方针的构建分割开,使得和方针运用相关的代码不依靠于构建方针的细节
  • 增加了一层“笼统”将“改动”封装起来,然后对“笼统”编程,并运用”多态“应对“改动”,对工厂形式来说,“改动”便是创立方针。
  • 完成办法
    1. 简略工厂形式
      • 将创立详细方针的代码移到工厂类中的静态办法。
      • 完成了躲藏细节和封装改动,对改动没有弹性,当需求新增方针时需求修正工厂类
    2. 工厂办法形式
      • 在父类界说一个创立方针的笼统办法,让子类决议实例化哪一个详细方针。
      • 特色
        • 只适用于构建一个方针
        • 运用承继完成多态
    3. 笼统工厂形式
      • 界说一个创立方针的接口,把多个方针的创立细节会集在一同
      • 特色:运用组合完成多态

建造者形式

  • 意图:简化方针的构建
  • 它是一种结构杂乱方针的办法,杂乱方针有很多可选参数,假如将一切可选参数都作为结构函数的参数,则结构函数太长,建造者形式完成了分批设置可选参数。Builder形式增加了结构进程代码的可读性
  • Dialog 用到了这个形式

观察者形式

意图:以解耦的办法进行通讯。将被观察者和详细的观察行为解耦。

  • 是一种一对多的告知办法,被观察者持有观察者的引证。
  • ListView的BaseAdapter中有DataSetObservable,在设置适配器的时分会创立观察者并注册,调用notifydataSetChange时会告知观察者,观察者会requestLayout

战略形式

  • 意图:将运用算法的客户和算法的完成解耦
  • 手法:增加了一层“笼统”将“改动”封装起来,然后对“笼统”编程,并运用”多态“应对“改动”,对战略形式来说,“改动”便是一组算法。
  • 完成办法:将算法笼统成接口,用组合的办法持有接口,经过依靠注入动态的修正算法
  • setXXListener都是这种形式

装修者形式

  • 意图:用比承继更灵敏的办法为现有类扩展功用
  • 手法:详细方针持有超类型方针
  • ~是承继的一种替代方案,防止了众多子类。
  • ~增加了一层笼统,这层笼统在原有功用的基础上扩展新功用,为了复用原有功用,它持有原有方针。这层笼统本身是一个原有类型
  • ~完成了开闭准则

外观形式

  • 意图:躲藏细节,降低杂乱度
  • 手法:增加了一层笼统,这层笼统屏蔽了不需求关怀的子体系调用细节
  • 降低了子体系与客户端之间的耦合度,使得子体系的改动不会影响调用它的客户类。
  • 对客户屏蔽了子体系组件,削减了客户处理的方针数目,并使得子体系运用起来更加简略。
  • 完成办法:外观形式会经过组合的办法持有多个子体系的类,~供给更简略易用的接口(和适配器类似,不过这里是新建接口,而适配器是已有接口)
  • 经过外观形式,能够让类更加契合最少常识准则
  • ContextImpl是外观形式

适配器形式

  • 意图: 将现有方针包装成另一个方针
  • 手法:增加了一层笼统,这层笼统完成了方针的转化。(详细方针持有另一个而详细方针)
  • 是一种将两个不兼容接口(源接口和方针接口)适配使他们能一同工作的办法,经过增加一个适配层来完成,终究经过运用适配层而不是直接运用源接口来抵达意图。

署理形式

  • 意图:约束方针的拜访,或许躲藏拜访的细节
  • 手法:增加了一层笼统,这层笼统阻拦了对方针的直接拜访
  • 完成办法:署理类经过组合持有委托方针(装修者是直接传入方针,而署理通常是悄悄构建方针)
  • 分类 :署理形式分为静态署理和动态署理
  • 静态署理:在编译时现已生成署理类,署理类和委托类一一对应
  • 动态署理:编译时还未生成署理类,仅仅界说了一种笼统行为(接口),只要当运转后才生成署理类,运用Proxy.newProxyInstance(),并传入invocationHandler
  • Binder通讯是署理形式,Retrofit运用动态署理构建恳求。

模板办法形式

  • 意图:复用算法
  • 手法:新增了一层笼统(父类的笼统办法),这层笼统将算法的某些进程泛化,让子类有不同的完成
  • 完成办法:在办法(通常是父类办法)中界说算法的骨架,将其间的一些进程推迟到子类完成,这样能够在不改动算法结构的状况下,从头界说某些进程。这些进程能够是笼统的(表明子类有必要完成),也能够不是笼统的(表明子类可选完成,这种办法叫钩子)
  • android触摸事情中的阻拦事情是钩子
  • android制作中的onDraw()是钩子

指令形式

  • 意图:将履行恳求和恳求细节解耦
  • 手法:增加了一层“笼统”将“改动”封装起来,然后对“笼统”编程,并运用”多态“应对“改动”,对指令形式来说,“改动”便是恳求细节。新增了一层笼统(指令)
  • 这层笼统将恳求细节封装起来,履行者和这层笼统打交道,就不需求了解履行的细节。由于恳求都被共同成了一种姿态,所以能够共同办理恳求,完成撤销恳求,恳求行列
  • 完成办法:将恳求界说成指令接口,履行者持有指令接口
  • java中的Runnable便是指令形式的一种完成

桥接形式

  • 意图:进步体系扩展性
  • 手法:笼统持有另一个笼统
  • 是适配器形式的泛化形式

拜访者形式

  • 意图:动态地为一类方针供给消费它们的办法。
  • 重载是静态绑定(办法名相同,参数不同),即在编译时现已绑定,办法的参数无法完成运转时多态
  • 重写是动态绑定(承继),办法的调用者可完成运转时多态
  • 双分派:a.fun(b)在a和b上都完成运转时多态,完成办法调用者和参数的运转时多态。
  • 编译时注解运用了拜访者形式,一类方针是Element,表明构成代码的元素(类,接口,字段,办法),他有一个accept办法传入一个Visitor方针

架构

关于 MVP,MVVM,MVI,Clean Architecture 的介绍能够点击如下文章:

怎么把事务代码越写越杂乱? | MVP – MVVM – Clean Architecture

写事务不必架构会怎么样?(一)

写事务不必架构会怎么样?(二)

写事务不必架构会怎么样?(三)

MVP 架构终究审判 —— MVP 处理了哪些痛点,又引入了哪些坑?(一)

MVP 架构终究审判 —— MVP 处理了哪些痛点,又引入了哪些坑?(二)

MVP 架构终究审判 —— MVP 处理了哪些痛点,又引入了哪些坑?(三)

“无架构”和“MVP”都救不了事务代码,MVVM才干挽狂澜?(一)

多线程

进程 & 线程

  • 体系按进程分配除CPU以外的体系资源(主存 外设 文件), 体系按线程分配CPU资源
  • Android体系进程叫system_server,默许状况下一个Android运用运转在一个进程中,进程名是运用包名,进程的主线程叫ActivityThread
  • jvm会等候一般线程履行完毕,但不会等看护线程
  • 若线程履行产生反常会开释锁
  • 线程上下文切换:cpu操控权由一个运转态的线程转交给另一个安排妥当态线程的进程(需求从用户态到中心态转化)
  • 一对一线程模型:java语言层面的线程会对应一个内核线程
  • 抢占式的线程调度,即由体系决议每个线程能够被分配到多少履行时刻

堵塞线程的办法

  1. sleep():使线程到堵塞态,但不开释锁,会触发线程调度。
  2. wait():使线程到堵塞态,开释锁(有必要先获取锁)
  3. yield():使线程到安排妥当态,自动让出cpu,不会开释锁,产生一次线程调度,同优先级或许更高优先级的线程有时机履行

线程安全三要素

  • 原子性:不会被线程调度器中止的操作。
  • 可见性:一个线程中对同享变量的修正,在其他线程当即可见。
  • 有序性:程序履行的次序依照代码的次序履行。

原子操作

  1. 除long和double之外的基本类型(int, byte, boolean, short, char, float)的赋值操作。
  2. 一切引证reference的赋值操作,不管是32位的机器仍是64位的机器。
  3. java.concurrent.Atomic.* 包中一切类的原子操作。

死锁

四个必要条件

  1. 互斥拜访资源
  2. 资源只能自动开释,不会被掠夺
  3. 持有资源而且还恳求资源
  4. 循环等候
    处理方案是:加锁次序+超时抛弃

线程生命周期

线程从新建状况到安排妥当状况,安排妥当态的线程假如取得了cpu履行权就变成了运转态,运转完变成逝世态,假如运转中产生等候锁的状况(sleep,wait),则会进入堵塞态,当堵塞态的进程被唤醒后进入安排妥当态,参与cpu时刻片的竞赛,履行完毕逝世态。

线程池

  • 假如创立方针价值大,且方针可被重复运用。则用容器保存已创立方针,以削减重复创立开支,这个容器叫做池。线程的创立便是贵重的,经过线程池来维护实例。

线程通讯:等候告知机制

  • 等候告知机制是一种线程间的通讯机制,能够调整多个进程的履行次序。
  • 需求等候某个资源的线程能够调用 wait(),当某资源具备后,能够调用共同方针上的notify()
  1. notify():随机使一个线程进入安排妥当态,它需求和调用wait()是同一个方针(取得锁的线程才干调用)
  2. notifyAll():唤醒一切等候线程,让他们到安排妥当行列中

Condition

  • 是多线程通讯的机制,挂起一个线程,开释锁,直到另一个线程告知唤醒,供给了一种自动抛弃锁的机制。
  • await()挂起线程的一起开释锁(所以有必要先获取锁,不然抛反常),signal 唤醒一个等候的线程
  • 每个Condition方针只能唤醒调用自己的await()办法的那个线程
  • 假如条件不必 Condition 完成,则线程或许不断地获取锁并开释锁,但因持续履行的条件不满足,cpu 负载打满。运用Condition 让等候线程不耗费cpu
  • await() 通常合作 while(){await()} 由于被唤醒是从上次挂起的地方履行,还需求再次判别是否满足条件
  • await()有必要在拥有锁的状况下调用,以防止lost wake-up,即在await条件判别和await调用之间notify被调用了。当await条件满足后,还没来得及履行await时产生线程调度,另一个线程调用了notify()。然后才轮到await()履行,它将错过刚才的notify,由于notify在await之前履行。

interrupt()

  • 不会真实中止正在履行的线程,仅仅告知它你应该被中止了,自己看着办吧。
  • 若线程正运转,则中止标志会被置为true,并不影响正常运转
  • 假如线程正处于堵塞态,则会收到InterruptedException,就能够在 catch中履行呼应逻辑
  • 若线程想呼应中止,则需求经常查看中止标志位,并自动中止,或许是正确处理 InterruptedException

内存屏障

  • 用于制止重排序,它分为以下四种:
  1. LoadLoad Load1; LoadLoad; Load2 确保Load1数据的装载,之前于Load2及一切后续装载指令的装载。
  2. StoreStore Store1; StoreStore; Store2 确保Store1数据对其他处理器可见(刷新到内存),之前于Store2及一切后续存储指令的存储。
  3. LoadStore Load1; LoadStore; Store2 确保Load1数据装载,之前于Store2及一切后续的存储指令刷新到内存。
  4. StoreLoad Store1; StoreLoad; Load2 确保Store1数据对其他处理器变得可见(指刷新到内存),之前于Load2及一切后续装载指令的装载。StoreLoad Barriers会使该屏障之前的一切内存拜访指令(存储和装载指令)完成之后,才履行该屏障之后的内存拜访指令。

volatile

  • 确保变量操作的有序性和可见性
  • 在每一个volatile写操作前面刺进一个StoreStore屏障,能够确保在volatile写之前,其前面的一切一般写操作都现已刷新到主内存中。
  • 在每一个volatile写操作后边刺进一个StoreLoad屏障,防止volatile写与后边或许有的volatile读/写操作重排序。
  • 在每一个volatile读操作后边刺进一个LoadLoad屏障,制止处理器把上面的volatile读与下面的一般读重排序。
  • 在每一个volatile读操作后边刺进一个LoadStore屏障,制止处理器把上面的volatile读与下面的一般写重排序。
  • volatile便是将同享变量在高速缓存中的副本无效化,这导致线程修正动量的值后需立刻同步到主存,读取同享变量都有必要从主存读取
  • 当volatile润饰数组时,表明数组首地址是volatile的而不是数组元素

CAS

  • Compare and Swap
  • 当时值,旧值,新值,只要当旧值和当时值相同的时分,才会将当时值更新为新值
  • Unsafe将cas编译成一条cpu指令,没有函数调用
  • aba问题:当时值或许是变为b后再变为a,此a非彼a,经过加版本号能处理
  • 非堵塞同步:没有挂起唤醒操作,多个线程一起修正一个同享变量时,只要一个线程会成功,其他失利,它们能够挑选轮询。

synchronized

  • 隐式加锁开释锁

  • 可润饰静态办法,实例办法,代码块

  • 当润饰静态办法的时,确定的是当时类的 Class 方针(就算该类有多个实例,运用的仍是同一把锁)。

  • 当润饰非静态办法的时,确定的是当时实例方针 this。当 饰代码块时需求指定确定的方针。

  • 经过将对变量的修正强制刷新到内存,且下一个获取锁的线程有必要从内存拿。确保了可见性

  • 同一时刻只要一个线程能够履行临界区,即一切线程是串行履行临界区的

  • happen-before 便是开释锁总是在获取锁之前产生。

  • synchronized特色

    1. 可重入:可重入锁指的是可重复可递归调用的锁,在外层运用锁之后,在内层仍然能够运用,而且不产生死锁。线程能够再次进入现已取得锁的代码段,表现为monitor计数+1
    2. 不公正:synchronized 代码块不能够确保进入拜访等候的线程的先后次序
    3. 不灵敏:synchronized 块有必要被完好地包括在单个办法里。而一个 Lock 方针能够把它的 lock() 和 unlock() 办法的调用放在不同的办法里
    4. 自旋锁(spinlock):是指当一个线程在获取锁的时分,假如锁现已被其它线程获取,那么该线程将循环等候,然后不断的判别锁是否能够被成功获取,直到获取到锁才会退出循环,synchronized是自旋锁。假如某个线程持有锁的时刻过长,就会导致其它等候获取锁的线程进入循环等候,耗费CPU。运用不当会构成CPU运用率极高
  • 1.8 之后synchronized功用提升:

    • 倾向锁:意图是消除无竞赛状况下功用耗费,假定在无竞赛,且只要一个线程运用锁的状况下,在 mark word中运用cas 记载线程id(Mark Word存储方针本身的运转数据,在方针存储结构的方针头中)尔后只需简略判别下markword中记载的线程是否和当时线程共同,若产生竞赛则胀大为轻量级锁,只要榜首个恳求倾向锁的会成功,其他都会失利
    • 轻量级锁:运用轻量级锁,不要恳求互斥量,只需求用 CAS 办法修正 Mark word,若成功则防止了线程切换
    • 自旋(一种轻量级锁):竞赛失利的线程不再直接到堵塞态(一次线程切换,耗时),而是坚持运转,经过轮询不断测验获取锁(有一个轮询次数约束),规则次数后仍是未获取则堵塞。进化版本是自适应自旋,自旋时刻次数约束是动态调整的。
    • 重量级锁:运用monitorEnter和monitorExit指令完成(底层是mutex lock),每个方针有一个monitor
    • 锁胀大是单向的,只能从倾向->轻量级->重量级

ReentrantLock

  • 手动加锁手动开释:JVM会自动开释synchronized锁,但可重入锁需求手动加锁手动开释,要确保确定必定会被开释,就有必要将unLock()放到finally{}中。手动加锁并开释灵敏性更高
    1. 可中止锁:lockInterruptibly(),未获取则堵塞,但可呼应当时线程的interrupt()被调用
    1. 超时锁:tryLock(long timeout, TimeUnit unit) ,未获取则堵塞,但堵塞超时。
    1. 非堵塞获取锁:tryLock() ,未获取则直接回来
    1. 可重入:若已获取锁,无需再次竞赛即可从头进入临界区履行,state +1,出来的时分需求开释两次锁 state -1
    1. 独占锁:同一时刻只能被一个线程获取,其他竞赛者得等候(AQS独占形式)
  • 功用:竞赛不激烈,Synchronized的功用优于ReetrantLock,激烈时,Synchronized的功用会下降几十倍,可是ReetrantLock的功用能坚持常态
  • 是AQS的完成类,AQS中有一个Node节点组成双向链表,寄存等候的线程方针(被包装成Node)
  • 获取锁流程:
    1. 测验获取锁,公正锁排队逻辑:判别锁是否闲暇,若闲暇还要判别行列中是否有排在更前面的等候线程,若无则测验获取锁。若当时独占线程是自己,表明重入,则增加state值。非公正锁抢占逻辑:直接进行CAS state操作(从0到1),若成功则设置当时线程为锁独占线程。若失利会判别当时线程是否便是独占线程若是表明重入,state+1

    2. 获取失利则入AQS行列,然后在挂起线程:将线程方针包装成EXCLUSIVE形式的Node节点刺进到AQS双向链表的尾部(cas值链尾的next结点+自旋确保必定成功),并不断测验获取锁,或中止Thread.interrupted()

  • 开释锁流程:
    1. 开释锁表现为将state减到0
    2. 调用unparkSuccessor()唤醒线程(非公正时怎么唤醒)

ReentrantReadWriteLock

  • 并发度比ReentrantLock高,由于有两个锁,运用AQS,读锁是同享形式,写锁是独占形式。读多写少的状况下,供给更大的并发度
  • 可完成读读并发,读写互斥,写写互斥
  • 运用一个int state记载了两个锁的数量,高16位是读锁,低16位是写锁
  • 获取写锁进程:除了考虑写锁是否被占用,还要考虑读锁是否被占用,若是则获取锁失利,不然运用cas置state值,成功则置当时线程为独占线程。
  • 读并发也有并发数约束,获取读锁时需验证,并运用ThreadLocal记载当时线程持有锁的数量
  • 或许产生写饥饿,由于太多读
  • 锁降级:当一个线程获取写锁后再获取读锁,然后开释写锁
  • 不支撑锁升级是为了确保可见性:多个线程获取读锁,其间任意线程获取写锁并更新数据,这个更新对其他读线程是不可见的

StampedLock

  • 完成读读并发,读写并发。
  • 在读的时分假如产生了写,应该经过重试的办法来获取新的值,而不应该堵塞写操作
  • 用二进制位记载每一次获取写锁的记载

CountdownLatch

  • 用作等候若干并发使命的完毕
  • 内部有一个计数器,当值为0时,在countdownLatch上await的线程就被唤醒
  • 经过AQS完成,初始化是置AQS.state为n,countdow()经过自旋+cas将履行state–作用

CyclicBarrier

  • 用于同步并发使命的履行进度
  • 运用 ReentranntLock 确保count线程安全,每次调用await() count–,然后在condition上堵塞,当count为0时,会signalAll()

Semaphore

  • 用于约束并发拜访资源线程的个数
  • 根据AQS,初始化是为state赋值最大并发数,调用acquire()时便是cas将state-1,当state小于零时,令牌缺乏,将线程包装成node结点会入行列,然后挂起
  • 有公正和非公正两个结构办法

AbstractQueuedSynchronizer

  • 完成了cas办法竞赛同享资源时的线程堵塞等候唤醒机制
  • AQS供给了两种资源同享办法1.独占:只要一个线程能获取资源(公正,不公正)2.同享:多个进程可获取资源
  • AQS运用了模板办法形式,子类只需求完成tryAcquire()和tryRelease(),等候行列的维护不需求关怀
  • AQS运用了CLH 行列:包括sync queue和condition queue,后者只要运用condition的时分才会产生
  • 持有一个volatile int state代表同享资源,state =1 表明被占用(供给了CAS 更新 state 的办法),其他线程来加锁会失利,加锁失利的线程会放入等候行列(被Unsafe.park()挂起)
  • 等候行列的队头是独占资源的线程。行列是双向链表

AtomicInteger

  • 线程安全的int值,为了防止在一个变量上运用锁完成同步,这样太重了
  • 在高并发的状况下,每次值成功更新了都需求将值刷到主存
  • 自增运用cas+自旋+volatile
    public final int incrementAndGet() {
        for (;;) {// 自旋
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))// cas
                return next;
        }
    }

AtomicIntegerArray

  • 线程安全整形数组
  • 内部持有 final int[] array,确保了运用时现已初始化,而且引证不能改动
  • 对数组的操作退化为对单个元素的操作,由于数组的内存模型是连续的,而且每个元素所占空间一样大,
  • 运用Unsafe带有volatile的办法进行对单个元素赋值。

AtomicReference

  • 供给方针引证非堵塞原子性并发读写
  • 方针引证是一个4字节的数字,代表着堆内存中的一个地址

CopyOnWriteArrayList

  • 完成了线程安全的读写并发,读读并发,但不能完成写写并发(上锁了,synchronized),由于他们操作是不同的副本
  • 运用不可变 Object[] 作为容器
  • 写时复制数组,写入新数组,引证指向新数组。加锁防止一次写操作导致复制了多个数组副本
  • 读操作便是一般的获取数组某元素,
  • 适宜读多写少,由于写需求复制数组,耗时
  • 适宜集合不大,由于写时要复制数组,耗时,耗内存
  • 实时性要求不高,由于或许会读到旧数据。(对新数组写,对数组读)
  • 选用快照遍历,即遍历建议时构成一张当时数组的快照,而且迭代器不允许删去,新增元素。不会产生 ConcurrentModificationException,但或许实时性不行。
  • 适用于作为观察者的容器

ArrayBlockingQueue

  • 巨细固定的,线程安全的次序行列,不能读读,读写,写写并发。
  • 运用Object[]作为容器,环形数组。比复制拷贝功率高。
  • 存取运用同一把锁 ReentrantLock 确保线程安全+2个condition(写操作或许在notFull条件上堵塞,读操作或许在notEmpty上堵塞)
  • 遍历支撑remove及并发读写。
  • 适用于操控内存巨细的出产者顾客形式,行列满,则堵塞出产者/有限等候,行列空则堵塞顾客/有限等候。
  • 适用于做缓存,缓存有巨细约束,缓存是出产者顾客模型,多线程场景下需考虑线程安全。

LinkedBlockingQueue

  • 线程安全的链行列,完成读写并发,不能读读,写写并发
  • 存取用两把不同的 ReentrantLock,适用于读写高并发场景。
  • 可完成并行存取,速度比 ArrayBlockingQueue 快,但有额外的Node结点方针的创立和毁掉,或许引发 gc,若消费速度远低于出产速度,则内存胀大

SynchronousQueue

  • 以非堵塞线程安全的办法将元素从一个出产线程递交给消费线程
  • 适用于出产的内容总是能够被瞬间消费的场景,比方容量为 Int.MAX_VALUE 的线程池,即当新恳求到来时,总是能够找到新得线程来履行恳求,不管是老的闲暇线程,仍是新建线程。
  • 存储结构是一个链,运用 cas + 自旋的办法完成线程安全

PriorityBlockingQueue

  • 运用 Object[] 作为容器完成堆
  • 运用 ReentrantLock 确保线程安全,读和取同一把锁
  • 每次存取会引发排序,运用堆排序进步功用
    1. 每次写,都写到数组结尾,然后堆向上调整
    2. 每次读都读取数组头,并将数组结尾元素放到数组头。然后履行一次向下调整

ConcurrentLinkedQueue

  • 是一个链式行列,运用非堵塞办法完成并发读写的线程安全,而是运用轮询+CAS确保了修正头尾指针的线程安全
  • 存储结构是带头尾指针的单链表
  • 头尾指针和结点next域都运用 volatile 确保可见性。
  • 出队时,经过 cas 确保结点 item 域置空的线程安全,更新头指针也运用了 cas。
  • 入队时,经过 cas 确保结点 next 域指向新结点的线程安全,更新尾指针也运用了 cas。
  • 出于功用考虑,头尾指针的更新都是推迟的。每刺进两个结点,更新一下尾指针,每取出两个结点,更新一下头指针。
  • 适用于出产者顾客场景
  • 入队算法:总是从当时tail指向的尾部向后寻觅真实的尾部(由于tail更新滞后,而且或许被另一个入队线程抢占),找到后经过cas值next域

ConcurrentHashMap

  • 1.7 运用的是开散列表,数组+链表
  • 1.7 Segment 数组,Segment 是一个 ReentrantLock,分段锁,并发数是 Segment 的数量。每个 Segment 持有一个 Entry 数组(桶)。(一个entry便是一条链)
  • 1.7 定位一个元素需求两次hash,先定位到 Segment 再定位到元素地点链表头。
  • 1.7 put()先测验非堵塞的获取锁,失利则自旋重试,并计算出对应桶的方位,抵达最大测验次数后堵塞式的获取。
  • 1.7 ConcurrentHashMap.get()不需求上锁是由于键值对实体中将value声明成了volatile,能够在线程之间坚持可见性
  • 1.7 假如ConcurrentHashMap的元素数量增加导致ConrruentHashMap需求扩容,ConcurrentHashMap不会增加Segment的数量,而只会增加Segment中链表数组的容量巨细,扩容的时分首先会创立一个两倍于原容量的数组,然后将原数组里的元素进行再 hash 后刺进到新的数组里
  • 1.7 遍历链表是个相对耗时的操作
  • 1.8 将重入锁改成synchronized,由于它被优化过了
  • 1.8 也是开散列表,数组+链表(或许红黑树),当链表长度大于8时,则将链表转化为红黑树,增加查找功率
  • 1.8 运用cas办法确保只要一个线程初始化成功
  • 1.8 put操作:对key进行hash得到数组索引,若数组未初始化则初始化,假如索引方位为null 则直接cas写(失利则自旋坚持成功),(后边的部分synchronize了)假如索引方位为链头指针,则进行链刺进,往后遍历找到相同的key 则覆盖,不然链尾刺进,若索引方位是红黑树,则进行红黑树刺进。
  • 1.8 锁的粒度更细了,一个桶一个锁。
  • 1.8 Node.next 用volatile润饰

红黑树

  • 二叉树是一个父节点有两个子节点的递归结构
  • 二叉排序树是一种特别的二叉树,它规则左孩子 < 父亲 < 右孩子,它处理了二叉树退化为单链表的状况(查找时刻杂乱度退化为O(n))
  • 平衡二叉排序树是一种特别的二叉排序树。它规则每一个结点的左右子树高度差不大于1
  • 红黑树是没有那么严格的平衡二叉排序树。由于频频的调整子树是耗时的。
  • 二叉排序树是二分查找,最大查找次数为树高度
  • 红黑树刺进结点后经过变色和旋转来坚持红黑树的平衡。确保了没有任何一条途径会比其他途径长出两倍。

ConcurrentModificationException

  • 当遍历数组的一起删去其间元素就会产生这个反常,这叫fast-fail机制。
  • 由于调用next()时会查看 modCount和expectModCount是否共同,不共同则抛这个反常。
  • 但单线程下怎么处理这个问题:运用iterator.remove,他会同步modCount和expectModeCount

ThreadPoolExecutor

  • 这是java的线程池。
  • ThreadPoolExecutor结构参数如下:
  1. 中心线程数:线程池中一向存活的线程数量,当新的使命恳求到来时,假如线程池中线程数小于中心线程数,则会新建线程,默许状况下中心线程会一向存活,只要当allowCoreThreadTimeOut设置为true时且产生超时才会被毁掉
  2. 最大线程数,线程池中线程的上限
  3. keepAlive:非中心线程允许闲暇的最大时长,超越闲暇时刻则会被毁掉(当池中线程数>=中心线程数时创立出来的线程都是非中心线程)
  • ThreadPoolExecutor线程池办理战略
if 线程池中正在运转的线程数 < corePoolSize
  {新建线程来处理使命(即便线程池中有线程处于闲暇状况)}
else if 线程池中正在运转的线程数 >= corePoolSize
  {
  if 缓冲行列未满
    使命被放入缓冲行列
  else 缓冲行列满
    if maximumPoolSize > 线程池中正在运转的线程数 > corePoolSize
      新建线程来处理使命 此刻的使命会被当即履行
    else if 线程池中正在运转的线程数 = maximunPoolSize
      经过handler所指定的战略来处理此使命
        回绝战略(丢掉战略)
          ThreadPoolExecutor.AbortPolicy 悄悄地丢掉一个使命
          ThreadPoolExecutor.DiscardOldestPolicy 丢掉最旧的使命,从头提交最新的
          ThreadPoolExecutor.CallerRunsPolicy 在调用者的线程中履行被回绝的使命
          ThreadPoolExecutor.DiscardPolicy() 丢掉当时使命
  }

网络

网络分层的长处是下层的可重用性,tcp不需求知道它传输的是http仍是ftp亦或是SSH。

1. 物理层

  • 二进制在物理媒体上传输

2. 数据链路层

在物理层的基础上供给过失校验。

3. 网络层(ip)

为数据包路由

4. 传输层(tcp,udp)

供给端到端接口

tcp

  • 传输操控协议,是传输层协议,处理数据怎么传输,是面向衔接的,牢靠的点到点传输协议。

  • tcp头包括 sequence number(32位) 用于标识报文中榜首个字节在整个数据流中的序号,确保有序。

  • tcp头包括 ack number(32位),表明对上一个接纳到的sequence number的承认,处理丢包。只要当ack位为1时才有用

  • tcp 头部包括滑动窗口巨细

  • tcp 头部包括 tcp flag,有6个标志位 URG,ACK,PSH,RST,SYN,FIN

  • tcp 头部包括两个16位的端口号(源+意图)

  • tcp是根据字节省的

  • 选用承认和超时重传战略确保牢靠传输

    • 承认:接纳方检测出帧出错是不会回来承认帧并直接丢掉该帧
    • 超时重传:发送方发送数据报后发动倒计时,若规则时刻内未收到承认才重传数据报
  • 供给拥塞操控和流量操控

    • 选用巨细可变的滑动窗口完成流量操控,窗口巨细便是发送方发送但未收到承认的数据报数量
    • 慢发动:每个rtt将滑动窗口翻倍。
    • 拥塞操控对链接是独立的
    • 但拥塞操控会导致tcp队头堵塞(tcp有必要接纳到完好正确次序的数据包后才干提交给上层),使得单路 http/2 的速度没有多路 http/1 的快
  • TCP通讯进程太杂乱而且开支大,一次TCP交换需求9个包: 三个衔接包,四个断开包,一个request包,一个呼应包。

  • UDP通讯进程简略,只需求一个查询包和一个呼应包。

tcp三次握手树立衔接

  1. 发送方恳求树立衔接Syn报文,syn方位1(表明链接树立恳求) ack方位0,seq number =x
  2. 接纳方承认恳求 syn方位1,ack方位1,seq number = y ack number = x+1
  3. 发送方承认的承认 ack number = y+1
  • 为啥不能两次:防止超时的衔接恳求报文抵达服务器再次树立衔接。

tcp四次挥手开释衔接

  • 4次挥手:发送方恳求开释衔接(Fin报文)-> 接纳方承认(ACK置1)-> 接纳方恳求开释衔接(Fin报文)-> 发送方承认-客户端等候 2MSL(报文最大生存时刻) 的时刻后仍然没有收到回复(服务端没收到ack,则服务端会从头发送fin),则证明服务端已正常封闭,那么客户端也能够封闭衔接了
  • 为啥挥手要四次,由于TCP全双工,客户端恳求开释衔接时,只表明客户端没东西发了,但服务器还有数据要回来。

tcp粘包,tcp分包

  1. 半包:假如数据包太大,导致服务器没有接纳完好的包
  2. 粘包:tcp根据字节省,不关怀上层传输的详细内容,在一个tcp报文中或许存在多个http包(发送端粘包:http包太小,tcp为了进步功率,所以粘包,接纳端粘包:接纳端没有及时处理接纳缓冲区的数据,读取时呈现粘包)
  3. 分包:tcp根据字节省,tcp不关怀上层传输的详细内容,一个大的http包或许被分在多个tcp报文上(发送http太大)
  • 粘包分包处理方案:定长音讯,用特别字符符号音讯边界,将音讯长度写在音讯头中

tcp心跳包

  • 通讯两边处于idle状况时确保长链接的有用性,需求发送的特别数据包给对方(ping),接纳方给予回复(pong)
  • tcp自带心态机制SO_KEEPALIVE,但不行灵敏,所以在运用层上完成心跳
  • Netty 运用 IdleStateHandler 根据超时时刻监听读写事情,若产生超时则会触发回调,这个时分能够发送心跳包

socket

  • 套接字 = {传输层协议,源地址,源端口,方针地址,方针端口},其间协议能够是tcp或udp,是不同主机进程间通讯的端点

udp

  • 用户数据包协议
  • UDP供给的是无衔接 无承认 不牢靠服务的点到多点传输协议
  • udp是根据报文的
  • 发送前无需握手,发送完无需开释衔接,传输功率高
  • 每个数据包独立发送,不同数据包或许传输途径或许不同
  • 没有拥塞操控
  • 有过失校验,对udp头部和数据段都进行校验,服务端经过校验和发现出错时直接丢掉
  • udp 依靠网络层的ip,udp数据包被包在ip数据包外层

5. 运用层

https

  • https 是安全的 http,它 = http + ssl(Secure Sockets Layer)
  • 是运用层协议,处理怎么封装数据
  • 无状况协议,服务器对用户操作没有回忆
  • http 1.1 开端有keep-alive,坚持链接,网页翻开后,客户端和服务器的衔接不会断开而是坚持一段时刻,为了功率(Connection:keep-alive,恳求头部的该字段决议了链接是否会复用)
  • 明文通讯,或许被偷听;不验证身份,或许被劫持;无法验证报文完好性,或许被篡改。
  • HTTP协议运用默许80端口,HTTPS协议运用443端口
  • http1.1 新增了pipeline,多个http资源能够并发地在一条tcp链接上发送(发送方不需求等候榜首个资源承认了才发送第二个资源)。但接纳方只能串行的处理呼应,一个慢呼应会堵塞一切快恳求向上层提交(管道处理了恳求的队头堵塞)
  • 证书: 是服务器下发给客户端的,客户端用证书验证服务端身份。证书需求购买
  • 证书包括:认证机构(CA)信息,公钥,域名,有用期,指纹(对证书进行hash运算,即证书摘要),指纹算法,数字签名(CA私钥加密的指纹)等

HTTP2.0

  • 1.0 每个http恳求都要从头树立一条tcp链接,完毕时要封闭链接,暂时链接。
  • 1.0 不紧缩header,且每次通讯都要重复发送head
  • 1.0 不支撑恳求优先级
  • 1.0 有必要串行的地完成地发送资源(构成队头堵塞)
  • 1.1 允许耐久链接,接纳方只能串行地处理不同恳求,两个恳求生命周期不能重叠,由于接纳方无法承认数据的开端和完毕(有用负荷字段写在header中),这会构成队头堵塞,多个并行恳求需树立多条 tcp链接,无法复用。封闭链接只需在头部带上Connection:Close
  • 2.0 支撑header紧缩,通讯两边缓存一个 header field 表,防止重复 header 传输
  • 2.0 多路复用,将数据流分解成更小的帧(经过在头部廷加stream id,和帧巨细),不同数据流的帧能够交错在一条tcp衔接上发送,再根据所属流从头拼装,完成了多恳求并行传输的作用(时刻片),处理了http层的队头堵塞(减轻了服务端的压力,每个客户端只树立了一条链接,服务器能够给更多的客户端树立衔接)
  • 2.0 支撑优先级

加密解密

加密算法分为两类:对称加密和非对称加密。

  • 对称加密:加密和解密用的都是相同的秘钥,长处是速度快,缺陷是安全性低。常见的对称加密算法有DES、AES等等。
  • 非对称加密:非对称加密有一个秘钥对,分为公钥和私钥。一般来说,私钥自己持有,公钥能够公开给对方,长处是安全性比对称加密高,缺陷是数据传输功率比对称加密低。选用公钥加密的信息只要对应的私钥能够解密。常见的非对称加密包括RSA等。

数字摘要

  • 是明文摘要成128位密文的进程,比方MD5,SHA1

数字签名

  • 是用于验证信息完好性的和身份验证。
  • 发送方将内容摘要并用私钥加密并发送,接纳方用公钥解密摘要,再对原文求摘要,比对两个摘要,若相同则未被篡改

数字证书

  • 是为了处理公钥相信的问题、

TLS

  • 是 ssl3.0 的后续版本
  • 分为 tls记载和tls握手
  • tls 完成了加密数据,验证数据完好性,认证身份

tls握手进程

是一个借助于数字证书协商出对称加密密钥的进程

  1. 客户端宣布恳求,说明支撑的协议,客户端生成的随机数,支撑的加密办法
  2. 服务端回来证书,服务端生成的随机数
  3. 客户端验证证书
  4. 客户端运用证书中的公钥加密另一个新得随机数。并发送给服务器
  5. 生成会话密钥:客户端和服务器分别用三个随机数生成相同的对称密钥
  6. 服务器告知握手完毕,之后就经过对称密钥通讯
  • 验证进程:
  1. 客户端 TLS 解析证书
  2. 证书是否过期
  3. CA是否牢靠(查询信赖的本地根证书)
  4. 证书是否被篡改(用户运用CA根公钥解密签名得到原始指纹,再对证书运用指纹算法得到新指纹,两指纹若不一样,则被篡改)
  5. 服务器域名和证书上的域名是否匹配

QUIC

  • quic树立在UDP之上,但完成了牢靠传输,它更应是TCP 2.0,它包括tcp的一切特性 :牢靠性,拥塞操控,流量操控。
  • quic 将 http2的流和帧的概念下移到了传输层,给每个数据流一个stream id,以及盯梢该字节省的字节范围(比方包1是从0-200,包2是从201-300),这将不能确保数据包的有序性,单个资源流的有序,多个流的次序无法确保(比方服务器发送资源1.1-1.2-2,接纳方的次序或许是2-1.1-1.2),

队头堵塞

  • 一个大的(慢的)呼应会堵塞这今后边的呼应。
  • http1.0 经过多个http链接缓解该问题
  • http2.0回到单个 TCP 衔接,处理队头堵塞问题。这在 HTTP/1.1 中是不或许的,由于没有办法分辨一个块属于哪个资源,或许它在哪里完毕,另一个块从哪里开端。HTTP/2 非常优雅地处理了这一问题,它在资源块之前增加了帧(frames)
  • http2.0处理了http层的队头堵塞。但还有tcp队头堵塞,当产生丢包,tcp会先将失序数据存在缓冲区,待重传数据到来时才依照正确的次序提交给上层,此刻丢掉的包会堵塞后续包提交给上层。
  • quic 将http2 流和帧的概念下移到了传输层,处理了 tcp队头堵塞
  • tls队头堵塞:tls加解密是整块进行的,tls记载或许涣散在多个tcp包上,若tcp丢包则tls队头堵塞,quic的处理方案是将加解密涣散处理,这样会拖慢加解密速度。

一次网络恳求

  1. 恳求dns服务器解析ip地址
  2. 三次握手树立TCP链接
  3. tls握手
  4. 恳求内容封装成http报文—tcp分包 在链路上发送出去
  5. 服务器解析报文呼应
  6. 封闭链接,四次握手

网络优化

  1. 恳求预热:发送无body的head恳求,提早树立好tcp,tls链接,省掉dns,tcp,tls时刻
  2. 共同域:不同的事务的域名在客户端宣布恳求之前进行兼并(由于若域名不同,恳求不同事务时都需求dns解析,且都需求树立不同的tcp链接),运用共同的域,将恳求不同的部分往后挪到接口部分,恳求抵达后端SLB后进行域名还原。okhttp链接复用最多坚持5个闲暇链接(经过调整最大闲暇恳求数,一个connection在内存中5k)
  3. 有了共同的域之后,能够进行网络嗅探,择优进行IP直连,app发动时,拉取域对应的ip列表,并对一切ip进行嗅探ping接口,挑选其间最优的ip 最为后续恳求的直连ip,不需求进行dns解析
  4. 关于牢靠性要求高的恳求,先入库,失利后重试
  5. 网络切换时,自动封闭缓存池中现有的链接(客户端网络地址产生改动,原先的链接失效)
  6. 削减数据传输量,protocolBuffer,图片紧缩,webp,恳求适宜巨细的图片
  7. 无网环境下, 增加强制缓存的阻拦器,对恳求增加cache-control:max-age:1年

OkHttp & Retrofit

Retrofit

  • Retrofit 是一个 RESTful 的 HTTP 网络恳求框架的封装

  • Retrofit将Http恳求笼统成预界说恳求接口,运用运转时注解装备恳求参数,经过动态署理生成恳求方针 (调用Call.Factory生成OkHttp.call),并将response转化成事务数据

  • 运用建造者形式,构建retrofit实例

  • 运用工厂形式:Convert.Factory构建序列化/反序列化工厂,将ResponseBody转化成事务层数据,将恳求转化成一个requestBody

  • 运用装修者形式:经过装修者形式将呼应回调抛到主线程,真实建议恳求的是OkhttpCall,他外面又套了一层 ExecutorCallbackCall 扩展了该功用

  • 运用了外观形式,create(),躲藏了动态署理生成接口实例,经过Call.Factory生成恳求的细节

  • Retrofit.crate()将接口恳求动态署理给了ServiceMethod的invoke办法(查找接口对应的ServiceMethod方针(没找到就当场运用反射遍历接口中的注解,并生成ServiceMethod方针对应一个事务接口,接口中的参数都会成为它的成员变量,存在ConcurrentHashMap中,键是Method)),在该办法中生成retrofit的call方针(内部会生成Okhttp3的call方针并建议同步或异步恳求),并调用CallAdapter将call适配成response(CallAdapter,它担任将retrofit.call 转化成事务层喜欢的消费办法(比方 observable,suspend办法))

  • 特色

  1. 链接池,HTTP/2复用衔接
  2. 默许支撑GZIP,告知服务器支撑gzip紧缩格式,恳求增加Accept-Encoding: gzip,呼应中回来Content-Encoding: gzip(运用哈夫曼算法,重复度越高紧缩作用越好)
  3. 呼应缓存
  4. 方便增加阻拦器

OkHttp 调度器 Dispatcher

  • 在 OkHttpClient.build 中构建
  • 维护三个行列和一个线程池来并发处理网络恳求,分别是同步运转行列,正在运转的异步行列,等候恳求异步行列
  • 持有 ExecutorService ,中心线程数为0,表明不保存闲暇线程。最大线程数为 Int.max,表明随时会新建线程,运用同步行列,使得恳求的出产不会被堵塞

五大阻拦器

0. 运用阻拦器必定会被履行一次

1. RetryAndFollowUpInterceptor

重试重定向阻拦器
这个阻拦器是一个while(true)的循环,只要恳求成功或许重试超越最大次数,没有路由供重试时才会退出

恳求抛出反常并满足重试条件时才重试,收到3xx,需求重定向时会从头构建恳求

2. BridgeInterceptor

  • 将http request加工,增加header 头字段(Connection:keep-alive,Accept-Encoding:Gzip),再将http response 加工 去掉 header

3. CacheInterceptor

  • 缓存阻拦器
  • 从DiskLruCache根据恳求url获取缓存Response,然后根据一些http头约好的缓存战略决议是运用缓存呼应仍是建议新的恳求。
  • 只缓存 get 恳求
  • 缓存是空间换时刻的办法,缓存需求页面置换算法(LRU,FIFO)
  • 缓存减小服务器压力,客户端更快地显现数据,无网下显现数据
  • 缓存分为强制缓存和比照缓存
    1. 客户端直接拿数据,若缓存未射中则恳求网络并更新数据
    2. 客户端拿数据标识,总是查询网络判别数据是否有用
  • 呼应头中包括Cache-Control:max-age,表明缓存过期时刻
  • 呼应头中有Last-Modified字段标识资源最后被修正时刻,客户端下次建议恳求时在恳求头中会带上If-Modified-Since,服务器比对假如最后修正时刻大于该值则回来200,不然304标识缓存有用。
  • 除了用最后修正时刻做判别,还能够用资源仅有标识来判别ETag/If-None-Match,呼应头包括ETag,再次恳求时带上If-None-Match,服务器比对标识是否相同,相同则304,不然200
  • 缓存战略:先判别缓存是否过期,若未过期则直接运用,若过期则建议恳求,恳求头带仅有标识,服务器回200或304,假如没有仅有标识则恳求头带上次修正时刻,服务器200或304
  • 在无网环境下便是缓存过期,仍然运用缓存,要增加 运用阻拦器,重构request修正cache-control字段为FORCE_CACHE:
public class ForceCacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request.Builder builder = chain.request().newBuilder();
        if (!NetworkUtils.internetAvailable()) {
            builder.cacheControl(CacheControl.FORCE_CACHE);
        }
        return chain.proceed(builder.build());
    }
}
okHttpClient.addInterceptor(new ForceCacheInterceptor());
  • 若服务器不支撑header头缓存字段,则能够增加网络阻拦器,在CacheInterceptor收到呼应之前修正response的header
public class CacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        Response response1 = response.newBuilder()
                .removeHeader("Pragma")
                .removeHeader("Cache-Control")
                //cache for 30 days
                .header("Cache-Control", "max-age=" + 3600 * 24 * 30)
                .build();
        return response1;
    }
}

4. ConnectInterceptor

  • 衔接阻拦器
  • 树立衔接及衔接上的流
  • 维护衔接池,以复用衔接
  • 一个物理链接上有多个流(逻辑上的恳求呼应对),一个物理链接上的多个流是并发的,但有数量约束,一个流上有多个分配,分配是并发的
  • 获取衔接流程:
  1. 复用已分配的衔接(重定向再次恳求)
  2. 无已分配链接,则从链接池那一个新得链接,经过主机名和端口(并不是池中的链接就能复用,除了host之外的字段要都持平,比方dns,协议,署理)
  3. 测验其他路由再从衔接池中获取衔接,若找到则进行dns查询
  4. 假如缓存池中没有链接,则新建链接(tcp+tls握手,sockect.connect+connectTls),这是耗时的,进程中或许有衔接池或许有新的可用衔接 所以再次测验从衔接池获取衔接,假如成功则开释刚树立的链接,不然把新建衔接入池
衔接复用
  • tcp衔接树立需求三次握手和四次挥手
  • 衔接池完成链接缓存,完成同一地址的链接复用
  • 衔接池以行列办法存储链接ArrayDeque,链接池中同一个地址最多维护5个闲暇链接,闲暇链接最多存活5分钟
衔接整理
  • 五分钟守时使命,每五分钟遍历一切链接,并找到其间闲暇时刻最长的,假如闲暇时刻超越keep-alive(5分钟),或许闲暇链接超越了阈值(5个)则铲除这个链接

4.x NetworkInterceptor

  • 网络阻拦器
  • 在衔接树立完成和发送恳求之间
  • 或许不被调用,比方缓存射中,或许屡次调用重定向

5. CallServerInterceptor

  • 恳求阻拦器
  • 将恳求和呼应分装成 http2 的帧,经过Http2ExchangeCodec(内部经过okio完成io)
  • 1 写入恳求头 – 2 写入恳求体 – 3 读取呼应头 – 4 读取呼应体
    假如呼应头中 Connection:close,则在当时链接上设置标志位,表明该链接不能再被复用

RealCall

  • 怎么检测重复恳求:运用一个 AtomicBoolean 作为恳求过的标志位,每次履行 execute之前就会查看
  • 怎么建议恳求:
  1. 恳求被封装成 RealCall 方针,异步恳求会进一步会封装成一个 Runnable
  2. 同步恳求直接将恳求在阻拦器职责链上传递(并加到同步恳求行列汇总)
  3. 异步恳求会缓存到一个预备恳求行列中,并查看当时并发恳求数(同一个域最多5个并发,不同域最多64个),若未超阈值,则将恳求出队入线程池履行(将恳求在职责链上传递)
    同一链接上的最大并发数据流是Int.max

恳求怎么在职责链上传递

职责链持有一组阻拦器和当时阻拦器索引,经过每次复制一条新职责链且索引+1,完成传递
建议恳求并获取呼应便是在恳求和呼应在职责链上u型传递的进程

Glide

特色

  1. 会根据控件巨细进行下采样,以解码出契合需求的巨细,对内存更友好
  2. 内存缓存+磁盘缓存
  3. 感知生命周期,撤销使命,防止内存走漏
  4. 感知内存吃紧,进行收回
  5. BitmapPool,防止内存颤动的进行bitmap改换
  6. 界说恳求优先级

手写一个图片库注意事项

  1. 获取资源:异步并发下载图片,最大化运用cpu资源
  2. 资源解码:按实践需求异步解码,多线程并发是否能加速解码速度
  3. 资源改换:运用资源池,复用改换的资源,防止内存颤动
  4. 缓存:磁盘缓存原始图片,或改换的资源。内存缓存刚运用过的资源,运用lru战略操控巨细
  5. 感知生命周期:防止内存走漏
  6. 感知内存吃紧:整理缓存

Glide 数据加载流程

  • RequestBuilder 构建 Request和 Target,将恳求委托给RequestManager,RequestManager触发Request.begin(),然后调用Engine.load()加载资源,若有内存缓存则回来,不然发动异步使命加载磁盘缓存,若无则从网络加载
  • DecodeJob 担任加载数据(或许从磁盘,或网络,onDataFetcherReady),再进行数据解码(onDataFetcherReady),再进行数据改换(Transformation),写ActiveResource,(将改换后的数据回调给Target),将改换后的资源写文件(ResourceEncoder)

预加载

preload,加载到一个PreloadTarget,等资源加载好了,就调用clear,将资源从ActiveResource移除存到Lrucache中

感知内存吃紧

注册ComponentCallbacks2,完成细粒度内存办理:

  1. onLowMemory(){铲除内存}
  2. onTrimMemory(){修剪内存}
    memoryCache.trimMemory(level); // 内存缓存
    bitmapPool.trimMemory(level); // bitmap池
    arrayPool.trimMemory(level); // 字节数组池

能够设置在onTrimMemory时,撤销一切正在进行的恳求。

BitmapPool

  • BitmatPool 是 Glide 维护了一个图片复用池,LruBitmapPool 运用 Lru 算法保存最近运用的尺度的 Bitmap。
  • api19 后运用bitmap的字节数和config作为key,而之前运用宽高和congif,所以19今后复用度更高
  • 用类似LinkedHashMap存储,键值对中的值是一组Bitmap,相同字节数的Bitmap 存在一个List中(这样规划的意图是,将Lru战略运用在Bitmap巨细上,而不是单个Bitmap上),操控BitmapPool巨细经过删去数据组中最后一个Bitmap。
  • BitmapPool 大部分用于Bitmap改换和gif加载时

ArrayPool

  • 是一个选用Lru战略的数组池,用于解码时分的字节数组的复用。
  • 整理内存意味着整理MemoryCache,BitmapPool,ArrayPool

缓存

默许状况下,Glide 会在开端一个新的图片恳求之前查看以下多级的缓存:

  • 活动资源 (Active Resources) – 现在是否有另一个 View 正在展现这张图片?
  • 内存缓存 (Memory cache) – 该图片是否最近被加载过并仍存在于内存中?
  • 资源类型(Resource) – 该图片是否之前曾被解码、转化并写入过磁盘缓存?
  • 数据来历 (Data) – 构建这个图片的资源是否之前曾被写入过文件缓存?

在 Glide v4 里,一切缓存键都包括至少两个元素
活动资源,内存缓存,资源磁盘缓存的缓存键还包括一些其他数据,包括:
必选:Model
可选:签名
宽度和高度
可选的改换(Transformation)
额外增加的任何 选项(Options)
恳求的数据类型 (Bitmap, GIF, 或其他)

磁盘缓存战略

  • 假如缓存战略是AUTOMATIC(默许),关于网络图片只缓存原始数据,加载本地资源是存储改换过的数据,假如加载不同尺度的图片,则会获取原始缓存并在此基础上做改换。
  • 假如缓存战略是ALL,会缓存原始图片以及每个尺度的副本,
  • 假如缓存战略是SOURCE,只会缓存改换过的资源,假如另一个界面换一个尺度显现图片,则会从头拉取网络
    可经过自界说Key完成操控缓存射中战略(混入自己的值,比方修正时刻)

内存缓存

  • 内存缓存分为两级
    1. 活泼图片 ActiveResource
      • 运用HashMap存储正在运用资源的弱引证
      • 资源被包装成带引证计数的EngineResource,符号引证资源的次数(当引证数不为0时阻止被收回或降级,降级便是存储到LruCache中)
      • 这一级缓存没有巨细约束,所以运用了资源的弱引证
      • 存:每逢下载资源后会在onEngineJobComplete()中存入ActiveResource,或许LruCache射中后,将资源从中LruCache移除并存入ActiveResource。
      • 取:每逢资源开释时,会降级到LruCache中(恳求对应的context onDestroy了或许被gc了)
      • 开一个后台线程,监听ReferenceQueue,不停地从中获取被gc的资源,将其从ActiveResource中移除,并从头构建一个新资源将其降级为LruCache
      • ActiveResource是为了缓解LruCache中缓存构成压力,由于LruCache中没有射中的缓存只要比及容量超限时才会被铲除,强引证即便内存吃紧也不会被gc,现在当LruCache射中后移到ActiveResource,弱引证持有,当内存吃紧时能被收回。
    2. LruCache
      • 运用 LinkedHashMap 存储从活泼图片降级的资源,运用Lru算法筛选最近最少运用的
      • 存:从活泼图片降级的资源(退出当时界面,或许ActiveResource资源被收回)
      • 取:网络恳求资源之前,从缓存中取,若射中则直接从LruCache中移除了。
  • 内存缓存只会缓存经过转化后的图片
  • 内存缓存键根据10多个参数生成,url,宽高

磁盘缓存

  • 会将源数据或经过改换的数据存储在磁盘,在内存中用LinkedHashMap记载一组Entry,Entry内部包括一组文件,文件名便是key,而且有敞开后台线程履行删去文件操作以操控磁盘缓存巨细。
  • 写磁盘缓存便是触发Writer将数据写入磁盘,并在内存构建对应的File缓存在LinkedHashMap中
  • 根据缓存战略的不同,或许存储源数据和经过改换的数据。

感知生命周期

  • 结构RequestManager时传入context,能够是app的,activity的,或许是view的
  • 向界面增加无界面Fragment(SupportRequestManagerFragment),Fragment把生命周期传递给Lifecycle,Fragment持有RequestManager,RequestManager监听Lifecycle,RequestManager向RequestTracker传递生命周期以暂停加载,RequestTracker遍历一切正在进行的恳求,并暂停他们(移除回调resourceReady回调)
  • 当绑定context destroy时,RequestManager会将该事情传递给RequestTracker,然后触发该恳求Resource的clear,再调用Engine.release,将resource降级到LruCache
  • 经过HashMap结构保存无界面Fragment以防止重复创立

撤销恳求

经过移除回调,设置撤销标志位完成:无法撤销现已宣布的恳求,会在DecodeJob的异步使命的run()办法中判别,假如cancel,则回来。移除各种回调,会传递到DataFetcher,httpUrlConnection 读取数据后会判别是否cancel,假如是则回来null。并没有断开链接

感知网络改动

  • 经过 ConnectivityManager 监听网络改动,当网络康复时,遍历恳求列表,将没有完成的使命持续开端

Transformation

  • 一切的BitmapTransformation 都是从BitmapPool 拿到一个bitmap,然后将在原有bitmap基础上运用一个matrix再画到新bitmap上。
  • 改换也是一个key,用以在缓存的时分做区别

RecycleView图片紊乱

  • 异步使命+视图复用导致
  • 处理方案:设置占位图+收回表项时撤销图片加载(或许新得加载开端时撤销旧的加载)+imageview加tag判别是否是自己的图片假如不是则先调用clear

Glide 缓存失效

  • 是由于 Key 产生改动,Url是生成key的根据,Url或许产生改动比方把token追加在后边
  • 自界说生成key的办法,承继GlideUrl重写getCacheKey()

自界说加载

  • 界说一个Model类用于包装需求加载的数据
  • 界说一个Key的完成类,用于完成榜首步的Model中的数据的签名用于区分缓存
  • 界说一个DataFetcher的完成类,用于告知Glide音频封面怎么加载,并把加载成果回调出去
  • 界说一个ModelLoader的完成类用于包装DataFetcher
  • 界说一个ModelLoaderFactory的完成类用于生成ModelLoader实例
  • 将自界说加载装备到AppGlideModule中

Glide线程池

  1. 磁盘缓存线程池,一个中心线程:用于io图片编码
  2. 加载资源线程池,最多不超越4个中心线程数,用于处理网络恳求,图片解码转码
  3. 动画线程池,最多不超越2个线程
  4. 磁盘缓存整理线程池
  5. ActiveResource 敞开一个后台线程监听ReferenceQueue
    一切线程池都默许选用优先级行列

加载Gif流程

读取流的前三个字节,若判别是gif,则会射中gif解码器-将资源解码成GifDrawable,它持有GifFrameLoader会将资源解码成一张张Bitmap而且传递给DelayTarget的方针,该方针每次资源加载完毕都会经过handler发送推迟音讯回调 onFrameReady() 以触发GifDrawable.invalidataSelf()重绘。加载下一帧时会从头构建DelayTarget

恳求优先级

经过给加载线程池装备优先级行列,加载使命DecodeJob 完成了 compareTo 办法,将priority相减

图片加载优化

  1. 服务器存多种尺度的图片
  2. 自界说 AppGlideModule,按设备功用好坏设定MemoryCategory.HIGH,LOW,NORMAL,内存缓存和bitmapPool的巨细系数,以及图片解码格式,ARGB_8888,RGB_565
  3. RecyclerView 在onViewRecycled 中调用clear ,由于recyclerView会默许会缓存5个同类表项,假如类型很多,内存中会持有表项,假如这些表项都包括图片,Glide 的ActiveResource会胀大。导致gc
  4. 假如 RecyclerView 包括一个很长的itemView,超越一屏,其间包括很多相片,最好把长itemView拆成多个itemView
  5. 运用thumbnail,加载一个缩略图,最好是一个独立的链接,假如是本地的也不差
  6. 运用preload,将资源提早加载到内存中。
  7. 大部分状况下 RESOURCE ,即缓存经过改换的图片上是最好挑选,节省内存和磁盘。关于gif资源只缓存原始资源DATA,由于gif是多张图每次编码解码反而耗时
  8. 运用Glide完成改换,由于有BitmapPool供复用