前言
开发同学应该都很了解咱们页面的烘托进程一般是从Activity#onCreate开端,再建议网络恳求,等恳求回调回来后,再基于网络数据烘托页面。能够用下面这幅图来大略描绘这个进程:
能够看到,方针页面烘托完成前必须得等候网络恳求,导致烘托速度并没有那么快。尤其是当网络并不好的时分感受会愈加明显。而且,当方针页面是H5页面或者是Flutter页面的时分,由于涉及到H5容器与Flutter容器的创立,白屏时刻会更长。
那么有没有或许提早建议恳求,来缩短网络恳求这一部分的等候时刻呢?这便是咱们今天要讲的部分,接口预恳求。
方针
咱们要达到的方针很简单,便是提早异步建议方针页面的网络恳求,然后加快方针页面的烘托速度。改善后的进程能够用下图表明:
而且,咱们的预恳求能力需求尽量少地侵入事务,与事务解耦,并保证能力的通用性,适用于工程内的任意页面(Android页面、H5页面、Flutter页面)。
计划
全体链路
首要给大家看一下全体链路,具体的细节能够先不用去抠,下面会一一讲到。
预恳求机遇
预恳求机遇一般有三种挑选:
- 由事务层自行挑选机遇进行异步预恳求
- 点击控件时进行异步预恳求
- 路由终究跳转前进行异步预恳求
第1种挑选,由事务层自行挑选机遇进行预恳求,需求涉及到事务层的改造,以及对机遇合理性的把握。一方面是存在改造成本,另一方面是无法保证事务侧调用机遇的合理性。
第2种挑选,点击控件时进行预恳求。若点击时进行预恳求,点击事件监听并不是事务域一致的,无法构成有用封装。而且,若后续路由拦截器修改了参数,或是停止了跳转,这次预恳求就失去了意义。
因此这儿咱们挑选第3种,基于一致路由结构,在路由终究跳转前进行预恳求。既保证了良好的封装性,也完成了对事务的零侵入,一同也做到了懒恳求,即用户必定要建议该恳求时才会去预恳求。这儿需求注意的是必须是在终究跳转前进行预恳求,能够理解为是路由的最后一个前置异步拦截器。
预恳求规矩装备
咱们经过本地的json文件(当然,有需求也能够上云经过装备后台下发),对预恳求的规矩进行装备,并将这份装备在App启动阶段异步读入到内存。后续在路由进程中,只有命中了预恳求规矩,才干建议预恳求。装备demo如下:
{
"routeConfig":{
"scheme://domain/path?param1=true&itemId=123":["prefetchKey"],
"route2":["prefetchKey2"],
"route3":["prefetchKey3","prefetchKey4"]
},
"prefetcher":{
"prefetchKey":{
"prefetchType":"network",
"prefetchInfo":{
"api":"network.api.name",
"apiVersion":"1.0",
"method":"post",
"needLogin":"false",
"showLoginUI":"false",
"params": {
"itemId":"$route.itemId",
"firstTime":"true"
},
"headers": {
},
"prefetchImgInResponse": [
{
"imgUrl":"$data.imgData.img",
"imgWidth":"$data.imgData.imgWidth",
"imgHeight":150
}
]
}
},
"prefetchKey2":{
"prefetchType":"network",
"prefetchInfo":{
"api":"network.api.name2",
"apiVersion":"1.0",
"method":"post",
"needLogin":"false",
"showLoginUI":"false",
"params": {
"itemId":"$route.productId",
"firstTime":"false"
},
"headers": {
}
},
"prefetchKey3":{
"prefetchType":"image",
"prefetchInfo":{
"imgUrl":"$route.imgUrl",
"imgWidth":"$route.imgWidth",
"imgHeight": 150
}
},
"prefetchKey4":{
"prefetchInfo":{}
}
}
}
规矩解读
参数名 | 描绘 | 备注 |
---|---|---|
routeConfig | 路由装备 | 装备路由到预恳求的映射 |
prefetcher | 预恳求装备 | 记载一切的预恳求 |
prefetchKey | 预恳求的key | |
prefetchType | 预恳求类型 | 分为network类型与image类型,两种类型所需求的参数不同 |
prefetchInfo | 预恳求所需求的信息 | 其间value若为route.param格局,那么该值从路由中获取;若为route.param格局,那么该值从路由中获取;若为data.param格局,则从呼应数据中获取。 |
params | network恳求所需求的恳求params | |
headers | network恳求所需求的恳求headers | |
prefetchImgFromResponse | 预恳求的呼应回来后,需求预加载的图片 | 用于需求预加载图片时,无法确认图片url,图片url只能从预恳求呼应中获取的场景。 |
举例说明
网络预恳求
例如跳转方针页面,它的路由是scheme://domain/path?param1=true&itemId=123
。
首要咱们在跳转路由时,若跳转的路由是这个方针页面,咱们就会测验去建议预恳求。依据上面的demo装备文件,它将匹配到prefetchKey
这个预恳求。
那么咱们详细看prefetchKey
这个预恳求,预恳求类型prefetchType
为network
,是一个网络预恳求,prefetchInfo
中具有了恳求的基本参数(如apiName、apiVersion、method、恳求params与恳求headers,不同工程不一样,大家能够依据自己的工程项目进行修改)。具体看params
中,有一个参数为itemId:$route.itemId
。以$route.
最初的意思,便是这个value值要从路由中获取,即itemId=123
,那么这个值便是123。
图片预恳求
在做网络预恳求的进程中,我忽然想到图片做预恳求也是能够大大提高用户体会的,尤其是当大图片初次下载到内存中烘托需求的时刻会比较长。图片预恳求分为url已知与url不知道两种场景,下面各举两个例子。
图片url已知
什么是图片url已知呢?比方咱们在主页跳转主页的二级页面时,假如二级页面需求预加载的图片跟主页的某张图是一样的(尺度或许不同),那么主页跳转路由时咱们是能够提早知道这个图片的url的,所以咱们看到prefetchKey3
中装备了prefetchType
为image
的预恳求。image的信息来自于路由参数,需求在跳转时将图片url和宽高作为路由参数之一。
比方scheme://domain/path?imgUrl=${encodeUrl}&imgWidth=200
,那么依据装备项,咱们将提早将encodeUrl
这个图片以宽200,高150的尺度,加载到内存中去。当方针页面用到这个图片时,将能很快烘托出来。
图片url不知道
相反,当跳转方针页面时,方针页面所要加载的图片url没法取到,就对应了图片url不知道的场景。
例如闪屏页跳转主页时,假如需求预加载主页顶部的图片,此刻闪屏页是无法获取到图片的url的,由于这个图片url是主页接口回来的。这种情况下,咱们只能依赖主页的预恳求进行。
在demo装备文件中,咱们能够看到prefetchImgFromResponse
字段。这个字段代表着,当这个预恳求呼应回来之后,我需求去预恳求某张图片。其间,imgUrl
是$data.param
格局,以$data.
最初,代表着这份数据是来自于呼应数据的。呼应数据便是一串json串,能够凭此,索引到预恳求呼应中图片url的位置,就能完成图片的提早加载了。
至于图片怎样提早加载到内存中,以及实在图片的加载怎样匹配到内存中的图片,这一部分是经过glide已有的preload机制完成的,感兴趣的同学能够去看一下源码了解一下,这儿就不展开了。后面讲的预恳求的计划细节,都只限于网络恳求。
预恳求匹配
预恳求匹配指的是实践的事务恳求怎样与现已执行的预恳求匹配上,然后节省恳求的空中时刻,直接回来预恳求的成果。
首要网络预恳求执行前先在内存中生成一份PrefetchRecord
,代表着现已执行的预恳求,其间的字段跟装备文件中差不多,主要便是记载预恳求相关的信息:
class PrefetchRecord {
// 恳求信息
String api;
String apiVersion;
String method;
String needLogin;
String showLoginUI;
JSONObject params;
JSONObject headers;
// 预恳求状况
int status;
// 预恳求成果
ResponseModel response;
// 生成的恳求id
String requestId;
boolean isMatch(RealRequest realRequest) {
requestId.equals(realRequest.requestId)
}
}
每一个PrefetchRecord
生成时,都会生成一个requestId
,用于跟实践事务恳求进行匹配。requestId
的生成规矩能够自行制定,比方将一切恳求信息包一同做一下md5处理之类。
在实践事务恳求建议之前,也会依据相同的规矩生成requestId
。若内存中存在相同requestId
对应的PrefetchRecord
,那么就相当于匹配成功了。匹配成功后,再依据预恳求的状况进行进一步的处理。
预恳求状况
预恳求状况分为START、FINISH、ABORT
,对应“正在建议预恳求”、“现已获得预恳求成果”、“预恳求被扔掉”。ABORT
状况下一节再讲。
为什么要记载这个状况呢?由于咱们无法保证,预恳求的呼应一定在实践恳求之前。用图来表明:
由于预恳求是一个并发行为。当预恳求的空中时刻特别长,长到方针页面现已宣布实践恳求了,预恳求的呼应还没回来,即预恳求状况为START
,而非FINISH
。那么此刻该怎样办?咱们就需求让实践恳求在一旁等着(记载到内存中,RealRequestRecord
),等预恳求接收到呼应了,再依据requestId
去进行匹配,匹配到RealRequestRecord
了,就触发RealRequestRecord
中的回调,回来数据。
别的,在匹配进程中需求注意一点,由于每次路由跳转,假如建议预恳求了,总会生成一个Record在内存中等候匹配。因此在匹配完毕后,不管是匹配成功仍是匹配失利,都要及时开释将Record从内存中开释掉。
超时重试机制
基于实践恳求等候预恳求呼应的场景,咱们再延伸一下。若预恳求恳求超时,迟迟拿不到呼应,该怎样办?用图表明:
假定现在的网络恳求,端上默认的超时时刻是30s。那么在超时场景下,实践的事务恳求在30s内若拿不到预恳求的成果,就需求从头建议事务恳求,扔掉预恳求,并将预恳求的状况置为ABORT,这样即便后面预恳求呼应回来了也不做任何处理。
忽然想到一个很恰当的场景来比喻这个预恳求计划。
咱们把跳转页面理解为去货台取餐。
预恳求代表着咱们人还没到货台,就先长途下单让柜员去预备食物。
假如柜员预备得比较快,那么咱们到货台后就能直接把食物拿走了,就能快点吃上了(代表着页面烘托速度变快)。
假如柜员预备得比较慢,那么咱们到货台后仍是得等一会儿才干取餐,但总体上吃上食物的速度仍是要比到货台后再点餐来得快。
但假如这个柜员消极怠工预备得太慢了,咱们到货台等了很久都没拿到食物,那么咱们就只能换个柜员从头点了(超时后建议实践的事务恳求),一同还不忘投诉一把(预恳求空中时刻太慢了)。
总结
经过这篇文章,咱们知道了什么是接口预恳求,怎样完成接口预恳求。咱们经过装备文件
+一致路由处理
+预恳求建议、匹配、回调
,完成了与事务解耦的,可适用于任意页面的轻量级预恳求计划,然后提高页面的烘托速度。