前言

开发同学应该都很了解咱们页面的烘托进程一般是从Activity#onCreate开端,再建议网络恳求,等恳求回调回来后,再基于网络数据烘托页面。能够用下面这幅图来大略描绘这个进程:

Android斩首行动——接口预请求

能够看到,方针页面烘托完成前必须得等候网络恳求,导致烘托速度并没有那么快。尤其是当网络并不好的时分感受会愈加明显。而且,当方针页面是H5页面或者是Flutter页面的时分,由于涉及到H5容器与Flutter容器的创立,白屏时刻会更长。

那么有没有或许提早建议恳求,来缩短网络恳求这一部分的等候时刻呢?这便是咱们今天要讲的部分,接口预恳求。

方针

咱们要达到的方针很简单,便是提早异步建议方针页面的网络恳求,然后加快方针页面的烘托速度。改善后的进程能够用下图表明:

Android斩首行动——接口预请求

而且,咱们的预恳求能力需求尽量少地侵入事务,与事务解耦,并保证能力的通用性,适用于工程内的任意页面(Android页面、H5页面、Flutter页面)。

计划

全体链路

首要给大家看一下全体链路,具体的细节能够先不用去抠,下面会一一讲到。

Android斩首行动——接口预请求

预恳求机遇

预恳求机遇一般有三种挑选:

  1. 由事务层自行挑选机遇进行异步预恳求
  2. 点击控件时进行异步预恳求
  3. 路由终究跳转前进行异步预恳求

第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这个预恳求,预恳求类型prefetchTypenetwork,是一个网络预恳求,prefetchInfo中具有了恳求的基本参数(如apiName、apiVersion、method、恳求params与恳求headers,不同工程不一样,大家能够依据自己的工程项目进行修改)。具体看params中,有一个参数为itemId:$route.itemId。以$route.最初的意思,便是这个value值要从路由中获取,即itemId=123,那么这个值便是123。

图片预恳求

在做网络预恳求的进程中,我忽然想到图片做预恳求也是能够大大提高用户体会的,尤其是当大图片初次下载到内存中烘托需求的时刻会比较长。图片预恳求分为url已知url不知道两种场景,下面各举两个例子。

图片url已知

什么是图片url已知呢?比方咱们在主页跳转主页的二级页面时,假如二级页面需求预加载的图片跟主页的某张图是一样的(尺度或许不同),那么主页跳转路由时咱们是能够提早知道这个图片的url的,所以咱们看到prefetchKey3中装备了prefetchTypeimage的预恳求。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状况下一节再讲。

为什么要记载这个状况呢?由于咱们无法保证,预恳求的呼应一定在实践恳求之前。用图来表明:

Android斩首行动——接口预请求

由于预恳求是一个并发行为。当预恳求的空中时刻特别长,长到方针页面现已宣布实践恳求了,预恳求的呼应还没回来,即预恳求状况为START,而非FINISH。那么此刻该怎样办?咱们就需求让实践恳求在一旁等着(记载到内存中,RealRequestRecord),等预恳求接收到呼应了,再依据requestId去进行匹配,匹配到RealRequestRecord了,就触发RealRequestRecord中的回调,回来数据。

别的,在匹配进程中需求注意一点,由于每次路由跳转,假如建议预恳求了,总会生成一个Record在内存中等候匹配。因此在匹配完毕后,不管是匹配成功仍是匹配失利,都要及时开释将Record从内存中开释掉。

超时重试机制

基于实践恳求等候预恳求呼应的场景,咱们再延伸一下。若预恳求恳求超时,迟迟拿不到呼应,该怎样办?用图表明:

Android斩首行动——接口预请求

假定现在的网络恳求,端上默认的超时时刻是30s。那么在超时场景下,实践的事务恳求在30s内若拿不到预恳求的成果,就需求从头建议事务恳求,扔掉预恳求,并将预恳求的状况置为ABORT,这样即便后面预恳求呼应回来了也不做任何处理。

Android斩首行动——接口预请求

忽然想到一个很恰当的场景来比喻这个预恳求计划。

咱们把跳转页面理解为去货台取餐。

预恳求代表着咱们人还没到货台,就先长途下单让柜员去预备食物。

假如柜员预备得比较快,那么咱们到货台后就能直接把食物拿走了,就能快点吃上了(代表着页面烘托速度变快)。

假如柜员预备得比较慢,那么咱们到货台后仍是得等一会儿才干取餐,但总体上吃上食物的速度仍是要比到货台后再点餐来得快。

但假如这个柜员消极怠工预备得太慢了,咱们到货台等了很久都没拿到食物,那么咱们就只能换个柜员从头点了(超时后建议实践的事务恳求),一同还不忘投诉一把(预恳求空中时刻太慢了)。

总结

经过这篇文章,咱们知道了什么是接口预恳求,怎样完成接口预恳求。咱们经过装备文件+一致路由处理+预恳求建议、匹配、回调,完成了与事务解耦的,可适用于任意页面的轻量级预恳求计划,然后提高页面的烘托速度。