使用OkHttp+DiskLrucache實現(xiàn)自定義web頁面緩存

對于安卓的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()
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市提澎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌念链,老刑警劉巖盼忌,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件积糯,死亡現(xiàn)場離奇詭異,居然都是意外死亡谦纱,警方通過查閱死者的電腦和手機看成,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跨嘉,“玉大人川慌,你說我怎么就攤上這事§裟耍” “怎么了梦重?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長亮瓷。 經(jīng)常有香客問我琴拧,道長,這世上最難降的妖魔是什么嘱支? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任蚓胸,我火速辦了婚禮,結(jié)果婚禮上除师,老公的妹妹穿的比我還像新娘沛膳。我一直安慰自己,他們只是感情好汛聚,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布于置。 她就那樣靜靜地躺著,像睡著了一般贞岭。 火紅的嫁衣襯著肌膚如雪八毯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天瞄桨,我揣著相機與錄音话速,去河邊找鬼。 笑死芯侥,一個胖子當著我的面吹牛泊交,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播柱查,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼廓俭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了唉工?” 一聲冷哼從身側(cè)響起研乒,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淋硝,沒想到半個月后雹熬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宽菜,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年竿报,在試婚紗的時候發(fā)現(xiàn)自己被綠了铅乡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡烈菌,死狀恐怖阵幸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芽世,我是刑警寧澤挚赊,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站捂襟,受9級特大地震影響咬腕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜葬荷,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一涨共、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宠漩,春花似錦举反、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雕崩,卻和暖如春魁索,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盼铁。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工粗蔚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饶火。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓鹏控,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肤寝。 傳聞我的和親對象是個殘疾皇子当辐,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355