preface

WebView optimization and H5 seconds open is the developer around the problem, but the collection of reference to a large number of information summary to share with you.

WebView preloading and reuse

Webviews are time-consuming to create, taking hundreds of milliseconds to create for the first time, so preloading and reuse is especially important. The general logic is to create a WebView and cache it until it is needed. The code is as follows:

class WebViewManager private constructor(a){

    companion object {
        @Volatile
        private var INSTANCE: WebViewManager? = nullprivate fun instance() = INSTANCE ? :synchronized(this){ INSTANCE ? : WebViewManager().also { INSTANCE = it } } funprepare(context: Context) {
            instance().prepare(context)
        }

        fun obtain(context: Context): WebView {
            return instance().obtain(context)
        }

        fun recycle(webView: WebView) {
            instance().recycle(webView)
        }

        fun destroy() {
            instance().destroy()
        }
    }

    private val webViewCache: MutableList<WebView> = ArrayList(1)

    private fun create(context: Context): WebView {
        val webView = WebView(context)
        webView.setBackgroundColor(Color.TRANSPARENT)
        webView.view.setBackgroundColor(Color.TRANSPARENT)
        webView.overScrollMode = WebView.OVER_SCROLL_NEVER
        webView.view.overScrollMode = WebView.OVER_SCROLL_NEVER
        val webSetting = webView.settings
        webSetting.allowFileAccess = true
        webSetting.setAppCacheEnabled(true)
        webSetting.cacheMode = WebSettings.LOAD_DEFAULT
        webSetting.domStorageEnabled = true
        webSetting.setGeolocationEnabled(true)
        webSetting.javaScriptEnabled = true
        webSetting.loadWithOverviewMode = true
        webSetting.setSupportZoom(true)
        webSetting.displayZoomControls = false
        webSetting.useWideViewPort = true
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            webSetting.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
            CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true) } webView.settingsExtension? .apply { setContentCacheEnable(true)
            setPageCacheCapacity(IX5WebSettings.DEFAULT_CACHE_CAPACITY)
        }
        return webView
    }

    fun prepare(context: Context) {
        if (webViewCache.isEmpty()) {
            Looper.myQueue().addIdleHandler {
                webViewCache.add(create(MutableContextWrapper(context)))
                false
            }
        }
    }

    fun obtain(context: Context): WebView {
        if (webViewCache.isEmpty()) {
            webViewCache.add(create(MutableContextWrapper(context)))
        }
        val webView = webViewCache.removeFirst()
        val contextWrapper = webView.context as MutableContextWrapper
        contextWrapper.baseContext = context
        webView.clearHistory()
        webView.resumeTimers()
        return webView
    }

    fun recycle(webView: WebView) {
        try {
            webView.stopLoading()
            webView.loadDataWithBaseURL(null.""."text/html"."utf-8".null)
            webView.clearHistory()
            webView.pauseTimers()
            webView.webChromeClient = null
            webView.webViewClient = null
            val parent = webView.parent
            if(parent ! =null) {
                (parent as ViewGroup).removeView(webView)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            if(! webViewCache.contains(webView)) { webViewCache.add(webView) } } } fundestroy() {
        try {
            webViewCache.forEach {
                it.removeAllViews()
                it.destroy()
                webViewCache.remove(it)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

}
Copy the code

The following points need to be noted:

  • Selection of preloading timing

WebView creation is time consuming. In order to ensure that preloading does not affect the current main thread task, we select IdleHandler to perform the precreation to ensure that it does not affect the current main thread task. For details, see the prepare(context: context) method.

  • The choice of the Context

Each WebView needs to be bound to an instance of the corresponding Activity Context. To ensure consistency between the preloaded WebView Context and the final Context, We solved this problem with MutableContextWrapper. MutableContextWrapper allows its baseContext to be replaced externally, so prepare(context: The Context) method can be precreated by passing the applicationContext and replaced when it is actually called, as described in the Obtain (Context: Context) method.

  • Reuse and destruction

Call Recycle (webView: webView) before the page is closed and call destroy() before the application exits.

  • Reuse WebView returns blank

In the call recycle (webView: WebView); loadDataWithBaseURL(null, “”, “text/ HTML “, “UTF-8 “, null); Therefore, we need to check the bottom of the stack when returning. If it is empty, we will return it directly. The code is as follows:

fun canGoBack(): Boolean {
    val canBack = webView.canGoBack()
    if (canBack) webView.goBack()
    val backForwardList = webView.copyBackForwardList()
    val currentIndex = backForwardList.currentIndex
    if (currentIndex == 0) {
        val currentUrl = backForwardList.currentItem.url
        val currentHost = Uri.parse(currentUrl).host
        // if the link is not at the bottom of the stack, it is returned directly
        if (currentHost.isNullOrBlank()) return false
    }
    return canBack
}
Copy the code
if(! webViewHelper.canGoBack()) { onBackPressed() }Copy the code

Mandatory resource caching for client rendering

When a WebView opens the same page, images and styles may be loaded for multiple times. Therefore, it cannot be cached and shared with the client, which is also a waste of traffic. ShouldInterceptRequest WebViewClient shouldInterceptRequest WebViewClient shouldInterceptRequest

webView.webViewClient = object : WebViewClient(){ override fun shouldInterceptRequest( view: WebView? , request: WebResourceRequest? ) : WebResourceResponse? {if(view ! =null&& request ! =null) {
            if(canCacheResource(request)){
                return cacheResourceRequest(view.context, request)
            }
        }
        return super.shouldInterceptRequest(view, request)
    }
}
Copy the code
private fun canCacheResource(webRequest: WebResourceRequest): Boolean {
    val url = webRequest.url.toString()
    val extension = getExtensionFromUrl(url)
    return extension == "ico" || extension == "bmp" || extension == "gif"
            || extension == "jpeg" || extension == "jpg" || extension == "png"
            || extension == "svg" || extension == "webp" || extension == "css"
            || extension == "js" || extension == "json" || extension == "eot"
            || extension == "otf" || extension == "ttf" || extension == "woff"
}
Copy the code
private fun cacheResourceRequest(context: Context, webRequest: WebResourceRequest): WebResourceResponse? {
    try {
    	val url = webRequest.url.toString()
    	val cachePath = CacheUtils.getCacheDirPath(context, "web_cache")
    	val filePathName = cachePath + File.separator + url.encodeUtf8().md5().hex()
    	val file = File(filePathName)
    	if(! file.exists() || ! file.isFile) { runBlocking { download(HttpRequest(url).apply { webRequest.requestHeaders.forEach { putHeader(it.key, it.value) } }, filePathName) } }if (file.exists() && file.isFile) {
    		val webResourceResponse = WebResourceResponse()
    		webResourceResponse.mimeType = getMimeTypeFromUrl(url)
    		webResourceResponse.encoding = "UTF-8"
    		webResourceResponse.data = file.inputStream()
    		webResourceResponse.responseHeaders = mapOf("access-control-allow-origin" to "*")
    		return webResourceResponse
    	}
    } catch (e: Exception) {
    	e.printStackTrace()
    }
    return null
}
Copy the code

CanCacheResource (webRequest: WebResourceRequest) is used to determine if a resource needs to be forcibly cached. The file is then obtained from the URL cache. Otherwise, resources are downloaded through network requests. Only images, fonts, CSS, JS, and JSON are cached. More types of resources can be cached according to the actual situation of the project.

Preset offline package, page straight out, template split, preheating, reuse, etc

Load the preset offline package as follows:

private fun assetsResourceRequest(context: Context, webRequest: WebResourceRequest): WebResourceResponse? {
    try {
        val url = webRequest.url.toString()
        val filenameIndex = url.lastIndexOf("/") + 1
        val filename = url.substring(filenameIndex)
        val suffixIndex = url.lastIndexOf(".")
        val suffix = url.substring(suffixIndex + 1)
        val webResourceResponse = WebResourceResponse()
        webResourceResponse.mimeType = getMimeTypeFromUrl(url)
        webResourceResponse.encoding = "UTF-8"
        webResourceResponse.data = context.assets.open("$suffix/$filename")
        return webResourceResponse
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return null
}
Copy the code

Template split, preheat, reuse that is, load the local template, and then inject data through JS, the code is as follows:

<! DOCTYPE html><html>
<head>
<meta charset="utf-8">
<title>title</title>
<script>
    function changeContent(name){
        document.getElementById('content').innerHTML=name;
    }
</script>
</head>
<body>

    <div id="content"></div>

</body>
</html>
Copy the code
webView.evaluateJavascript("Javascript: changeContent (' I am HTML)") {}
Copy the code

CDN acceleration, DNS and other optimizations

Once said, this matter we all understand, do not understand, said you do not understand, it is better not to say. You don’t have to ask me what’s wrong. There are too many interests involved, and it won’t do you any good to talk about it. Just don’t know. Detailed information you find is very difficult to find, most of the net has been deleted clean, so I can only say that know all understand, do not know also no way. 😃 😏

The sample project

  • Making: fragmject

Thanks

That’s all for this article. If you have any questions, please point them out and we will make progress together. If you like it, please give me a thumbs-up. Your encouragement is my motivation to move forward. Thank you ~ ~

reference

Android WebView H5 seconds open solution summary baidu app-android H5 first screen optimization practice today headlines quality optimization – 图文 内 容 页 second open practice which technology optimization solution has been out of date?