文章主要记录学习web性能优化方面的内容。

Hybrid下WebView优化

WebView优点是发版快。缺点是性能加载问题,白屏问题,无法使用系统功能等等。
H5是Hybrid App中的一个核心,可以通过SDK访问App的底层,让H5页面拥有原生的能力。
webview加载流程:
进入App-初始化webview-客户端发起请求-下载HTML以及JS/CSS资源-解析JS执行-JS请求数据-
服务端处理数据并返回-客户端解析DOM渲染-下载渲染图片-完成渲染。

初始化webview之前是App启动阶段
初始化webview到客户端解析DOM渲染属于白屏时间
剩下缓解到首屏渲染结束时首屏渲染阶段

App启动阶段的优化方案

冷启动App时候,会先创建WebView实例,然后启动浏览器内核。
首次启动webview大概可能平均会有400ms左右,二次启动平均有220ms。页面秒开标准会占用40%时间,所以会想办法来优化。
WebView全局优化:在启动App时候,同时启动一个webview让其全局化。或者将webview实例保存在一个公共池当中,当用户访问从公共池取来加载网页,而不是重新初始化一个webview。可以减少200ms左右的启动时间,但是要注意及时销毁,不然会资源占用过大

页面白屏阶段的优化方案

页面白屏阶段,也就是H5页面加载下载HTML以及JS/CSS资源的时间。
一般情况下,我们将资源放到服务器中后,WebView会发起网络请求,如果网络比较差,页面加载时间会变长,可以通过离线包优化。就是将HTML,JS,CSS静态资源打包到一个压缩包内,App先下载到本地,打开页面直接从本地加载。
如果资源有变动,可以生成一个配置文件,App根据配置文件判断是否需要更新离线包还是直接向服务器请求,做一个骨架屏。
或者可以采用SSR服务端渲染的方案

页面首屏渲染

这部分需要解决如何减少数据接口加载时间

  • 降低后端接口响应时间,比如从200ms降低到100ms。优化到100ms之后的优化空间就比较少了。
  • 接口预加载
    • 初始化webview时候,直接由native发起网络请求,H5页面初始化完成之后,直接通过SDK向native获取数据
    • 比如滚动页面时候,可以提前去加载一页的展示数据
    • 通过用户操作系统预判下一步路径提前请求,需要服务端配合做计算

image.png

webview离线包

离线包,最大程度摆脱网络环境对H5页面的影响。
如果是常规优化手段都用过但是还是不明显,可以考虑采用离线包的方案。
https://help.aliyun.com/document_detail/59594.html
https://juejin.cn/post/6844904031773523976#heading-13
https://juejin.cn/post/7117262387312328718
https://www.cnblogs.com/zhangrunhao/p/14582448.html
《移动端本地 H5 秒开方案探索与实现》
可以将一些通用基础库做成离线包。
离线包的源码主要包括HTML,CSS,Img等内容,在CI/CD阶段将离线包版本上线。
另外还需要一个管理后台来管理离线包。
最后,我们将离线包存储到CDN上,当用户进入App向服务器发起静态资源时候,客户端拦截请求,根据配置请求离线包管理后台。离线包返回之后客户端决定是使用全量包还是请求差分包,之后将内容缓存到本地。
image.png

离线包生成

借助webpakc插件ak-webpack-plugin,腾讯 Alloy 团队出品,打包生成压缩包。

  1. 在git上面创建一个offline分支
  2. 修改当前项目的webpack配置,比如修改package.json文件,webpack插件的下方代码示例等,方便本地测试
    1
    2
    "builduploadtest": "node build/uploadtest.js",
    "buildupload": "node build/upload.js”,
  3. 安装npm i 包,并且执行npm run build,同步修改config/offline.js中的对象URL为页面真实URL,修改导出静态资源路径为真实的CDN资源路径

下方代码中的offlinepath对应的就是需要拦截的静态资源路径,客户端从对应的离线包加载资源

1
2
3
4
5
6
7

"bizid": 13,
"date": "1513681326579",
"ver": "20171219185710",
"offlinePath": [
"c.58cdn.com.cn/youpin/activities"
]

可以将离线包功能封装进项目脚手架。一些非首屏的图片资源,不需要走离线包,可以通过webpack排除掉。我们只需要关注webpack构建出来的内容即可。

离线包管理后台

这个后台的作用是监控离线包以及配置管理平台,可以具体查看某条业务离线包使用情况,比如是否正在使用,离线包版本多少,启用时间多长等,也可以开启和关闭离线包。

  • 全局页面。提供离线包管理,可以开启和关闭离线包
  • 离线包列表页面。对所有离线包进行展示和操作,包括展示业务名称、版本号、包类型、发布时间、在线情况等
  • 详情页。查看下载的离线包内容是否正确,以及设置业务优先级,因为不同的业务都想使用,会导致体积越来越大,优先给流量大的业务。

离线包的类型分为差分和全量包。首先在App内,预置一份全量包作为基线版本。加载时候判断是不是最新,不是的话可以下载全新的版本,也可以下载一个差分包版本,也可以绕过离线包直接请求线上数据。

差分包

主要作用是为了避免全量拉取。也就是需要实现增量更新
image.png
实现的方案是通过BSDP,一个基于二进制diff的Node工具包,核心模块是(bsdiff/bspatch)。
我们可以通过这个工具来生成一个增量包(比如升级了页面中某一块内容),然后发布,放在CDN上,然后生成配置文件。
客户端通过对比服务器和本地的配置文件去请求对应的差分包。bapatch主要是用来合并本地版本生成新的全量包。

离线包部署流程

image.png
image.png
前端工程打包,生成离线包入口页面index.soinc.html(支持离线包的index.html文件),然后上传到CDN上。
然后前端将静态资源(index.js、home.css)打包成全量离线包发布CDN,离线管理后台根据基础包生成差分包上传到CDN,离线包异常要及时关掉。
image.png

  • IOS系统中,如果要实现离线包,要先解决WKWebview下面的请求拦截问题,可以借助私有API来实现

  • 抓包要分析离线包是否正常,抓包getofflineconfig接口,找到bid,然后找到配置项下载离线包,解压确认是否有问题。

    webview骨架屏和SSR

    骨架屏

    这里说的骨架屏是指客户端App配合实现的方式。就是让UI设计师设计一张当前页面的骨架屏,上传到CDN,客户端启动时候读取骨架屏配置文件,比如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 传入设备分辨率ratioWidth:400, ratioHeight:500
    {
    "code":0,
    // code 是0 代表请求成功 -1 代表图片骨架屏功能关闭
    "data":{
    "m.58.com/enjoy-given/eg/index.html":{ //对应页面的URL
    "rege":'#/content/index'
    "routes":{
    "#/content/index":{
    "downloadUrl":'https://m.58.com/pic.png?400*500' //骨架屏地址和设备分辨率
    "imgName":'pic.png',
    "id":'10001',
    }
    }
    },

    "msg":""
    }
    }

    用户打开webview,客户端做解析,获取对应的host和pathname路径,然后和data中的路由做比较,获取到对应的骨架屏就是通过id+imgName拼接起来就是图片名称。

  • 首次下载可以直接使用,二次的话要判断图片名称是否一样

  • 客户端内存中建立图片,加快骨架屏加载速度

  • 骨架屏可能会出现拉伸问题,需要加上页面分辨率以返回最合适的图片

    SSR

    比如通过nuxt等框架来实现。一般可以达到200ms白屏。
    需要注意的是,前端在写后端代码时候的安全问题,另外就是SSR出现高并发,估算QPS,需要后端一起配合做好降级方案。

但是SSR没有取代CSP(Vue)原因是,需要对后端Nodejs知识有一定的掌握,有可能环境变量获取不到等问题,需要踩坑。

WebView优化

性能优化

在App内,会有多一个启动内核的过程,所以速度会比较慢。
Android只有一个webview,IOS会有UIWebView和WKWebView。这些启动时间会严重影响首屏秒开,会占到400ms左右,所以需要优化。

并行初始化

进入App时候,根据用户访问路径,选择初始化策略,比如进入某一个页面,直接加载WebView和模板。使用完成不注销,将数据清空,放到WebView池子中。下次直接注入数据使用即可。不能直接放入到UI线程,将初始化过程放到子线程结束才添加到View树中。

资源预加载

静态资源预加载不是离线包。是指在初始化的webview中放置一个静态资源列表,强缓存起来,比如

  • 一定时间内不变的外链
  • 基础框架,vue.js等
  • 基础CSS样式库等

App启动时候,系统加载一个带有通用资源模板的HTML页面。
同时,在离线包后台添加一个栏目静态资源预加载,用来管理静态资源,通过后台发布一个静态资源列表页,把URL提供给App,App启动时候对这个URL下静态资源进行预加载。之后就可以通过查看静态资源的编号等进行操作了。

接口请求优化

  • 请求域名统一方便DNS缓存,如果请求的域名不同,那么就要重新耗费解析DNS时间。
  • 客户端发起请求将数据给到H5,Andriod重写WebViewClient的shouldtercepRequest方法。IOS需要通过私有API,自定义协议和LocalWebServer来实现。

    前端架构性能调优

    长列表优化

    比如用Vue对某一个长列表优化,不需要双向数据的话,可以通过Object.freeze冻结等

    打包优化

    webpack通过webpack-bundle-analyzer分析当前包的大小来优化

WebView优化需要客户端来实现,需要前端工程师做主导去推动。

预请求、预加载、预渲染机制

  • 预请求,对后端请求参数事先拼装

  • 预加载,对数据接口提前加载

  • 预渲染,提前对页面进行渲染

    预请求

    提前拼接参数走Native请求

    预加载

    预判用户的操作路径,命中的话就会做预先加载,去提前请求某些接口数据。

  • 用户进入列表页,后端接口会返回操作路径数组比如[1,2,3]。我们对路径有一个具体的编号,比如进入首页是0,输入搜索内容是2,切换关键词是3,如果前端操作路径和后端返回一致,就开始预加载资源。

  • 当用户点击开始搜索,前端判断有没有预加载下级页面接口数据,有的话并且数据没有过期,直接跳转下级页面。没有的话就重新通过接口进行一次预加载。

  • 预加载实现通过Native或者Axios实现。Axios请求数据,在afterFetch钩子中,将数据存储到本地,供下一个路由使用。

    预渲染

    命中特定的操作路径,进行NSR客户端渲染,需要Native实现,我们会提前把结果页面渲染出来,只不过在可视区域下方,所以用户看不到。
    image.png

NSR优化时候,需要将离线包提供HTML,JS等资源,预加载提供数据。
实现方案:

  • 用户进入页面,页面资源已经准备好,可以使用离线包、预请求,预加载方案来做

(这个方案原文写的不是很明确具体做法)