Android-H5首屏秒开优化方案

Android-H5首屏秒开优化方案

一、遇到的问题

在实现阅读APP从原生渲染迁移到webview渲染过程中,发现WebView第一加载会出现白屏,但是后续打开WebView速度就会明显快很多。

二、具体实现方案

首先,通过客户端去请求API数据,拿到数据后解析数据,并且把具体渲染文本组装成HTML,并把组装的HTML内容内嵌到本地的HTML模板,然后通过WebView加载本地HTML、CSS和JS。对于文章中具体词和词组的标记,也是通过客户端请求数据后,通过PieBridge通信到WebView层,异步添加标记,确保首次优先加载出文章文本。此外后续我们还打算支持HTML模板+CSS+JS离线包动态下发功能,这样就能动态更换文章皮肤和主题。

三、分析问题

遇到WebView第一次加载会出现短暂白屏的问题,其实还是蛮惊讶,因为参考市面上webview优化方案,我们近似地做了静态直出和动静分离、Native代理所有网络请求解析数据组装HTML。webview仅仅就负责渲染本地HTML。

经过分析WebView启动整个加载生命周期发现: WebView加载一个页面,首先需要经历webView初始化、然后建立连接、接收页面、接收样式、下载脚本解析执行,接收数据,最后渲染。

当App首次打开时,默认是并不初始化浏览器内核的;只有当创建WebView实例的时候,才会创建WebView的基础框架。 所以与浏览器不同,App中打开WebView的第一步并不是建立连接,而是启动浏览器内核

于是我们找到了“为什么WebView总是很慢”的原因之一:

在浏览器中,我们输入地址时(甚至在之前),浏览器就可以开始加载页面。而在客户端中,客户端需要先花费时间初始化WebView完成后,才开始加载。而这段时间,由于WebView还不存在,所有后续的过程是完全阻塞的。

找到了问题,那么就去解决问题,主要是解决webview首次初始化带来时间开销。

四、解决方案

1、静态直出方案进一步优化

为了进一步减轻客户端时间开销,我们把原来的解析数据具体渲染文本组装成HTML工作直接放到后端了,也就是说后端就直接输出拼接好的HTML片段,客户端只需要拿到这个片段内嵌到本地模板中,然后放到webview就直接渲染了。(这样就能省去客户端解析数据+组装HTML的时间,进一步提高了整体的加载速度)

2、客户端请求和WebView加载的并行化(PieBridge的支持)

在原有方案中,客户端请求数据、解析数据以及组装HTML、内嵌HTML到本地模板中、到webview初始化以及最后的渲染都是串行化的。有了PieBridge通信桥的支持,我们可以实现客户端数据请求和webview加载并行化。如果客户端数据请求先结束了,webview未加载完毕此时PieBridge会把通信的消息放入到MessageQueue消息队列中,一旦webview加载完毕了就开始调用dispatchMessage分发消息。如果webview先加载完毕,此时网路请求未结束,可以在具体内容部分以loading提示,等到数据到时插入HTML片段进行渲染即可。这样就保证了整体渲染的及时性。

3、WebView预加载(MutableContextWrapper)+WebView 复用池+借鉴Glide生命周期的绑定思想。

前两步优化实际上还没有第3步来得明显,因为Webview的初始化,内核的启动所占用的时间是阻塞且很长。webview的初始化优化主要分为三步: WebView的预加载(MutableContextWrapper)、WebView的复用池、为了内部更好维护生命周期以及防止内存泄漏,借鉴了Glide生命周期绑定的思想,采用创建空白Fragment方式自检测Activity销毁后,改变ContextWrapper中的context以及释放webview池。

1、第一步,使用MutableContextWrapper实现WebView的预加载

简单来说,MutableContextWrapper就是一种新的context包装类,允许外部修改它的baseContext,并且所有ContextWrapper调用的方法都会代理到baseContext来执行。所以我们决定在Application启动的时候,先创建一个webview实例对象,与webview绑定的context是ApplicationContext,然后再将webview实例对象加入到webview复用池栈中。

2、第二步,加载webview, 带着当前Activity的context去复用池栈中获取预加载好的Webview实例。

首先在栈中找到WebView的Context是ApplicationContext的webview实例,并通过MutableContextWrapper中setBaseContext方法将当前Activity的context设置,这样此时WebView实例就和Activity绑定了,然后,再重新创建一个新的WebView实例,Context是ApplicationContext,始终保证栈顶存在一个可复用的已初始化好的WebView实例,为了便于webViewA 打开 webViewB ,此时的webViewB也能拿到初始化好的webView实例。

3、第三步,就是当前Activity被销毁, 修改回当前WebView中的Context为ApplicationContext

通过MutableContextWrapper将当前webview的context修改为ApplicationContext,如果此时栈顶还存在可复用的WebView将它的Context销毁移除栈中. (为了防止Activity内存泄漏)

4、第四步,借鉴了Glide生命周期绑定的思想

为了更好地维护和管理生命周期需要修改Context以及移除栈顶元素问题,不用需要外部手动去释放和管理,借鉴了Glide生命周期绑定的思想,根据传入的Context,在内部创建一个不可见的Fragment,并把它加入到FragmentManager中,一旦Activity销毁后就在执行处理WebView的Context切换以及栈管理。无需外部调用者手动处理。

以下为大致的流程图:

loading-ag-1849

五、扩展和补充

基于这套经验,后续又把它webview渲染技术方案用于了公司的其他业务。比如带交互的富文本渲染业务。我们后续使用webivew渲染技术实现一套来渲染markdown组件,在原生页面中嵌套webview来渲染富文本, 内容方只需要将内容以markdown格式输出,后端进行将markdown解析成HTML片段,分发给到客户端。通过PieBridge实现Native和JS之间的交互,整体渲染和交互都很不错。


   转载规则


《Android-H5首屏秒开优化方案》 mikyou 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Android组件化通信方案 Android组件化通信方案
Android组件化通信方案一、背景随着业务规模不断扩大,模块化和组件化必将是一个很大的趋势。组件化的基础就是模块化,不断将业务进行拆分,按照业务边界将其拆分成一个个独立可测试、可运行的模块。但是必须面临一个很大问题就是组件与组件之间的通信
2019-12-23
下一篇 
Android布局、网络、电量、APP启动、APP瘦身分析和优化 Android布局、网络、电量、APP启动、APP瘦身分析和优化
Android布局、网络、电量、APP启动、APP瘦身分析和优化一、布局优化 布局优化的一个目的也就是减少层级的嵌套,提高渲染,从而避免卡顿问题出现。我们都知道大部分设备屏幕刷新率是60HZ,也就是渲染一帧的时间是16.6ms,如果出现某一
2019-12-22