美文网首页
使用OkHttp+DiskLrucache实现自定义web页面缓

使用OkHttp+DiskLrucache实现自定义web页面缓

作者: android_hcf | 来源:发表于2020-06-22 18:44 被阅读0次

    对于安卓的WebView页面缓存,可以通过WebSetting的setAppCachePath+setCacheMode的方式实现,native实现代码很简单,如下:

    // 开启 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);
    

    当你写完这几句代码之后,可能你认为缓存的功能已经万事大吉了。但是实际情况却是,当你浏览了一个web页面之后,再查看下生成的目录发现,该目录下竟然一个页面都没有缓存下来,具体情况可以通过查看data/data/包名下定义的缓存目录可查。
    为什么会这样呢?这篇文章可以解答我们的疑惑:Html5利用AppCache和LocalStorage实现缓存h5页面数据
    也就是说我们要缓存的文件需要配合前端一块处理。我经过调研后发现ios似乎不支持这种方式,并且指定页面只要有文件有改动,前端的manifest配置文件就相应的需要改动,整个过程不太灵活,所以前端也就不愿意配合了。
    所以,既然如此,我们能不能做个简单的缓存功能呢?
    我的思路是,对于预知不会改变的文件,可以在页面加载期间,通过拦截url的方式,根据自定义的规则,动态缓存起来,以后再请求该文件时,则首先判断本地有无该缓存文件,有则直接返回本地资源,否则就默认网络加载,并拦截缓存。
    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")
        // 经过确认,指定域名下的这些格式的文件是不会变的
        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()
                }
            }
        }
    
        /**
         * 设置缓存路径,优先sd卡,没有则使用内置路径
         */
        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转成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()
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:使用OkHttp+DiskLrucache实现自定义web页面缓

          本文链接:https://www.haomeiwen.com/subject/xerffktx.html