# cachewebview
**Repository Path**: perry_fan/cachewebview
## Basic Information
- **Project Name**: cachewebview
- **Description**: 简单易用,高性能Hybrid组件 及 首屏直出用户秒开体验的解决方案
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 1
- **Created**: 2023-05-15
- **Last Updated**: 2023-09-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 一. 简介
>+ 本项目提供有 简单易用,高性能Hybrid组件 及 首屏直出用户秒开体验的解决方案
>+ 功能:H5预加载、H5资源缓存、首屏直出等
>+ 便于监控加载性能,接入线上监控平台。
## 二. 架构
### 架构简图
### CacheWebView时序图
## 三. 优化方案
### 1. 引入 CacheWebView 组件
#### 1)引入组件
```maven
implementation 'io.gitee.perry_fan:cachewebview:1.0.1'
repositories {
mavenCentral()
}
```
#### 2)用 CacheWebView 来替代原生 WebView 引入到项目中
#### 3)设置缓存模式
```kotlin
// 缓存模式:必选配置
val cacheMode = if (WebSettingUtil.isNetworkConnected(this))
WebSettings.LOAD_DEFAULT else WebSettings.LOAD_CACHE_ELSE_NETWORK
webView.settings.cacheMode = cacheMode
webView.setCacheMode(CacheMode.CACHE)
// 自定义可选配置
val config: CacheConfig = CacheConfig.Builder(this)
.setCacheDir(cacheDir.toString() + File.separator + "custom")
.setDiskCacheSize(200 * 1024 * 1024) // 自定义缓存大小
.setExtensionFilter(CustomMimeTypeFilter()) // 自定义缓存格式文件
.build()
webView.setCacheMode(CacheMode.CACHE, config)
```
#### 4)原理解读
H5加载过程中有大量静态资源需下载后,交予容器渲染,每次加载均会重复请求。本方案在容器回调方法中进行拦截。定义拦截指定格式的资源如(js、css、png、jpg、webp、ico...等),交由原生网络框架进行加载,并本地化缓存。
通过资源链接url的 md5 值作为唯一标识,当资源名称变更则默认为新资源,缓存方式有:内存缓存、磁盘缓存均采用 LruCache 算法进行优化、还有OkHttp的网络缓存。再次读取时可极大缩减资源重复加载导致的网络请求耗时。缓存区域设置在应用的 cache目录下,无需申请文件读写权限,并设置100MB为默认缓存空间大小(优于原生自有的5MB空间)。
由于首次进入页面才会进行静态资源下载缓存,用户首次仍会感受到加载速度稍慢,因而选择在H5业务的前置页预加载指定业务url并执行缓存静态资源。
### 2. 预加载
#### 1)application 中引擎预加载
```kotlin
// application onCreate()方法中
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PreloadEngineUtil.preloadWebView(this)
}
```
webview初始化耗时的部分很多在 WebViewChromiumAwInit 完成,在我们的应用创建时预先执行这部分初始化工作,当主线程空闲即完成Chromium引擎的加载。缩短容器初始化容器耗时。
#### 2)容器预创建 并 预加载业务Url
思想是“以空间换时间”,通过内存的少量消耗,换取对webview加载耗时的减少。
进入主页后/打开H5页面前置页,预创建一个webview容器。在打开H5相关业务时可直接复用该容器,缩短原构建耗时。
另外,在退出相关页面后,清空当前业务内容并重建容器加入缓存池中为下一次H5业务使用做准备。
#### a. 初始化 / 获取CacheWebView
```kotlin
WebViewCacheHolder.acquire(context)
```
使用方式:
```kotlin
// H5前置页 / 应用首页调用
private fun preloadUrl(url: String) {
val webView: CacheWebView = WebViewCacheHolder.acquire(this)
val cacheMode = if (WebSettingUtil.isNetworkConnected(this))
WebSettings.LOAD_DEFAULT else WebSettings.LOAD_CACHE_ELSE_NETWORK
WebSettingUtil.webSetting(webView)
webView.settings.cacheMode = cacheMode
webView.preload(this, url)
}
```
#### b. 释放资源防泄漏 / 并变更容器上下文为下次使用准备
```kotlin
WebViewCacheHolder.release(webview)
```
使用方式:
```kotlin
// WebActivity中调用
override fun onDestroy() {
if (webView != null) {
WebViewCacheHolder.release(webView)
}
super.onDestroy()
}
```
### 3. 首屏优化
为达成首屏业务直出,无白屏的体验。本方案在部分 **首屏入口交互样式变化较少场景中应用**,达成直出交互效果。
#### 1)在首次页面加载完成后,存储当前页面截屏,并存入应用缓存中。
#### 2)在其后进入时,直接加载本次截屏(需判断是否为入口页),当容器渲染加载完成url时将截屏隐藏,展示当前H5内容。
```kotlin
fun saveImageToCache(context: Context, bmp: Bitmap, urlContent: String): Boolean {
// 首先保存图片
val storePath: String =
context.cacheDir.absolutePath + File.separator.toString() + context.packageName
val appDir = File(storePath)
if (!appDir.exists()) {
appDir.mkdir()
}
val fileName = "$urlContent.jpg"
val file = File(appDir, fileName)
try {
val fos = FileOutputStream(file)
//通过io流的方式来压缩保存图片
val isSuccess = bmp.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos.flush()
fos.close()
return isSuccess
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
private fun cacheImageCheck() {
var path = ""
if (targetUrl == UrlManager.webUrl) {
path = ImgUtils.getImageFromCache(this@WebActivity, UrlManager.webContent)
}
if (!TextUtils.isEmpty(path)) {
val bm = BitmapFactory.decodeFile(path)
if (bm != null) {
imgViewCache.visibility = View.VISIBLE
imgViewCache.setImageBitmap(bm)
}
mHandler.postDelayed(Runnable {
if (this@WebActivity.isFinishing) {
return@Runnable
}
imgViewCache.visibility = View.GONE
}, 6000)
} else {
imgViewCache.visibility = View.GONE
}
}
```
#### 3)白屏判断:截屏的时机为当前页不为白屏,具体判断方法定义为,定时判断左上、中间、右下取点,颜色不完全相同即认为达到保存时机。
```kotlin
fun checkPictureIsSameColor(bitmap: Bitmap): Boolean {
val random = Random()
val bitmapHeight = bitmap.height
val bitmapWidth = bitmap.width
val colorOne = bitmap.getPixel(random.nextInt(bitmapWidth), random.nextInt(bitmapHeight))
val colorTwo = bitmap.getPixel(random.nextInt(bitmapWidth / 2), random.nextInt(bitmapHeight / 2))
val colorThree =
bitmap.getPixel(random.nextInt(bitmapWidth / 3), random.nextInt(bitmapHeight / 3))
return colorOne == colorTwo && colorTwo == colorThree && colorOne == colorThree
}
```
### 4. 其他
#### 1)blockNetworkImage 处理
未加载完成前
```kotlin
webSettings.blockNetworkImage = true
```
加载完成后即 onPageFinished方法调用后
```java
view.settings.blockNetworkImage = false
```
#### 2)WebView setting配置等,如webSettings.domStorageEnabled = true
#### 3)H5打离线包,根据策略下载分发,匹配到本地模板即直接加载本地资源 (需自行添加)
## 四. 监控方案
> 容器 Js 注入的方式,在容器侧 定量监控 H5 性能
#### 1)H5性能统计API
[Performance.Timing](https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/timing) + [PerformancePaintTiming](https://developer.mozilla.org/zh-CN/docs/Web/API/PerformancePaintTiming) + [PerformanceNavigationTiming](https://developer.mozilla.org/zh-CN/docs/Web/API/PerformanceNavigationTiming)
#### 2)数据指标:fp、fcp、du 等
由于官方不再推荐使用 `Performance.Timing` 特性,因而采用推荐的后两种API为主,
采集字段为 `fp`、`fcp` 由 `PerforamancePaintTiming` 获取 、`du` 由 `PerformanceNavigationTiming`的 duration 获取。
因兼容性问题采集不到的使用 `PerformanceTiming` 中的
`du = loadEventEnd - navigationStart, fp = domInteractive - navigationStart, fcp = loadEventEnd- navigationStart` 作为兜底方案;
另外可根据需要,新增 dns、tcp、ttfbTime 等监控指标。
```kotlin
webView.addJavascriptInterface(this, "web")
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
// 性能监控
view.evaluateJavascript(UrlManager.JS_MONITOR, null)
}
const val JS_MONITOR =
"javascript:web.setH5Performance(" +
"JSON.stringify(window.performance.timing), window.location.href , " +
"JSON.stringify(window.performance.getEntriesByType('paint'))," +
"JSON.stringify(window.performance.getEntriesByType('navigation')))"
@JavascriptInterface
fun setH5Performance(timing: String?, curUrl: String?, paint: String?, navigation: String?) {
// 具体数据处理,数据监控工具接入
...
}
```
> 例如:{fp(白屏时间)=344, fcp(渲染时间)=344, du(总时长)=656}
## 五. 优化效果对比
![]() |
![]() |