当前位置: 首页 > 科技观察

移动端H5首屏秒开优化方案探讨

时间:2023-03-18 16:30:30 科技观察

随着移动设备性能的不断提升,网页的性能体验也逐渐被接受,并且由于web开发模式(跨平台)的诸多好处,动态更新,减量,***扩展),越来越多的内嵌网页出现在APP客户端(为了配合现在流行的说法,以下所有网页都称为H5页面,虽然可能什么都没有用H5做),很多APP把一些功能模块改成用H5来实现。虽然H5页面的性能有所提升,但是如果没有针对性的做一些优化,体验还是很差的。体验主要有两部分:页面启动时的白屏时间:打开一个H5页面需要一系列的处理,会有一段时间的白屏。屏幕时间,糟糕的体验。响应流畅性:由于webkit的渲染机制、单线程、历史包袱等原因,页面刷新/交互的性能体验不如原生。本文不讨论第二点,只讨论第一点,如何减少白屏时间。如何加快使用H5实现的APP中部分功能模块的启动速度,使其启动体验接近原版。为什么打开H5页面的过程白屏时间长?因为它做了很多事情,大概是:初始化webview->请求页面->下载数据->解析HTML->请求js/css资源->dom渲染->解析JS执行->JS请求数据->解析渲染->DownloadandRenderImages一些简单的页面可能没有JS请求数据这一步,但是大部分功能模块应该都有。根据当前用户信息,JS从后台请求相关数据,然后Rendering是常规的开发方式。一般在渲染完dom之后就可以显示页面的原型了。在此之前,用户看到的是空白屏幕,直到下载渲染图像后才会显示整个页面。首屏优化是为了减少耗时的过程。前端优化上面打开页面的过程中有很多优化点,包括前端和客户端。常规的前后端性能优化在桌面时代已经实践。主要有:减少请求量:合并资源,减少HTTP请求数,minify/gzip压缩,webP,lazyLoad。加速请求:预解析DNS,减少域名数量,并行加载,通过CDN分发。缓存:HTTP协议缓存请求、离线缓存清单、离线数据缓存localStorage。渲染:JS/CSS优化、加载顺序、服务器端渲染、管道。其中网络请求对首屏启动速度的影响最大,所以优化的重点是缓存。这里我们将重点介绍请求的前端缓存策略。我们再细分为HTML缓存、JS/CSS/图片资源缓存、json数据缓存。HTML和JS/CSS/image资源都是静态文件。HTTP本身提供缓存协议。浏览器实现这些协议并可以缓存静态文件。有关详细信息,请参阅此处。缓存一般有两种:询问是否有更新:根据If-Modified-Since/ETag等协议,请求到后端询问是否有更新,如果没有更新,则返回304,浏览器使用本地缓存。直接使用本地缓存:根据协议中的Cache-Control/Expires字段判断多久不能发送请求更新,直接使用本地缓存。前端最大的缓存策略是:HTML文件每次都向服务器请求更新,而JS/CSS/Image资源文件不请求更新,直接使用本地缓存。如何更新JS/CSS资源文件?一种常见的做法是在构建过程中为每个资源文件赋予一个版本号或哈希值。如果资源文件更新,版本号和哈希值发生变化,资源请求的URL也会发生变化。,相应的HTML页面被更新以请求新的资源URL,资源也被更新。json数据的缓存可以使用localStorage缓存请求的数据,显示***时可以先使用本地数据,再请求更新,由前端JS控制。这些缓存策略可以实现对JS/CSS等资源文件和用户数据缓存的全缓存,每次都可以直接使用本地缓存数据,无需等待网络请求。但是HTML文件的缓存是做不到的。对于HTML文件,如果Expires/max-age时间设置的过长,长时间只使用本地缓存,那么更新就不会及时。网络请求询问是否有更新,然后判断是否使用本地资源。一般这里前端的策略是每次请求。在网络弱的情况下,用户感受到的白屏时间还是会很长。所以“缓存”和“更新”HTML文件之间存在矛盾。客户端优化接下来就轮到客户端上场了。桌面时代受限于浏览器,H5页面无法做更多的优化。现在H5页面内嵌在客户端APP中,客户端拥有更多的权限。因此,在客户端端,可以超越浏览器的范围,做更多的优化。HTML先缓存后缓存。客户端有一个更自由的缓存策略。客户端可以拦截所有对H5页面的请求,自行管理缓存。针对上述HTML文件的“缓存”和“更新”的矛盾,我们可以采用这种策略来解决:在客户端拦截请求,缓存第一次请求HTML文件后的数据,并直接使用缓存的数据,无需第二次发送请求。何时请求更新?这个更新请求可以由客户端自由控制。可以使用本地缓存打开本地页面,然后在后台发起更新缓存的请求。在后台发起预更新请求,以增加用户访问被黑代码的机会。这似乎是相对安全的。HTML文件使用客户端的策略进行缓存,其余资源和数据按照上述前端缓存方式进行。这样一个H5页面,第二次从HTML访问JS/CSS/Image资源,然后到本地直接读取数据,不需要等待网络请求,同时可以实时更新为尽可能的解决了缓存问题,大大提升了H5页面首屏的启动速度。问题上面的方案看似完全解决了缓存问题,但实际上还有很多问题:没有预加载:第一次打开体验很差,所有数据都要从网络请求。不可控缓存:缓存访问受系统webview控制,其缓存逻辑无法控制。这些问题包括:i.清理逻辑不可控,缓存空间有限。缓存几张大图后,清除重要的HTML/JS/CSS缓存。二.磁盘IO无法控制,数据无法从磁盘预加载到内存。更新体验差:更新时后台HTML/JS/CSS全部下载,数据量大,弱网下载时间长。无法防止劫持:如果HTML页面被运营商或其他第三方劫持,被劫持的页面会被长期缓存。这些问题在客户端都可以解决,但是有点麻烦。简单描述一下:可以配置一个预加载列表,在APP启动的时候或者特定的时间提前请求。这个preloadlist需要包含需要的H5Module页面,资源也需要考虑一个H5module有多个页面的情况。这个列表可能非常大,需要工具来生成和管理这个预加载列表。客户端可以接管所有请求的缓存,而不是遵循webview默认的缓存逻辑,自己实现缓存机制,分为缓存优先级和缓存预加载。可以对每个HTML和资源文件做增量更新,但是实现和管理起来比较麻烦。在客户端使用httpdns+https来防止劫持。上述方案实现起来非常繁琐,因为各种HTML和资源文件比较分散,难以管理。有一个更好的方案来解决这些问题,那就是离线包。由于离线包的很多问题都是因为文件难以分散管理,而我们这里的使用场景是使用H5开发功能模块,所以很容易想到将各个功能模块的相关页面和资源全部打包交付.这个压缩包可以称为是功能模块的离线包。使用离线包方案可以比较轻松的解决以上问题:可以提前下载整个离线包,只需要按照业务模块进行配置,不需要按照文件配置。离线包包含所有与业务模块相关的页面,可以Preloading。离线包核心文件与页面动态图片资源文件缓存分离,可以更方便的管理缓存。离线包也可以整体提前加载到内存中,减少耗时的磁盘IO。离线包可以方便的根据版本进行增量更新。线下包以压缩包形式交付,同时进行加密验证,运营商和第三方无法劫持和篡改。到目前为止,离线包是一个非常好的使用H5开发功能模块的解决方案。简单回顾一下离线打包的方案:后台通过构建工具将同一业务模块相关的页面和资源打包成一个文件,同时对文件进行加密/签名。客户端根据配置表,在自定义时间拉取离线包,进行解压/解密/校验等工作。根据配置表,开启服务时,会跳转至开启离线包的入口页面。拦截网络请求。对于离线包中已经存在的文件,直接读取离线包数据返回,否则走HTTP协议缓存逻辑。更新离线包时,后台根据版本号发送两个版本的diff数据,合并客户端,进行增量更新。比较优化的离线包方案缓存都差不多了,可以加一些更细化的优化:公共资源包的每个包都会使用相同的JS框架和CSS全局样式,这些资源会在每个离线包中重复出现,太浪费了,你可以制作一个公共资源包来提供这些全局文件。Preloadwebview无论是iOS还是Android,初始化本地webview都需要大量的时间,所以可以预先初始化webview。这里有两种预加载:***预加载:***在一个进程中初始化webview与第二次初始化不同,***会比第二次初始化慢很多。原因估计是webview完全初始化后,即使webview已经释放了,但是一些全局的服务或者多个webview共享的资源对象还没有释放,第二次初始化时不需要重新生成这些对象让它更快。我们可以预先初始化一个webview,然后在APP启动的时候释放,这样当用户真正走到H5模块加载webview的时候会更快。webviewpool:可以和两个或多个webview复用,而不是每次打开H5都创建一个新的webview。但是这个方法需要解决页面跳转时清除上一页的问题。另外,如果某个H5页面的JS内存泄露,会影响到其他页面,在APP运行过程中无法释放。预加载数据离线包方案理想情况下是在第一次打开时对所有的HTML/JS/CSS使用本地缓存,不需要等待网络请求,但是页面上的用户数据还是需要实时拉取。这里我们可以做一个优化。在初始化webview的同时,并行请求数据。webview初始化需要一些时间。在此期间,没有网络请求。这时候并行请求可以节省很多时间。具体实现上,可以先在配置表中指明需要预加载离线包的URL。客户端在webview初始化的同时发起请求。该请求由经理管理。当请求完成后,将结果缓存起来,然后webview初始化后启动。请求刚刚预加载的URL,客户端拦截请求,转给刚才提到的请求管理器,预加载完成直接返回内容,预加载未完成则等待。Fallback用户访问离线包模块,离线包还未下载,或者配置表检测到有新版本,本地版本旧,如何处理?几种解决方法:简单的解决方法是如果本地离线包没有或者不是***,就同步阻塞,等待下载***离线包。这种用户打开体验就更差了,因为离线包体积比较大。也可以是如果本地有旧包,用户这次可以直接使用旧包。如果没有再同步块等待,这会导致更新延迟,无法保证用户使用的是最新版本。您也可以制作离线包的在线版本。离线包中的文件在服务器上有一个一一对应的访问地址。当本地没有离线包时,可以直接访问对应的在线地址,就像传统打开在线页面一样。这种体验比等待下载整个离线包要好,也能保证用户可以访问***。第三种Fallback方法也带来了底线的好处。在某些意外情况下,当离线包失败时,可以直接访问在线版,不会影响功能。另外,在公共资源包更新不及时,版本不匹配的情况下也可以使用。直接访问在线版本是一个很好的底线。以上几种方案和策略也可以结合使用,视业务需要而定。如果使用clientinterfacenetwork和storageinterface,如果使用webkit的ajax和localStorage,会有很多限制,很难优化。你可以把这些接口提供给客户端的JS,客户端可以对网络请求做DNS预解析/IP直接解析之类的事情。持续/长连接/并行请求等更细化的优化,存储也可以使用客户端接口进行读写并发/用户隔离等针对性优化。在早期网页的服务器端渲染中,JS只负责交互,所有内容直接在HTML中。在现代H5页面中,很多内容已经依赖JS逻辑来决定渲染什么,比如等待JS请求JSON数据,然后拼接成HTML生成DOM渲染到页面,这样渲染显示页面必须等待整个过程。这里有一个耗时,减少这里的耗时也在白屏优化的范围内。优化的方式可以是人为的减少JS渲染逻辑,也可以更彻底,返璞归真。所有的内容都是由服务器返回的HTML决定的,不需要等待JS逻辑,这叫做服务端渲染。是否做这种优化要看业务情况。毕竟这会带来开发模式改变/流量增加/服务器开销增加等负面影响。手Q部分页面使用服务端渲染,称为动态直出,详见文章。***从前端优化,到客户端缓存,到离线包,再到更细致的优化,做到以上几点,H5页面在启动方面几乎可以媲美原生体验。总结一下,大体的优化思路是:缓存/预加载/并行,缓存所有的网络请求,尽量在用户打开之前加载所有的内容,能并行的不串行。这里的一些优化方法需要有一套完整的工具和流程支持,需要权衡开发效率,根据实际需要进行优化。另外,以上讨论的是功能模块H5页面即时打开的优化方案。除了客户端APP上的功能模块外,其他H5页面如营销活动/外访等可能会有一些不适用的优化点。视情况和需要而定。另外,微信小程序属于功能模块的范畴,几乎是同一个套路。这里我们讨论H5页面首屏启动时间的优化。经过上述优化,基本上就只剩下webview本身的启动/渲染机制了。像RN/Weex这样的程序有机会再讨论。