Android-Enthauptungsoperation - Schnittstellenvorabanforderung

Vorwort

Entwickler sollten mit dem Rendering-Prozess unserer Seiten vertraut sein. Im Allgemeinen beginnt es mit Activity#onCreate, initiiert dann eine Netzwerkanfrage und rendert die Seite basierend auf Netzwerkdaten, nachdem die Anfrage zurückgerufen wurde. Das folgende Diagramm kann verwendet werden, um diesen Prozess grob zu beschreiben:

Bild.png

Es ist ersichtlich, dass die Zielseite auf die Netzwerkanforderung warten muss, bevor die Wiedergabe der Zielseite abgeschlossen ist, sodass die Wiedergabegeschwindigkeit nicht so hoch ist. Besonders wenn das Netzwerk nicht gut ist, wird das Gefühl deutlicher. Wenn die Zielseite eine H5-Seite oder eine Flutter-Seite ist, ist die Whitescreen-Zeit außerdem länger, da sie die Erstellung des H5-Containers und des Flutter-Containers umfasst.

Ist es also möglich, eine Anfrage im Voraus zu initiieren, um die Wartezeit für diesen Teil der Netzwerkanfrage zu verkürzen? Dies ist der Teil, über den wir heute sprechen werden, Schnittstellen-Voranfrage.

Ziel

Das Ziel, das wir erreichen möchten, ist sehr einfach, nämlich die Netzwerkanforderung der Zielseite asynchron im Voraus zu initiieren, um die Wiedergabegeschwindigkeit der Zielseite zu beschleunigen. Der verbesserte Prozess kann durch das folgende Diagramm dargestellt werden:

Bild.png

Darüber hinaus muss unsere Pre-Request-Funktion so wenig wie möglich in das Geschäft eindringen, es vom Geschäft entkoppeln und die Vielseitigkeit der Funktion sicherstellen, die auf jede Seite im Projekt anwendbar ist (Android-Seite, H5-Seite, Flutter-Seite ).

planen

Gesamtverknüpfung

Lassen Sie mich Ihnen zunächst den allgemeinen Link zeigen. Sie müssen die spezifischen Details nicht ausgraben, und ich werde sie im Folgenden einzeln erläutern.

Bild.png

Timing vor der Anfrage

Es gibt im Allgemeinen drei Optionen für das Timing vor der Anfrage:

  1. Asynchrone Vorabanforderungen werden von der Geschäftsschicht nach eigener Wahl gestellt
  2. Asynchrone Vorabanforderung, wenn auf das Steuerelement geklickt wird
  3. Asynchrone Voranfrage vor dem letzten Sprung der Route

Die erste Option besteht darin, dass die Geschäftsschicht das Timing für die Vorabanforderung auswählt, was die Transformation der Geschäftsschicht und das Verständnis der Rationalität des Timings erfordert. Einerseits entstehen Transformationskosten, andererseits kann die Rationalität des Call-Timings auf der Business-Seite nicht garantiert werden.

Die zweite Option besteht darin, vorab anzufordern, wenn auf das Steuerelement geklickt wird. Wenn beim Klicken eine Voranfrage gestellt wird, ist die Klickereignisüberwachung in der Geschäftsdomäne nicht vereinheitlicht, und eine effektive Kapselung kann nicht gebildet werden. Darüber hinaus verliert diese Vorabanforderung ihre Bedeutung, wenn der nachfolgende Routenabfanger die Parameter modifiziert oder den Sprung beendet.

因此这里我们选择第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若为 R Ö In T Es ist . P A R A M Format, dann wird der Wert von der Route erhalten; wenn ja 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状态下一节再讲。

为什么要记录这个状态呢?因为我们无法保证,预请求的响应一定在实际请求之前。用图来表示:

Bild.png

因为预请求是一个并发行为。当预请求的空中时间特别长,长到目标页面已经发出实际请求了,预请求的响应还没回来,即预请求状态为START,而非FINISH。那么此时该怎么办?我们就需要让实际请求在一旁等着(记录到内存中,RealRequestRecord),等预请求接收到响应了,再根据requestId去进行匹配,匹配到RealRequestRecord了,就触发RealRequestRecord中的回调,返回数据。

另外,在匹配过程中需要注意一点,因为每次路由跳转,如果发起预请求了,总会生成一个Record在内存中等待匹配。因此在匹配结束后,不管是匹配成功还是匹配失败,都要及时释放将Record从内存中释放掉。

超时重试机制

基于实际请求等待预请求响应的场景,我们再延伸一下。若预请求请求超时,迟迟拿不到响应,该怎么办?用图表示:

Bild.png

假设目前的网络请求,端上默认的超时时间是30s。那么在超时场景下,实际的业务请求在30s内若拿不到预请求的结果,就需要重新发起业务请求,抛弃预请求,并将预请求的状态置为ABORT,这样即使后面预请求响应回来了也不做任何处理。

Bild.png

忽然想到一个很贴切的场景来比喻这个预请求方案。

我们把跳转页面理解为去柜台取餐。

预请求代表着我们人还没到柜台,就先远程下单让柜员去准备食物。

如果柜员准备得比较快,那么我们到柜台后就能直接把食物拿走了,就能快点吃上了(代表着页面渲染速度变快)。

如果柜员准备得比较慢,那么我们到柜台后还是得等一会儿才能取餐,但总体上吃上食物的速度还是要比到柜台后再点餐来得快。

Aber wenn der Kassierer passiv und langsam in der Zubereitung ist und wir lange an der Theke gewartet haben, aber das Essen nicht bekommen haben, dann können wir nur zu einem anderen Kassierer wechseln und neu bestellen (die eigentliche Geschäftsanfrage nach der Zeitüberschreitung einleiten) , und gleichzeitig vergessen wir nicht zu reklamieren.

Zusammenfassen

Durch diesen Artikel wissen wir, was eine Schnittstellen-Vorabanforderung ist und wie eine Schnittstellen-Vorabanforderung implementiert wird. 配置文件Durch ++ haben wir eine leichtgewichtige Pre-Request-Lösung realisiert, die vom Geschäft entkoppelt ist und auf jede Seite angewendet werden kann, wodurch die Rendering-Geschwindigkeit der Seite verbessert wird 统一路由处理.预请求发起、匹配、回调

Supongo que te gusta

Origin juejin.im/post/7203615594390732855
Recomendado
Clasificación