裸辞-疫情-闭关-复习-大厂offer(二)(下)

简介: 裸辞-疫情-闭关-复习-大厂offer(二)

五大拦截器


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. 客户端直接拿数据,若缓存未命中则请求网络并更新数据


  1. 客户端拿数据标识,总是查询网络判断数据是否有效


  • 响应头中包含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. 复用已分配的连接(重定向再次请求)


  1. 无已分配链接,则从链接池那一个新得链接,通过主机名和端口(并不是池中的链接就能复用,除了host之外的字段要都相等,比如dns,协议,代理)


  1. 尝试其他路由再从连接池中获取连接,若找到则进行dns查询


  1. 如果缓存池中没有链接,则新建链接(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


  1. 同步请求直接将请求在拦截器责任链上传递(并加到同步请求队列汇总)


  1. 异步请求会缓存到一个准备请求队列中,并检查当前并发请求数(同一个域最多5个并发,不同域最多64个),若未超阈值,则将请求出队入线程池执行(将请求在责任链上传递) 同一链接上的最大并发数据流是Int.max


请求如何在责任链上传递


责任链持有一组拦截器和当前拦截器索引,通过每次复制一条新责任链且索引+1,实现传递 发起请求并获取响应就是在请求和响应在责任链上u型传递的过程


Glide


特点


  1. 会根据控件大小进行下采样,以解码出符合需求的大小,对内存更友好


  1. 内存缓存+磁盘缓存


  1. 感知生命周期,取消任务,防止内存泄漏


  1. 感知内存吃紧,进行回收


  1. BitmapPool,防止内存抖动的进行bitmap变换


  1. 定义请求优先级


手写一个图片库注意事项


  1. 获取资源:异步并发下载图片,最大化利用cpu资源


  1. 资源解码:按实际需求异步解码,多线程并发是否能加快解码速度


  1. 资源变换:使用资源池,复用变换的资源,避免内存抖动


  1. 缓存:磁盘缓存原始图片,或变换的资源。内存缓存刚使用过的资源,使用lru策略控制大小


  1. 感知生命周期:避免内存泄漏


  1. 感知内存吃紧:清理缓存


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(){清除内存}


  1. 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,弱引用持有,当内存吃紧时能被回收。


  1. 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图片编码


  1. 加载资源线程池,最多不超过4个核心线程数,用于处理网络请求,图片解码转码


  1. 动画线程池,最多不超过2个线程


  1. 磁盘缓存清理线程池


  1. ActiveResource 开启一个后台线程监听ReferenceQueue 所有线程池都默认采用优先级队列


加载Gif流程


读取流的前三个字节,若判断是gif,则会命中gif解码器-将资源解码成GifDrawable,它持有GifFrameLoader会将资源解码成一张张Bitmap并且传递给DelayTarget的对象,该对象每次资源加载完毕都会通过handler发送延迟消息回调 onFrameReady() 以触发GifDrawable.invalidataSelf()重绘。加载下一帧时会重新构建DelayTarget


请求优先级


通过给加载线程池配置优先级队列,加载任务DecodeJob 实现了 compareTo 方法,将priority相减


图片加载优化


  1. 服务器存多种尺寸的图片


  1. 自定义 AppGlideModule,按设备性能好坏设定MemoryCategory.HIGH,LOW,NORMAL,内存缓存和bitmapPool的大小系数,以及图片解码格式,ARGB_8888,RGB_565


  1. RecyclerView 在onViewRecycled 中调用clear ,因为recyclerView会默认会缓存5个同类表项,如果类型很多,内存中会持有表项,如果这些表项都包含图片,Glide 的ActiveResource会膨胀。导致gc


  1. 如果 RecyclerView 包含一个很长的itemView,超过一屏,其中包含很多照片,最好把长itemView拆成多个itemView


  1. 使用thumbnail,加载一个缩略图,最好是一个独立的链接,如果是本地的也不差
  2. 使用preload,将资源提前加载到内存中。


  1. 大部分情况下 RESOURCE ,即缓存经过变换的图片上是最好选择,节约内存和磁盘。对于gif资源只缓存原始资源DATA,因为gif是多张图每次编码解码反而耗时
  2. 使用Glide实现变换,因为有BitmapPool供复用


目录
相关文章
|
存储 搜索推荐 Linux
秋招简历项目这样写,offer拿到手软(C++方向)
秋招简历项目这样写,offer拿到手软(C++方向)
|
7月前
|
设计模式 算法 NoSQL
Java开发三年四面字节跳动复习一个月斩获offer,寒冬并不可怕
目前互联网行业形势越来越严峻,我接连投递了很多的简历,得到的回复却是寥寥无几,索性好好复习了大概一个半月的样子,挑战字节跳动成功!!接下来分享我在字节面试遇到的面试题,欢迎大家文末留言与我一起讨论!
|
7月前
|
消息中间件 Dubbo Java
疫情下的机遇,阿里直招怒斩"P7"offer,自曝狂啃六遍的面试笔记
工作肯定会找的,面试肯定要过的,小编在这里为大家整理了我的一位朋友,一位从中游公司跳槽到阿里P7的面试题库
|
存储 缓存 编解码
裸辞-疫情-闭关-复习-大厂offer(一)(中)
裸辞-疫情-闭关-复习-大厂offer(一)
85 0
|
存储 缓存 安全
裸辞-疫情-闭关-复习-大厂offer(一)(下)
裸辞-疫情-闭关-复习-大厂offer(一)
86 0
|
存储 缓存 网络协议
裸辞-疫情-闭关-复习-大厂offer(二)(中)
裸辞-疫情-闭关-复习-大厂offer(二)
113 0
|
存储 设计模式 算法
裸辞-疫情-闭关-复习-大厂offer(二)
裸辞-疫情-闭关-复习-大厂offer(二)
131 0
|
存储 消息中间件 前端开发
裸辞-疫情-闭关-复习-大厂offer(一)
裸辞-疫情-闭关-复习-大厂offer(一)
110 0
|
存储 XML 安全
面试来临之际,就是你斩Offer之时!
本文主要介绍 面试中高频出现的问题
132 0
|
Arthas NoSQL 算法
二本院校学弟大二开始实习,大三收割阿里、腾讯实习offer
二本院校学弟大二开始实习,大三收割阿里、腾讯实习offer
二本院校学弟大二开始实习,大三收割阿里、腾讯实习offer