从1200ms到400ms,一份Webview启动优化实操案例
总体链路
在Android上完整打开一个WebView需要经历的链路:
所以,要想优化WebView的加载速度,就要想办法去缩短链路中这些节点所耗费的时间。
从客户端的角度上来讲,Page初始化就是H5容器的初始化。一般而言,容器初始化时间是比较快的(如果没有太多初始化逻辑的话),优化空间有限。WebView的初始化相对就比较复杂,涉及到了浏览器内核在主线程的初始化。APP冷启动后,首次创建WebView时需要去初始化浏览器内核。
Webview优化
预创建
预创建WebView,当内存中没有预创建的WebView可以复用(即预创建没有命中)时,就走原来创建WebView的逻辑。
预创建个数
这里我们选择只预创建1个WebView。之所以选择1个,是因为我们预创建WebView的根本目的,是为了解决APP首次安装/冷启动时,第一个WebView加载慢的问题。后续的WebView实例的创建都是很快的。所以,即使后面没有命中预创建的WebView,用的重新创建的WebView,也就是多花了15ms左右的时间,影响是很小的。所以综合下来,预创建1个WebView的性价比是最高的,多了反而浪费内存。
预创建时机
这里的时机可以选择把这个时机放到进入首页后,用IdleHandler进行主线程闲时创建。
其次是在预创建的WebView被拿去复用后,此时也是需要预创建的。因为一旦被拿去复用,意味着我们缓存中已经没有可用的WebView了,若打开新的页面,我们最好也能提供预创建的WebView。
具体代码:
object WebviewCacheHolder {
private val webViewCacheStack = Stack<RobustWebview>()
//最大预存的Webview 现阶段只有一个不需要太多
private const val CACHED_WEB_VIEW_MAX_NUM = 1
private lateinit var application: Application
fun init(application: Application) {
this.application = application
prepareWebView()
}
private fun prepareWebView() {
if (webViewCacheStack.size < CACHED_WEB_VIEW_MAX_NUM) {
Looper.myQueue().addIdleHandler {
if (webViewCacheStack.size < CACHED_WEB_VIEW_MAX_NUM) {
webViewCacheStack.push(createWebView(MutableContextWrapper(application)))
}
false
}
}
}
fun acquireWebViewInternal(context: Context): RobustWebview {
if (webViewCacheStack.isEmpty()) {
return createWebView(context)
}
val webView = webViewCacheStack.pop()
val contextWrapper = webView.context as MutableContextWrapper
contextWrapper.baseContext = context
prepareWebView()
return webView
}
private fun createWebView(context: Context): RobustWebview {
return RobustWebview(context)
}
}
预加载
预加载,其实就是在预创建的基础上,执行 WebView#loadUrl(),将页面提前渲染完成。
接口优化
我们的业务在登陆后获取到用户 token 后传递给 H5 页面,在 H5 页面加载成功后才会调用菜单请求,我们可以把这个请求前置到登录后获取到 token 直接调用菜单,待H5页面加载成功后把数据传过去。
js优化
在webview加载完成后页面开始请求 js 文件并且解析,在这个阶段可以把js文件通过摇树优化,减少文件体积,从而加快响应速度。
摇树优化就是将项目中没有用到的代码,或者是永远不会执行的代码,在 Uglify 阶段查出,不打包到 bundle 中。
开启前:
开启后:
前后对比
经实际测试优化后整体从平均 1200ms 缩减到 400ms。
参考
转载自:https://juejin.cn/post/7380645714932908095