對于安卓的WebView頁面緩存,可以通過WebSetting的setAppCachePath+setCacheMode的方式實現(xiàn),native實現(xiàn)代碼很簡單,如下:
// 開啟 Application Caches 功能
webSettings.setAppCacheEnabled(true);
String appCachePath = mContext.getDir("webAppCache", Context.MODE_PRIVATE).getPath();
webSettings.setAppCachePath(appCachePath);
// 默認就是LOAD_DEFAULT书在,所以是LOAD_DEFAULT,則下面一句可不寫
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
當你寫完這幾句代碼之后拆又,可能你認為緩存的功能已經(jīng)萬事大吉了儒旬。但是實際情況卻是栏账,當你瀏覽了一個web頁面之后,再查看下生成的目錄發(fā)現(xiàn)栈源,該目錄下竟然一個頁面都沒有緩存下來发笔,具體情況可以通過查看data/data/包名下定義的緩存目錄可查。
為什么會這樣呢凉翻?這篇文章可以解答我們的疑惑:Html5利用AppCache和LocalStorage實現(xiàn)緩存h5頁面數(shù)據(jù)
也就是說我們要緩存的文件需要配合前端一塊處理了讨。我經(jīng)過調(diào)研后發(fā)現(xiàn)ios似乎不支持這種方式,并且指定頁面只要有文件有改動制轰,前端的manifest配置文件就相應(yīng)的需要改動前计,整個過程不太靈活,所以前端也就不愿意配合了垃杖。
所以男杈,既然如此,我們能不能做個簡單的緩存功能呢调俘?
我的思路是伶棒,對于預(yù)知不會改變的文件,可以在頁面加載期間彩库,通過攔截url的方式肤无,根據(jù)自定義的規(guī)則,動態(tài)緩存起來骇钦,以后再請求該文件時宛渐,則首先判斷本地有無該緩存文件,有則直接返回本地資源眯搭,否則就默認網(wǎng)絡(luò)加載窥翩,并攔截緩存。
web緩存使用場景代碼如下:
class MyWebViewClient extends WebViewClient implements JSInvokeNative.ExitListener {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
......
InputStream is = WebCacheManager.INSTANCE.getCache(activity, url);
if (is != null) {
return new WebResourceResponse(WebCacheManager.INSTANCE.getMimeType(url), "UTF-8", is);
}
return super.shouldInterceptRequest(view, url);
}
}
web緩存邏輯代碼如下:
object WebCacheManager {
private const val MAX_SIZE = 100 * 1024 * 1024L //100MB
private const val cacheDir = "WebCacheDir"
private val okHttpClient by lazy { OkHttpClient() }
private val imageSuffix = arrayOf(".png", ".jpg", ".jpeg")
// 配置緩存指定域名下的文件
private val hosts = arrayOf("xxx.xxx.com", "xxx.xxx.com")
// 經(jīng)過確認鳞仙,指定域名下的這些格式的文件是不會變的
private val fileSuffix = arrayOf(".css", ".js")
private var diskLruCache: DiskLruCache? = null
/**
* DiskLruCache初始化
*/
private fun initDiskLruCache(context: Context) {
if (diskLruCache == null || diskLruCache!!.isClosed) {
try {
val cacheDir = getDiskCacheDir(context)
if (!cacheDir.exists()) {
cacheDir.mkdirs()
}
//初始化DiskLruCache
diskLruCache = DiskLruCache.create(FileSystem.SYSTEM, cacheDir, 1, 1, MAX_SIZE)
} catch (e: IOException) {
e.printStackTrace()
}
}
}
/**
* 設(shè)置緩存路徑寇蚊,優(yōu)先sd卡,沒有則使用內(nèi)置路徑
*/
private fun getDiskCacheDir(context: Context): File {
if (MobileUtil.isSdExist()) {
val fileDir = context.getExternalFilesDir(cacheDir)
if (fileDir != null) {
return fileDir
}
}
return File(context.filesDir.absolutePath, cacheDir)
}
/**
* 獲取緩存棍好,若無緩存則通過OkHttp+DiskLruCache緩存文件
*/
fun getCache(context: Context, url: String): InputStream? {
if (!canCache(url)) return null
val result: InputStream? = try {
initDiskLruCache(context)
val snapshot = diskLruCache!!.get(hashKeyForDisk(url))
if (snapshot == null) {
null
} else {
val source = snapshot.getSource(0)
val buffer = Buffer()
var len = 0L
while (len != -1L) {
len = source.read(buffer, 4 * 1024)
}
//獲取到buffer的inputStream對象
buffer.inputStream()
}
} catch (e: IOException) {
null
}
if (result == null) {
initDiskLruCache(context)
okHttpClient.newCall(Request.Builder().url(url).build()).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) = Unit
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val key = hashKeyForDisk(url)
writeToDisk(response.body(), url, diskLruCache!!.edit(key), key)
}
}
})
}
return result
}
/**
* 緩存非空仗岸,且非本地文件,且為圖片格式梳玫,或指定域名下的指定文件格式
*/
private fun canCache(url: String): Boolean {
if (TextUtils.isEmpty(url)) return false
val uri = Uri.parse(url)
if ("file" == uri.scheme) return false
val lastPath = uri.lastPathSegment
if (TextUtils.isEmpty(lastPath)) return false
if (imageSuffix.any { lastPath!!.endsWith(it) }) return true
if (!hosts.contains(uri.host)) return false
return fileSuffix.any { lastPath!!.endsWith(it) }
}
/**
* DiskLruCache緩存
*/
private fun writeToDisk(body: ResponseBody?, imageUrl: String, editor: DiskLruCache.Editor?, key: String) {
if (body == null) return
if (editor == null) return
val sink = editor.newSink(0)
L.e("writeToDisk url ---> $imageUrl\tkey=${hashKeyForDisk(imageUrl)}")
var inputStream: InputStream? = null
val buffer = Buffer()
var isSuccess = false
try {
val byteArray = ByteArray(4 * 1024)
inputStream = body.byteStream()
while (true) {
val read = inputStream.read(byteArray)
if (read == -1) {
break
}
buffer.write(byteArray, 0, read)
sink.write(buffer, read.toLong())
buffer.clear()
}
isSuccess = true
} catch (e: IOException) {
if (MobileUtil.isDebug()) {
e.printStackTrace()
}
isSuccess = false
L.e("${imageUrl}${e.printStackTrace()}")
} finally {
buffer.clear()
buffer.close()
inputStream?.close()
sink.flush()
sink.close()
if (!isSuccess) {
L.e("${imageUrl}下載不完整爹梁,已刪除")
diskLruCache!!.remove(key)
} else {
editor.commit()
}
}
}
/**
* web加載文件的類型
*/
fun getMimeType(url: String): String {
if (TextUtils.isEmpty(url)) return "text/html"
val uri = Uri.parse(url)
val lastPath = uri.lastPathSegment
if (TextUtils.isEmpty(lastPath)) return "text/html"
return if (lastPath!!.endsWith(".png")) {
"image/x-png"
} else if (lastPath.endsWith(".jpg") || lastPath.endsWith(".jpeg")) {
"image/jpeg"
} else if (lastPath.endsWith(".css")) {
"text/css"
} else {
"text/javascript"
}
}
/**
* 將url轉(zhuǎn)成md5
*/
private fun hashKeyForDisk(url: String): String {
return try {
val mDigest = MessageDigest.getInstance("MD5")
val md5bytes = mDigest.digest(url.toByteArray(charset("UTF-8")))
ByteString.of(*md5bytes).hex()
} catch (e: NoSuchAlgorithmException) {
url.hashCode().toString()
}
}
}