百度App自2016年上半年尝试Feed流业务形态,至2017年下半年,历经10个版本的迭代,基本完成了产品形态的初步探索。在整个Feed流形态的闭环中,新闻详情页(文中称为落地页)作为重要的组成部分,如果打开页面后,loading时间过长,会严重影响用户体验。因此我们针对落地页这种H5的首屏展现速度进行了长期优化,本文会详细阐述整个优化思路和技术细节
通过分析用户反馈,发现当时的落地页从点击到首屏展现平均需要3s的时间,每次用户兴致勃勃的想要浏览感兴趣的文章时,却因为过长的loading时间,而不耐烦的选择了back。为了提升用户体验,我们进行了以下工作:
优化之前,我们与业内大多数的App一样,在落地页的技术选型中,为了满足跨平台和动态性的要求,采用了Hybrid这种比较成熟的方案。Hybrid,顾名思义,即混合开发,也就是半原生半Web的方式。页面中的复杂交互功能采用端能力的方式,调用原生API来实现。成本低,灵活性较好,适合偏信息展示类的H5场景
下面用一张图来表示百度App中Hybrid的实现机制和加载流程
为了分析Hybrid方案首屏展现较慢的原因,找到具体的性能瓶颈,客户端和前端分别针对各自加载过程中的关键节点进行埋点统计,并借由性能监控平台日志进行展示,下图是截取的某一天全网用户的落地页首屏展现速度80分位数据
各阶段性能点可以按Hybrid加载流程进行划分,可以看到,从点击到首屏展现,大致需要2600ms,其中初始化NA组件需要350ms,Hybrid初始化需要170ms,前端H5执行JS获取正文并渲染需要1400ms,完成图片加载和渲染需要700ms的时间
我们具体分析下四个阶段的性能损耗主要发生在哪些地方: 1) 初始化NA组件 从点击到落地页框架初始化完成,主要工作为初始化WebView,尤其是第一次进入(WebView首次创建耗时均值为500ms)
2) Hybrid初始化
这个阶段的工作主要包含两部分,一个是根据调起协议中传入的相关参数,校验解压下发到本地的Hybrid模板,大致需要100ms的时间;此外,WebView.loadUrl执行后,会触发对Hybrid模板头部和Body的解析
3) 正文加载&渲染
执行到这个阶段,内核已经完成了对Hybrid模板头部和body的解析,此时需要加载解析页面所需的JS文件,并通过JS调用端能力发起对正文数据的请求,客户端从Server拿到数据后,用JsCallback的方式回传给前端,前端需要对客户端传来的JSON格式的正文数据进行解析,并构造DOM结构,进而触发内核的渲染流程;此过程中,涉及到对JS的请求,加载、解析、执行等一系列步骤,并且存在端能力调用、JSON解析、构造DOM等操作,较为耗时
4) 图片加载
第(3)步中,前端获取到的正文数据包含落地页的图片地址集,在完成正文的渲染后,需要前端再次执行图片请求的端能力,客户端这边接收到图片地址集后按顺序请求服务器,完成下载后,客户端会调用一次IO将文件写入缓存,同时将对应图片的本地地址回传给前端,最终通过内核再发起一次IO操作获取到图片数据流,进行渲染;
总体来看,图片渲染的时间依赖前端的解析效率、端能力执行效率、下载速度、IO速度等因素
通过分析,延伸出对Hybrid方案的一些思考:
基于之前对Hybrid性能的分析,我们内部孵化了一个叫做CloudHybrid的项目,用来解决落地页首屏展现慢的痛点;一句话来形容CloudHybrid方案,就是采用后端直出+预取+拦截的方式,简化页面渲染流程,提前化&并行化网络请求逻辑,进而提升H5首屏速度
对于Hybrid方案来说,端上预置和加载的html文件只是一个模板文件,内部包含一些简单的JS和CSS文件,端上加载HTML后,需要执行JS通过端能力从Server异步请求正文数据,得到数据后,还需要解析JSON,构造DOM,应用CSS样式等一系列耗时的步骤,最终才能由内核进行渲染上屏;为了提升首屏展示速度,可以利用后端渲染技术(smarty)对正文数据和前端代码进行整合,直出首屏内容,直出后的html文件包含首屏展现所需的内容和样式,内核可以直接渲染;首屏外的内容(包括相关推荐、广告等)可以在内核渲染完首屏后,执行JS,并利用preact进行异步渲染
百度APP直出方案:
对于客户端来说,从CDN中拉取到的html都是已经在server渲染好首屏的,这样的内容无需二次加工,展现速度可以大大提升,仅直出一点,手百Feed落地页的首屏性能数据就从2600ms优化到2000ms以内
为了保证首屏渲染结果的准确性,除了在server侧对正文内容和前端代码进行整合外,还需要一些影响页面渲染的客户端状态信息,例如首图地址、字体大小、夜间模式等 这里我们采用动态回填的方式,前端会在直出的html中定义一系列特殊字符,用来占位;客户端在loadUrl之前,会利用正则匹配的方式,查找这些占位字符,并按照协议映射成端信息;经过客户端回填处理后的html内容,已经具备了展现首屏的所有条件
先看下优化前后效果:
优化前-图1
优化后-图2
正常来说,直出后的页面展现速度已经很快了;但在实际开发中,你可能会遇到即使自己的数据加载速度再快,仍然会出现Activity切换过程中无法渲染H5页面的问题(可以通过开发者模式放慢动画时间来验证),产生视觉上的白屏现象(如上面图1) 我们通过研究源码发现,系统处理view绘制的时候,有一个属性setDrawDuringWindowsAnimating,从命名可以看出来,这个属性是用来控制window做动画的过程中是否可以正常绘制,而恰好在Android 4.2到Android N之间,系统为了组件切换的流程性考虑,该字段为false,我们可以利用反射的方式去手动修改这个属性,改进后的效果见上面图2
/**
* 让 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();
}
}
经过直出的改造之后,为了更快的渲染首屏,减少过程中涉及到的网络请求耗时,我们可以按照一定的策略和时机,提前从CDN中请求部分落地页html,缓存到本地,这样当用户点击查看新闻时,只需从缓存中加载即可
手百预取服务架构图
目前手百预取服务支撑着图文、图集、视频、广告等多个业务方,根据业务场景的不同,触发时机可以自定义,也可以遵循我们默认的刷新、滑停、点击等时机,此外,我们会对预取内容进行优先级排序(根据资源类型、触发时机),会动态的根据当前手机状态信息进行并发控制和流量控制,在一些降级场景中,server还可以通过云控的方式来控制是否预取以及预取的数量
在落地页中,除了文本外,图片也是重要的组成部分。直出解决了文字展现的速度问题,但图片的加载渲染速度仍不理想,尤其是首屏中带有图片的文章,其首图的渲染速度才是真正的首屏时间点 传统Hybrid方案,前端页面通过端能力调用NA图片下载能力来缓存和渲染图片,虽然实现了客户端和前端图片缓存的共享,但由于JS执行时机较晚,且多次端能力调用存在效率问题,导致图片渲染延后
初步改进方案:为了提升图片加载速度,减少JS调用耗时,改为纯H5请求图片,速度虽然有所提升,但是客户端和前端缓存无法共享,当点击图片调起NA图片查看器时,无法做到沉浸式效果,且仍需重复下载一次图片,造成流量浪费 终极方案:借由内核的shouldInterceptRequest回调,拦截落地页图片请求,由客户端调用NA图片下载框架进行下载,并以管道方式填充到内核的WebResourceResponse中
此方案在满足图片渲染速度的同时,解耦了客户端和前端代码,客户端充当server角色,对图片进行请求和缓存控制,保证前端和客户端可以共用图片缓存,改造后的方案,非首图展现流程,页面不卡顿,首屏80分位值缩短80ms~150ms
效果如下:
优化前Hybrid方案
优化后通用拦截方案
为了减少WebView的性能损耗,我们可以在合适时机提前创建好WebView,并存入缓存池,当页面需要显示内容时,直接从缓存池获取创建好的WebView,根据性能数据显示,WebView预创建可以减少首屏渲染时间200ms+
具体以Feed落地页为例,当用户进入手百并触发Feed吸顶操作后,我们会创建第一个WebView,当用户进入落地页后,会从缓存池中取出来渲染H5页面,为了不影响页面的加载速度,同时保证下次进入落地页缓存池中仍然有可用的WebView组件,我们会在每次页面加载完成(pageFinish)或者back退出落地页的时机,去触发预创建WebView的逻辑
由于WebView的初始化需要和context进行绑定,若想实现预创建的逻辑,需要保证context的一致性,常规做法我们考虑可以用fragment来实现承载H5页面的容器,这样context可以用外层的activity实例,但Fragment本身的切换流畅度存在一定问题,并且这样做限定了WebView预创建适用的场景。为此,我们找到了一种更加完美的替代方案,即MutableContextWrapper
Special version of ContextWrapper that allows the base context to be modified after it is initially set. Change the base context for this ContextWrapper. All calls will then be delegated to the base context. Unlike ContextWrapper, the base context can be changed even after one is already set. 简单来说,就是一种新的context包装类,允许外部修改它的baseContext,并且所有ContextWrapper调用的方法都会代理到baseContext来执行
下面是截取的一段预创建WebView的代码
/**
* 创建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;
}
a. WebView初始化完成,立刻loadUrl,无需等待框架onCreate或者OnResume结束 b. WebView初始完成后到页面首屏绘制完成之间,尽量减少UI线程的其他操作,繁忙的UI线程会拖慢WebView.loadUrl的速度
具体到Feed落地页场景,由于我们的落地页包含两部分,WebView+NA评论组件,正常流程会在WebView初始化结束后,开始评论组件的初始化及评论数据的获取。由于此时评论的初始化仍处在onCreate的UI消息处理中,会严重延迟内核加载主文档的逻辑。考虑到用户进入落地页的时候,评论组件对用户来说并不可见,所以将评论组件的初始化延迟到页面的pageFinish时机或者firstScreenPaintFinished;80分位性能提升60ms~100ms
a. 内核渲染优化: 内核中主要分为三个线程(IOThread、MainThread、ParserThread),首先IOThread会从网络端或者本地获取html数据,并把数据交给MainThread(渲染线程,十分繁忙,用于JS执行,页面布局等),为了保证MainThread不被阻塞,需要额外起一个后台线程(ParserThread)用来做html的解析工作。ParserThread每解析到落地页html中带有特殊class标记的一个div标签或者P标签(图中的first、second)时,就会触发一次MainThread的layout工作,并把layout后得到的高度与屏幕高度进行对比,如果当前layout高度已经大于屏幕高度,我们认为首屏内容已经完成布局,可以触发渲染上屏逻辑,不必等到整篇html全部解析完成再上屏,提前了首屏的渲染时间;80分位下,内核的渲染优化可以提升首屏速度100ms~200ms
b. 预加载JS: 预创建好WebView后,通过预加载JS(与内核约定好的JS内容,内核侧执行该JS时,只做初始化操作),触发WebView初始化逻辑,缩短后续加载url耗时;80分位性能提升80ms左右
频繁预取会带来流量的浪费:预取的命中率虽然达到了90%以上,但有效率仅有15%
图文:优化直出html中内联的css、icon等数据,数据大小减少约40%
1) 图文:通过对图文资源进行评分,来决定4G是否需要预取,多组AB试验最优效果劣化9.5ms 2)视频:为了平衡性能和流量,在性能劣化可接受的范围内(视频起播时间劣化100ms),针对视频部分采用流量高峰期不预取的策略,减少视频总流量约7%,整体带宽峰值下降3%
通用用户操作行为,对Feed预取进行AI预测,减少无效预取的数量。
在总结之前,先来看下整体优化的前后效果对比:
优化前
优化后
可以看到,经过一系列的优化手段,落地页已经实现了秒开效果。回顾所做的事情,从分析用户反馈到定位性能瓶颈,再到各种优化尝试,发现所有类似的性能优化手段都可以从以下几点入手:
本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/AqQgDB-0dUp2ScLkqxbLZg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。