百度APP-Android H5首屏优化实践
一、背景
百度App在2016年上半年尝试了Feed流业务形态,到2017年下半年,经过10次迭代,产品形态初步探索基本完成。在整个feed流表单的闭环中,新闻详情页(文中称为落地页)是重要的一环。如果页面打开时间过长,会严重影响用户体验。因此,我们针对着陆页等H5首屏的显示速度进行了长期优化。本文将详细阐述整个优化思路和技术细节。
二、方法论
通过分析用户反馈发现,着陆页平均需要 3 秒才能出现在首屏。每当用户急切地想浏览感兴趣的文章时,由于时间太长,他都迫不及待地选择了。背部。为了提升用户体验,我们做了以下工作:
三、解决方案和性能瓶颈的简要说明
(一)节目简介
在优化之前,我们和业界大多数应用一样,在登陆页面的技术选择上采用了这种相对成熟的方案,以满足跨平台和动态的需求。,顾名思义,即混合开发,是一种半原生半Web的方式。页面中复杂的交互功能是通过调用原生API以终端能力的形式实现的。成本低,灵活性好,适用于部分信息展示的H5场景。
下图表示百度App中的实现机制和加载过程
(二)性能瓶颈
为了分析解决方案首屏显示慢的原因,找出具体的性能瓶颈,客户端和前端分别统计各自加载过程中的关键节点,通过性能展示监控平台日志。下图截取某天全网用户登陆页首页显示速度的第80个百分位数据
每个阶段的性能点可以根据加载过程进行划分。可以看出,从点击到首屏显示,大致是需要的。NA组件的初始化耗时350ms,初始化耗时170ms。前端H5执行JS获取文本并渲染需要,完成图片加载和渲染耗时700ms
我们具体分析接下来四个阶段的性能损失主要发生在哪里:
1) 初始化 NA 组件
从点击到登陆页面框架初始化完成,主要工作是初始化,尤其是第一个入口(创建第一个时间平均耗时500ms)
2) 初始化
这一阶段的工作主要由两部分组成。一是根据调用协议中传入的相关参数,对解压后发送到本地的模板进行校验,耗时100ms左右;以及身体的分析
3) 身体加载和渲染
在这个阶段,内核已经完成了模板头和正文的解析。此时需要加载解析页面所需的JS文件,通过JS调用者能力发起对body数据的请求。,并将其发送回前端。前端需要解析客户端发来的JSON格式的body数据,构造DOM结构,然后触发内核的渲染过程;这个过程涉及到对JS的请求、加载、解析和执行等一系列步骤,还有终端能力调用、JSON解析、DOM构建等操作,比较耗时
4) 图片加载
在(3)这一步,前端获取到的body数据中包含着陆页的图片地址集。body渲染完成后,前端需要执行图片请求的结束能力再次,客户端收到设置好的图片地址,然后依次向服务器请求,下载完成后,客户端会调用IO将文件写入缓存,同时返回本地地址将对应的图片发送到前端,最后通过内核发起IO操作,获取图片数据流进行渲染。 ;总体来说,图片渲染时间取决于前端解析效率、端容量执行效率、下载速度、IO速度等因素
通过分析,引申出对方案的一些思考:
四、百度App落地页优化方案
(一)
基于前面对性能的分析,我们孵化了一个项目,叫做项目,解决落地页首屏显示慢的痛点;用一句话描述解决方案,就是采用后端直出+预取+拦截的方式,简化页面渲染过程,提前&并行化网络请求逻辑,从而提升H5首屏速度
1.后端直出——快速渲染首屏
一种。页面静态直出
对于解决方案,终端上预置加载的html文件只是一个模板文件,里面包含一些简单的JS和CSS文件。终端加载HTML后,需要通过终端能力执行JS异步请求body数据,获取数据后,以及解析JSON、构建DOM、应用CSS样式等一系列耗时的步骤等,最终可以由内核渲染到屏幕上;为了提高首屏的显示速度,可以使用后端渲染技术()修改文本数据和前端代码。对于集成,第一屏的内容是直截了当的。直出后的html文件包含首屏显示所需的内容和样式,内核可以直接渲染;首屏以外的内容(包括相关推荐、广告等)可以在首屏后在内核中渲染。屏幕显示后,执行JS,用于异步渲染
百度APP直接输出解决方案:
对于客户端,从 CDN 拉取的 html 已经在渲染第一个屏幕。这样的内容不需要二次处理,可以大大提高显示速度。稍微直接一点,手动提要将落在页面顶部。屏幕性能数据从优化到内
湾。动态信息回填
为了保证首屏渲染结果的准确性,除了整合侧边的文本内容和前端代码外,还需要一些影响页面渲染的客户端状态信息,比如第一张图片、字体大小、夜间模式等。
这里我们使用动态回填的方法。前端会在直出的html中定义一系列特殊字符来占位;在客户端之前,它会使用正则匹配的方法找到这些占位符,并将它们映射到客户端信息中;客户端回填处理后的HTML内容已经具备显示首屏的所有条件
C。动画之间的渲染
先看优化前后的效果(上:优化前;下:优化后):
正常情况下,直出后的页面显示速度很快;但在实际开发中,可能会遇到即使数据加载速度快也无法在切换过程中渲染H5页面的问题(可以在开发者模式下通过减慢动画时间来验证),导致视觉白屏现象(如上图)
通过研究源码,我们发现系统在处理视图绘制的时候,是有一个属性的。从名字可以看出,这个属性是用来控制动画是否可以正常绘制的,恰好在 4.2 到 N 之间,系统考虑了组件切换的过程,这个字段为false,我们可以使用反射手动修改这个属性,改进后的效果如下图所示
/**
* 让 activity transition 动画过程中可以正常渲染页面
*/
private void setDrawDuringWindowsAnimating(View view) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
// 1 android n以上 & android 4.1以下不存在此问题,无须处理
return;
}
// 4.2不存在setDrawDuringWindowsAnimating,需要特殊处理
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
handleDispatchDoneAnimating(view);
return;
}
try {
// 4.3及以上,反射setDrawDuringWindowsAnimating来实现动画过程中渲染
ViewParent rootParent = view.getRootView().getParent();
Method method = rootParent.getClass()
.getDeclaredMethod("setDrawDuringWindowsAnimating", boolean.class);
method.setAccessible(true);
method.invoke(rootParent, true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* android4.2可以反射handleDispatchDoneAnimating来解决
*/
private void handleDispatchDoneAnimating(View paramView) {
try {
ViewParent localViewParent = paramView.getRootView().getParent();
Class localClass = localViewParent.getClass();
Method localMethod = localClass.getDeclaredMethod("handleDispatchDoneAnimating");
localMethod.setAccessible(true);
localMethod.invoke(localViewParent);
} catch (Exception localException) {
localException.printStackTrace();
}
}
2.智能预取——提前网络请求
直出式改造后,为了更快的渲染首屏,减少过程中涉及的耗时网络请求,我们可以按照一定的策略和时机提前向CDN请求部分落地页html,并将其缓存在本地,这样当用户点击查看新闻时,只需从缓存中加载
移动预取服务架构图
目前,移动预取服务支持图形、图集、视频、广告等多种业务方。根据不同的业务场景,可以自定义触发时机,也可以按照我们默认的刷新、滑动停止、点击等时机。另外,我们会优先预取内容(根据资源类型和触发时机),并根据当前手机状态信息动态控制并发和流量。在一些降级场景下,也可以通过云控制来控制是否预取以及预取多少
3.通用拦截——缓存共享、请求并行
除了文字,图片也是登陆页面的重要组成部分。直出解决了文字显示的速度问题,但是图片的加载和渲染速度仍然不理想,尤其是首屏有图片的文章,首屏渲染速度才是真正的首屏时间点
传统方案中,前端页面通过终端能力调用NA图片下载能力来缓存和渲染图片。虽然实现了客户端和前端图片缓存的共享,但是JS的执行时间比较晚,多个终端能力调用存在效率问题,导致图片渲染延迟
初步改进计划:为了提高图片加载速度,减少JS调用耗时,将请求图片改为纯H5。虽然速度提高了,但是客户端和前端缓存不能共享。点击图片调出NA图片查看器时,为了达到身临其境的效果,还需要重新下载图片,造成流量浪费
终极解决方案:通过内核的st回调拦截登陆页图片请求,由客户端调用NA图片下载框架下载,并以管道方式填充到内核中
该方案在满足图像渲染速度的同时,解耦了客户端和前端代码。客户端作为一个角色来请求和缓存图片,保证前端和客户端可以共享图片缓存。修改后的解决方案不是第一个图像。显示过程,页面不卡顿,首屏第80个百分值缩短80ms~150ms
效果如下(上图:优化前的方案;下图:优化后的一般拦截方案):
4.整体程序流程
(二)新的优化尝试
1.预创建
为了节省性能损失,我们可以在合适的时间提前创建并存储在缓存池中。当页面需要显示内容时百度优化,我们可以直接从缓存池中获取创建的内容。根据性能数据,预创建可以提高首屏的渲染时间。200毫秒+
具体以feed落地页为例,当用户入手触发feed上限操作时,我们会创建第一个。当用户进入登陆页面时,会从缓存池中取出来渲染H5页面,以免影响页面。同时,我们会确保下次登陆页面缓存池中仍有可用组件。我们会在每次页面加载()或者后台退出登陆页面的时候触发预先创建的逻辑。
由于需要初始化和绑定,如果要实现预先创建的逻辑,需要保证一致性。作为一般的实践,我们认为它可以用来实现一个承载H5页面的容器。这样,你可以使用外部实例,但切换本身是平滑的。度数存在一定的问题百度优化,这样做会限制预创建适用的场景。为此,我们找到了一个更完美的替代方案,即r
在它设置之后的基础。这个的基础。然后所有呼叫都将发送到 base 。, 底座可以在一个设置后。
简单来说就是一个新的包装类,允许对其进行外部修改,所有被调用的方法都会被委托去执行
下面是一段预先创建的代码
/**
* 创建WebView实例
* 用了applicationContext
*/
@DebugTrace
public void prepareNewWebView() {
if (mCachedWebViewStack.size() < CACHED_WEBVIEW_MAX_NUM) {
mCachedWebViewStack.push(new WebView(new MutableContextWrapper(getAppContext())));
}
}
/**
* 从缓存池中获取合适的WebView
*
* @param context activity context
* @return WebView
*/
private WebView acquireWebViewInternal(Context context) {
// 为空,直接返回新实例
if (mCachedWebViewStack == null || mCachedWebViewStack.isEmpty()) {
return new WebView(context);
}
WebView webView = mCachedWebViewStack.pop();
// webView不为空,则开始使用预创建的WebView,并且替换Context
MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
contextWrapper.setBaseContext(context);
return webView;
}
2.NA 组件延迟加载
一种。初始化完成,立即完成,无需等待帧或结束
湾。在初始完成和页面首屏绘制完成之间,尽量减少UI线程的其他操作。繁忙的 UI 线程会减慢速度。
具体到feed落地页场景,由于我们的落地页包含+NA评论组件两部分,正常流程会启动评论组件的初始化和初始化结束后评论数据的获取。由于此时注释的初始化还在UI消息处理中,会严重延迟内核加载主文档的逻辑。考虑到当用户进入落地页时,评论组件对用户是不可见的,所以评论组件的初始化要延迟到页面或者shed的时机;第80个百分位性能提升60ms~100ms
3.内核优化
一种。内核渲染优化:
内核主要分为三个线程(, , ),首先会从网络或者本地获取html数据,然后将数据交给(渲染线程,很忙,用于JS执行,页面布局等) .),为了保证不阻塞需要额外的后台线程()来做html解析工作。每次解析落地页html中带有特殊class标签的div标签或P标签(如图),触发一个job,将得到的高度与屏幕高度进行比较。如果当前高度已经大于屏幕高度。我们认为第一屏内容的布局已经完成,可以触发渲染逻辑了。无需等待整个 HTML 解析完毕再放到屏幕上,缩短了首屏的渲染时间;在第 80 个百分位,内核的渲染得到优化。可提升首屏速度100ms~200ms
湾。预加载 JS:
预创建后,通过预加载JS(与内核约定的JS内容,内核端执行JS时,只进行初始化操作),触发初始化逻辑,后续url加载耗时被缩短;第80个百分位的性能提升约80ms
五、新问题 - 流量和速度的平衡
频繁预取会浪费流量:虽然预取的命中率在90%以上,但有效率只有15%
解决方案:
1.压缩预取包大小以减少下行流量
2.较少或没有预取
(一)减少预取数据:
图形:优化html中的内联css、icon等数据,数据量减少40%左右
(二)后端智能预取:
1)图形:通过对图形资源打分判断4G是否需要预取,多组AB测试的最优效果降级9.5ms
2)视频:为了平衡性能和流量,在可接受的性能降级范围内(视频开始时间降级100ms),视频部分采用流量高峰不预取的策略,减少总视频流量减少约 7%,整体带宽峰值下降 3%
(三)AI智能预取
一般用户操作行为,Feed预取AI预测,减少无效预取次数。
六、总结与展望
(一)优化总结
总结之前,先来看看整体优化前后效果对比(上:优化前;下:优化后):
可以看到,经过一系列的优化手段,落地页已经达到秒开的效果。回顾我们所做的,从分析用户反馈到定位性能瓶颈,再到各种优化尝试,发现所有类似的性能优化方法都可以从以下几点入手:
(二)待办事项