最近项目中需要使用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的时候,有看到可以通过重写WebViewClient的shouldInterceptRequest方法来拦截请求,然后通过匹配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
}
}
效果如下:
| 未拦截请求 | 拦截请求 |
|---|---|
示例
演示代码已在示例Demo中添加。