背景
在app中内嵌的h5,部署更新h5项目以后,app中获取不到最新的代码资源,页面无法得到及时更新,目前需手动清空app缓存文件,或等待webview应用缓存失效,这样的体验需优化
调研与问题
不进行缓存,每次都请求最新的h5
能实现上述的方案有:
- app设置webview强制不缓存,每次都向服务端拉取最新文件
- 每次打开webview,url后缀增加时间戳或随机字符,确保不被应用缓存
- 运维配置nginx,强制不缓存html
此种处理,可以避免h5发版更新不及时的问题,但每次都会请求最新代码,加载体验不佳
验证发现问题
app内嵌h5,webview缓存会连同index.html入口文件也一致缓存了,导致每次打开的index.html页面都是旧版本,所以就算css,js文件进行了hash,增加时间戳操作也没更新资源
html文件内容增加meta标签设置不缓存,此方法并不通用,在微信浏览器或app内嵌webview中还是无效,应用缓存了整个html文件
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="expires" content="0" />
应用缓存不变,通过版本判断清除缓存
借阅网上已有的方案,结合app及h5项目的环境,考虑可以从线上本地版本差异判断出发
http请求json文件,通过url拼接随机数,避开缓存问题,以获取线上最新版本
app存储h5版本,比对版本号差异,触发执行清除app内缓存的操作
以此实现h5项目发版后,app内打开自动清除缓存需求
处理方案
思路
h5端处理:
需要进行缓存清理的h5项目,在其打包构建脚本中动态生成打包版本信息的json文件,写入生成文件列表中
app端处理:
在打开需要缓存清理的h5项目前,请求h5项目版本信息json文件,获取版本号与app本地存储的版本号进行比对,判断是否需要进行缓存清理操作
H5端进行处理
创建【build-version.js】,用于h5打包构建时动态生成h5项目版本信息
const fs = require('fs')
const path = require('path')
const pkg = require('../package.json')
// 输出路径 - h5打包后的目录
const output = path.resolve(__dirname, '../dist')
if (!fs.existsSync(output)) {
// 如不存在此路径,则生成路径
fs.mkdirSync(output)
}
// 获取版本信息字符
function geVersionJson() {
const buildTime = new Date().getTime()
const appVersion = pkg.version
// edgg: 'v1.7.0 - 1672124524686'
const versionJson = {
version: `v${appVersion} - ${buildTime}`
}
return versionJson
}
try {
const versionInfo = geVersionJson()
// 生成version.json文件并写入版本信息
fs.writeFileSync(path.resolve(output, 'version.json'), JSON.stringify(versionInfo, null, '\t'))
} catch (e) {
console.log(e)
}
在【package.json】的构建脚本中添加,执行【build-version.js】,以生成【version.json】文件
构建脚本增加以下代码node ./build/build-version.js
{
"name": "open_account_h5",
"version": "1.7.2",
"description": "OpenAccountH5, 盈宝H5开户",
"scripts": {
...
"build:dev": "yarn && vue-cli-service build --mode development && node ./build/build-version.js",
"build:test": "yarn && vue-cli-service build --mode staging && node ./build/build-version.js",
"build:test2": "yarn && vue-cli-service build --mode staging2 && node ./build/build-version.js",
"build:prod": "yarn && vue-cli-service build --mode production && node ./build/build-version.js",
...
},
}
如进行开发环境打包构建,执行yarn build:dev
,运行结束,生成的dist打包目录中则会新增【version.json】文件
此外h5端不需再进行其它额外的处理,此流程只作用于打包项目时,不影响h5现有功能
APP端进行处理
创建h5项目缓存清理类文件【h5_cache_clean】,在其内部实现以下几个功能
- getLocalH5VersionByUrl:获取app本地存储的h5项目版本号
- setLocalH5VersionByUrl:设置app本地存储的h5项目版本号
- reqH5Version: http请求线上最新的h5版本号
- doCacheClean:进行webview缓存目录清理操作
- checkH5Version:比对app本地及线上h5版本号,判断是否需要进行缓存清理操作,并保存线上最新版本号
其中,主要依靠reqH5Version,获取线上版本信息,reqH5Version内处理方式为,通过http get请求h5项目的version.json文件,并在请求url附加时间戳,以防止缓存干扰
大体代码:
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:win_bull_app/header.dart';
import 'package:win_bull_app/util/sputil.dart';
// 请求version.json的超时时间
int _timeOut = 1000;
// 本地存储key前缀
String _spKey = 'h5Version-';
class H5CacheClean {
/// 获取app本地存储的h5项目版本号
static String? getLocalH5VersionByUrl(String loadUrl) {
String key = '$_spKey$loadUrl';
return SP.sp.getString(key);
}
/// 设置app本地存储的h5项目版本号
static Future<bool> setLocalH5VersionByUrl(
String loadUrl, String version) async {
String key = '$_spKey$loadUrl';
return await SP.sp.setString(key, version);
}
/// 比对app本地及线上h5版本号,判断是否需要进行缓存清理操作,并保存线上最新版本号
static Future<void> checkH5Version(String loadUrl) async {
String? localVersion = getLocalH5VersionByUrl(loadUrl);
String newVersion = await reqH5Version(loadUrl);
if (kDebugMode) {
print('h5链接:$loadUrl');
print('本地版本:$localVersion');
print('在线版本:$newVersion');
}
// 获取线上版本异常或者空会返回'', 先存储本地
await setLocalH5VersionByUrl(loadUrl, newVersion);
/// 如果获取到的本地版本为null(注意不是''), 此时是第一次判断此h5项目
/// 做一次清理缓存操作
if (localVersion == null) {
await doCacheClean();
} else {
/// 如果获取到的线上版本为空,则不做缓存清理,可能的情况
/// 1.请求异常:可能是断网,或者是超时
/// 2.该h5项目未增加动态打包版本配置
if (newVersion.isEmpty) {
return;
} else {
// 线上版本不为空
// 如果跟本地版本不一致,就执行缓存清除操作
if (newVersion != localVersion) {
await doCacheClean();
}
}
}
}
/// http请求线上最新的h5版本号
static Future<String> reqH5Version(String loadUrl) async {
String h5Version = '';
try {
String curTime = DateTime.now().millisecondsSinceEpoch.toString();
String reqUrl = '$loadUrl/version.json?_t=$curTime';
final baseDio = Dio(BaseOptions(
receiveTimeout: _timeOut,
sendTimeout: _timeOut,
connectTimeout: _timeOut,
));
dynamic res = await baseDio.get<dynamic>(reqUrl);
var resMap = json.decode(res.toString());
if (resMap['version'] != null) {
h5Version = resMap['version'] as String;
} else {
h5Version = '';
}
} catch (e) {
h5Version = '';
}
return h5Version;
}
/// 进行缓存清理操作
static Future<void> doCacheClean() async {
try {
Directory dir = await getTemporaryDirectory();
List<FileSystemEntity> list = dir.listSync();
for (var i = 0; i < list.length; i++) {
var fileStream = list[i];
String path = fileStream.path.toLowerCase();
/// 安卓设备webview缓存路径包含【webview】不包含【webkit】
/// 苹果设备webview缓存路径包含【webkit】不包含【webview】
if (path.contains('webview') || path.contains('webkit')) {
if (kDebugMode) {
print('清理webview缓存:');
print(fileStream.path);
print('------------------');
}
fileStream.deleteSync(recursive: true);
}
}
} catch (err) {
if (kDebugMode){
print(err);
}
}
}
}
调用及验证
app中,需要进行h5缓存清理的项目(不是所有h5项目,对于那些更新不频繁的可不进行此操作?),在其打开webview的方法前,调用
String loadUrl = H5UrlTool.getH5UrlByName('openAccount');
await H5CacheClean.checkH5Version(loadUrl);
// ...打开webview
如版本不一致,则app会调用清除缓存的方法,在打开webview前清空缓存,再打开webview,故实现h5发版清除缓存逻辑;
经验证,二次通过Jenkins构建测试环境,可以实现方案目标需求
参考: