安卓 WebView 图片离线缓存方案
有这样一个项目,UI 渲染全部由 WebView 来完成,套个安卓的壳,壳子里面做一些和硬件交互的功能,例如摄像头、麦克风等。WebView 加载的页面走的本地打包的文件。不过 WebView 中的图片等资源走的是网络访问。
为了减少网络访问的流量,以及提升在弱网络或无网络情况下的体验,需要对网络访问的图片进行本地缓存。
原先采用的是 WebView 自带的缓存机制来实现,但并不可靠,于是需要通过拦截网络请求,通过本地缓存干预的方式来实现。具体原理如下:
- 通过
shouldInterceptRequest
拦截请求,判断是否是访问网络图片,如果是则进行干预 - 取请求地址的
md5
值加图片文件扩展名组成的文件名,拼接cache
目录获得一个本地资源地址,判断该资源是否存在,若存在则直接返回该资源 - 若该资源不存在,说明是首次访问,则将该网络图片下载到该地址下,并返回该资源
具体代码如下:
import android.content.Context
import android.net.http.SslError
import android.webkit.*
import androidx.webkit.WebViewAssetLoader
import java.io.File
import java.io.FileOutputStream
import java.math.BigInteger
import java.net.HttpURLConnection
import java.net.URL
import java.security.MessageDigest
class CommonWebClient(context: Context) : WebViewClient() {
private var assetLoader: WebViewAssetLoader = WebViewAssetLoader.Builder()
.addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(context))
.build()
private fun md5(input: String): String {
return BigInteger(1, MessageDigest.getInstance("MD5").digest(input.toByteArray())).toString(16).padStart(32, '0')
}
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
return true
}
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
return request?.url?.let { url ->
val urlString = url.toString()
if (!urlString.contains("appassets.androidplatform.net") && urlString.contains("aliyuncs.com")) {
try {
var extension = urlString.substring(urlString.lastIndexOf("."))
if (extension.lastIndexOf("?") > -1) {
extension = extension.substring(0, extension.lastIndexOf("?"))
}
val fileName = "${md5(urlString)}${extension}"
val file = File(view?.context?.externalCacheDir, fileName)
if (!file.exists()) {
val conn = URL(urlString).openConnection() as HttpURLConnection
conn.connectTimeout = 5000
conn.requestMethod = "GET"
conn.doInput = true
if (conn.responseCode == 200) {
val fos = FileOutputStream(file)
val buffer = ByteArray(1024)
var len = 0
while (conn.inputStream.read(buffer).also { len = it } != -1) {
fos.write(buffer, 0, len)
}
conn.inputStream.close()
fos.close()
}
}
WebResourceResponse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension), "UTF-8", file.inputStream())
} catch (ex: Exception) {
assetLoader.shouldInterceptRequest(url)
}
} else {
val response = assetLoader.shouldInterceptRequest(url)
if (url.path?.endsWith(".js") == true && response != null) {
response.mimeType = "text/javascript"
}
response
}
}
}
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
handler?.proceed()
super.onReceivedSslError(view, handler, error)
}
}
评论区