WebView实现离线缓存

场景

在App在长期发展之中,对动态性要求很高的 活动页面 或是 一些带有简单功能的详情页面都可能会有大量Webview使用的情况。但是webview初始化时极有可能遇到网络波动的影响导致加载不出 或者 会重复下载一些公共资源造成性能问题。这时我们希望有一种缓存方案能够暂时解决这些初始化变慢的问题

原理

android WebViewClient提供了shouldInterceptRequest的接口供我们使用这个接口会拦截webview所有请求。如果错误缓存了资源,可能会出现web页面无法更新的情况。所以用的时候要谨慎只对我们需要使用缓存的部分进行拦截

override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
    if (view != null && request != null) {
      //判断需要使用缓存的url 
        if (WebViewCacheUtils.needCache(request.url.toString())) {
          //从缓存池中获取缓存
            val cache = WebViewCacheUtils.getCache(request)
            if (cache != null) {
                return cache
            }
        }
    }
		//未找到缓存文件或者不需要缓存 还是正常走请求
    return super.shouldInterceptRequest(view, request)
}
  fun getCache(webResourceRequest: WebResourceRequest): WebResourceResponse? {
        val uri = webResourceRequest.url
        try {
            //获取加载资源类型
            var mimeType: String? = MimeTypeMapUtils.getMimeTypeFromUrl(uri.toString())
            val type: String
            val header = HashMap<String, String>()
          //我们可能对多个域名进行缓存 先设置跨域
            header["Access-Control-Allow-Origin"] = "*"
            header["Access-Control-Allow-Headers"] = "Content-Type"
            if (mimeType == null) {
                if (uri.path!!.contains("js")) {
                    type = "js"
                    mimeType = "application/javascript"
                    header["content-type"] = "application/javascript; charset=utf-8"
                } else {
                    mimeType = "text/html"
                    type = "html"
                    header["content-type"] = "text/html; charset=utf-8"
                }

            } else if (mimeType.contains("img") || mimeType.contains("image")) {
                type = "img"
            } else {
              //这里主要是css 格式是 text/css
                type = mimeType.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1]
            }
//确定是否获取了资源类型
            if (!TextUtils.isEmpty(mimeType)) {
                val name = (if (type == "html") {
                  //由于多个url可能使用的是同一个html 这里需要判断下 返回的是 该html的md5
                    needCacheHtml(uri.path)
                } else {
                    MD5Utils.encode(uri.path)
                })
              //从缓存池中获取缓存 
              val cacheSteam=getWebCache(name, type)
              //构造响应体 并返回
             return  WebResourceResponse(mimeType, "", 200, "ok", header, cacheSteam)
            }
        } catch (e: FileNotFoundException) {
           e.printStackTrace()
        }

        return null
    }

注意事项

  • HTML的缓存一定要小心 最好是由前端同学出一份目录 的接口并且做好版本管理 防止误操作 否则线上可能会出严重问题
  • 存储的文件名都是用md5过的 防止有特殊字符影响持久化
  • 如果有大文件缓存 最好需要有文件完整性验证