Android WebView 拦截请求加载本地资源

4,737 阅读1分钟

最近项目中需要使用WebView加载本地的H5文件,期间遇到了个问题,H5中使用Fetch API来加载资源,但是无法成功加载,看了日志发现问题如下:

Fetch API cannot load file:///android_asset/xxx/xxxx URL scheme "file" is not supported.

也就是Fetch API不支持file协议,所以需要进行调整。可以直接让前端的同事处理,但是Android端也可以提供解决方案。

通过WebView拦截请求

之前调研WebView的时候,有看到可以通过重写WebViewClientshouldInterceptRequest方法来拦截请求,然后通过匹配url,来决定使用本地资源还是从网络加载。

实现代码如下:

//html示例如下(以图片和视频为例)
<!DOCTYPE html>
<html lang=zh-CN>
<head>
    <meta charset=utf-8>
    <title>test</title>
</head>
<body>
<div style="position:relative;left:40px;top:100px">
    <img src="http:/minigame_test/assets/test_icon.jpg" style="width:500px;height:700px;">

    <video src="http:/minigame_test/assets/test_video.mp4"
           style="width:1000px;height:700px;" controls autoplay loop></video>
</div>
</body>
</html>

//WebViewActivity示例
class WebViewActivity : Activity() {

    private lateinit var layoutWebViewActivityBinding: LayoutWebViewActivityBinding

    private var mainWebView: WebView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        layoutWebViewActivityBinding = DataBindingUtil.setContentView(this, R.layout.layout_web_view_activity)
        layoutWebViewActivityBinding.ivBack.setOnClickListener { onBackPressed() }
        mainWebView = WebView(this)
        mainWebView?.let {
            it.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
            initWebViewSetting(it)
            layoutWebViewActivityBinding.webViewContainer.addView(it)
        }
        mainWebView?.loadUrl("file:///android_asset/index_intercept_request.html")
    }

    @SuppressLint("JavascriptInterface", "SetJavaScriptEnabled")
    private fun initWebViewSetting(webView: WebView?) {
        webView?.run {
            settings.cacheMode = WebSettings.LOAD_DEFAULT
            settings.domStorageEnabled = true
            settings.allowContentAccess = true
            settings.allowFileAccess = true
            settings.useWideViewPort = true
            settings.loadWithOverviewMode = true
            settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
            settings.javaScriptEnabled = true
            settings.javaScriptCanOpenWindowsAutomatically = true
            settings.setSupportMultipleWindows(true)

            addJavascriptInterface(jsInteractive, "JsInteractive")
            webChromeClient = object : WebChromeClient() {}
            webViewClient = object : WebViewClient() {
                override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
                    request?.run {
                        val urlStr = url.toString()
                        if (urlStr.contains("minigame") && urlStr.contains("assets")) {
                            val assetsNamespace = "assets/"
                            var localAssetsPath = ""
                            var mineType = ""
                            when {
                                urlStr.contains("test_icon.jpg") -> {
                                    localAssetsPath = urlStr.substring(urlStr.indexOf(assetsNamespace) + assetsNamespace.length)
                                    mineType = "image/jpeg"
                                }

                                urlStr.contains("test_video.mp4") -> {
                                    localAssetsPath = urlStr.substring(urlStr.indexOf(assetsNamespace) + assetsNamespace.length)
                                    mineType = "video/mp4"
                                }
                            }

                            if (localAssetsPath.isNotEmpty() && mineType.isNotEmpty()) {
                                val inputStream = assets.open(localAssetsPath)
                                return WebResourceResponse(mineType, Charsets.UTF_8.toString(), inputStream)
                            }
                        }
                    }
                    return super.shouldInterceptRequest(view, request)
                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        mainWebView?.run {
            onResume()
            resumeTimers()
        }
    }

    override fun onPause() {
        super.onPause()
        mainWebView?.run {
            onPause()
            pauseTimers()
        }
    }

    override fun onDestroy() {
        destroyMainWebView()
        super.onDestroy()
    }

    override fun onBackPressed() {
        when {
            mainWebView?.canGoBack() == true -> mainWebView?.goBack()
            else -> super.onBackPressed()
        }
    }

    private fun destroyMainWebView() {
        mainWebView?.run {
            Log.i(TAG, "destroy mainWeb webView:$this")
            clearHistory()
            loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
            layoutWebViewActivityBinding.webViewContainer.removeView(this)
            destroy()
        }
        mainWebView = null
    }
}

效果如下:

未拦截请求拦截请求
Screen_recording_202 -middle-original (1).gifScreen_recording_202 -middle-original.gif

示例

演示代码已在示例Demo中添加。

ExampleDemo github

ExampleDemo gitee