您好,欢迎来到百家汽车网。
搜索
您的当前位置:首页今日头条品质优化今日头条品质优化怎么做

今日头条品质优化今日头条品质优化怎么做

来源:百家汽车网


今日头条品质优化(今日头条品质优化怎么做)
背景
作为一个内容类应用,看新闻读资讯一直是头条用户的核心需求,页面的打开速度直接关系到用户使用头条的核心体验,在头条中,为了更多的承载足够丰富的样式和逻辑下保持多端体验的统一,详情页的内容我们是通过WebView来承载的,但WebView本身的性能相比Native来说比较差,因此,今日头条技术团队一直致力于优化详情页的加载速度。
经过不断的优化,目前今日头条中详情页在线上的打开体验,从肉眼上基本已经感知不到加载过程。在接下来这篇文章里,我们会逐步拆解和介绍我们对详情页加载优化的思路和实践。
先让我们来看看优化前后的效果吧~
今日头条详情页加载体验优化前
今日头条详情页加载体验优化后
数据建立
性能
当我们开始着手优化页面加载速度之前,我们需要明确一个问题,怎样才是用户真正体验到的页面加载时间。
首先我们可以看下面这个公式:
页面加载时间=页面加载完成时间-页面开始加载时间
页面开始加载时间很好确定,当用户点击了Feed上的卡片,我们就可以认为页面开始加载了。
问题是怎么定义页面加载完成了呢?从客户端的角度上看,无论是iOS还是Android,WebView都提供了一个loadFinsih的回调,但在实际应用中我们发现,loadFinish回调并不能反应用户的真实体验。
一般来说,WebView渲染需要经过下面几个步骤
解析HTML文件
加载JavaScript和CSS文件
解析并执行JavaScript
构建DOM结构
加载图片等资源
页面加载完毕
而loadFinish实际上是在页面加载完毕阶段,而DOM构建完成时页面结构就已经基本渲染完成,所以从用户真实体验的角度出发,我们以DOM结构构建完成(即domready)的时间点作为页面加载完成时间点。
白屏
在详情页浏览过程中,除了页面加载速度之外,还有一个特别影响用户体验的问题,就是页面的白屏,也是早期的时候用户反馈比较多的问题,但有很多场景都可能导致详情页发生白屏,比如说网络异常,WebView异常等等,需要从用户体验的角度出发去检测用户发生白屏的情况。
目前可以想到最直观的方案就是对WebView进行截图,遍历截图的像素点的颜色值,如果非白屏颜色的颜色点超过一定的阈值,就可以认为不是白屏,目前需要考虑的是这个方案的性能问题和检测时机。
iOS中提供了WebView快照的接口获取当前WebView渲染的内容,底层采用异步回调的实现方式,API耗时10ms左右,用户基本无感知。
-(void)takeSnapshotWithConfiguration:(nullableWKSnapshotConfiguration*)snapshotConfigurationcompletionHandler:(void(^)(UIImage*_Nullablesnapshotimage,NSError*_Nullableerror))completionHandlerAPI_AVAILABLE(ios(11.0));
Android中系统提供的获取视图内容的接口为getDrawingCache,API耗时在40ms左右,性能损耗也不是特别大。
除了截图的性能损耗,像素点检测也是白屏检测中比较耗时的场景,经过实验,我们把WebView截图的图片进行缩小到原图的1/6,遍历检测图片的像素点,当非白色的像素点大于5%的时候我们就认为是非白屏的情况,可以相对高效检测准确得出详情页是否发生了白屏。
指标建立
确定好口径之后,我们还有需要明确的一个问题是,什么指标可以反映用户刷头条时的真实体验。
最早的时候,我们用的是详情页页面的页面平均加载时长,也就是页面加载时长的总和/页面pv,在开始的时候这个指标也的确可以明确我们的加载速度。
后来随着详情页的加载优化逐渐的深入,会发现平均加载时长虽然也可以反映详情页加载速度,但是因为详情页的pv比较高,如果使用平均加载速度化很多用户体验问题就被平均掉了,并不能反映用户的真实情况,后面我们又调整了口径,将指标调整为所有用户进入详情页的80分位值,比如说,假如头条详情页加载速度80分位值是1秒,那么就说明80%的情况下用户进入详情页都能在1s内加载完成,当然经过我们的不断优化,详情页加载的80分位值已经能够达到0.3s以内,也就是说,80%的情况下用户都能够在0.3s内完成页面加载。
80分位优化数据对比
再后来我们又发现,在头条详情页的量级下面,即使是80分位的数据也不能反应许多长尾用户的真实情况,也为了更极致的追求详情页的加载性能,我们最后将详情页的性能口径调整到95分位。到目前在我们的努力下,今日头条详情页的加载速度95分位也优化了将近80%。
我们究竟做了什么呢,接下来会慢慢介绍一下。
模板优化
模板拆分
如前所述,图文详情页是通过WebView来承载的,而WebView承载页面最简单的做法就是直接通过URL去加载一个线上页面。那么先来一道简单的面试题,当用户从浏览器输入一个URL到页面展现发生了什么呢?
之前已经介绍过页面的渲染流程了,现在我们再简单看看用户从点击到看到页面内容需要经历如下几个阶段:
WebView加载流程
可以看到,通过线上页面加载用户每次进入详情页都要通过多次网络加载,极容易受网络波动的影响,这种情况下,也无法保证页面加载的时长和成功率,极大的影响了用户体验。
于是在头条中,我们将新闻中标题和正文内容进行拆分,把头条详情页的公共样式CSS和逻辑JS都抽离出来,形成一个而完备的详情页模板,这样我们就可以把模板直接内置在客户端中。
同时我们会与前端约定好的JS脚本,通过接口将正文内容数据注入页面完成详情页的页面展示,通过该这种方式我们可以将接口放到客户端上进行请求。
这样用户进入详情页的时候只需要本地加载模板,而且加载模板的时候也可以同时并行请求详情页数据,再将数据注入进模板中。
那么用户点击到看到页面内容只需要经历下面的阶段:
模板拆分
如上图所示,我们只需要通过一次网络加载就可以完成页面渲染。
还能不能更快一点呢?当然能!
为了提高页面的加载速度,客户端通过一定的策略去预加载新闻数据,这样在理想状态下用户进入页面时看到页面时就可以直接使用缓存的数据,用户在看新闻的时候可以实现完全离线化,避免受到网络的影响。
本地加载
模板预热
完全脱离了网络加载之后,还能再快一点呢?当然还是可以的!
当全流程离线化之后,页面加载的瓶颈就变成了本地模板的加载时间,所以我们接下来要做的就是优化模板加载时间。
对于模板来说,我们做了两件事情
模板合并,正常来说,WebView需要在加载玩主HTML之后再去加载HTML中的JS和CSS,需要多次IO操作,于是我们将JS和CSS还有一些图片都内联到一个文件中,这样,加载模板时就只需要一次IO操作,也大大减少因为IO加载冲突导致模板加载失败问题
模板简化,我们将部分非必须的脚本异步化拉取,精简不必要的样式和JS代码,将模板大小压缩了20%以上
通过上面优化,我们就已经将模板加载时间大大优化了,但是还能不能更给力呢?还是可以的。
对于客户端来说,当模板跟数据分离之后,由于每次用户点击的时候加载的都是同一个模板,所以实际上,我们并不需要在用户进入页面的时候才去创建WebView以及加载模板,我们只需要在合适的时机在后台创建WebView,并且提前预热加载模板,当用户点击进入页面的时候就能使用已经加载好模板的WebView,直接将详情页的内容数据通过JS注入到页面中,前端收到数据后进行页面渲染即可。
此时用户进入详情页实际就不再需要重新加载模板了,路径就变成了:
模板预热
可以看下,通过本地测试的模板预热和数据预取的优化效果,还是比较明显的,基本上已经达到了上面的截图中的验证效果。
本地测试数据
模板复用
当我们拆分完模板和数据之后,数据上优化已经比较明显,但我们说过,除了验证数据,我们还需要看线上用户的真实体验数据,从95分位上看实际数据优化却不是很明显,所以我们从数据上观察,用户预热模板的命中率只有53%,还有进一步的提升空间。
模板预热率
为了尽可能的提高页面的加载速度,我们希望用户每次进入详情页的时候都能够使用预热好模板的WebView,一般情况下,我们都会使用模板预创建池的手段来优化用户进入详情页时的预热模板命中率。
但其实在很多情况下,WebView的创建是一个性能开销比较大的操作,如果我们使用预创建池的方案,那么就会在后台频繁创建WebView,这样对用户在Feed场景的浏览体验也会有一定的影响。
而且假如用户频繁且快速进出详情页时,实际场景中用户也很容易遇到无法命中预热模板的场景。
这个时候为了优化用户的体验,如前文所述,我们每次使用的时候都是同一个模板,所以我们使用完当前WebView之后,只需要在用户退出页面的时候把正文数据清空,这样进入下一个页面的时候就能够继续复用这个WebView重新注入数据即可。
通过这个手段,我们既避免了频繁在后台预创建WebView对用户刷Feed体验的影响,把用户进入页面时候的预热模板命中率从53%提升到92%,优化了用户体验。
预热模板命中率
网络优化
说完我们在模板WebView方面的优化之后,再介绍一下我们在内容请求上的优化。
CDN加速
由于头条详情页请求有以下特点
流量大,之前说过,看新闻作为用户在头条的核心场景,每天都有上亿用户在使用头条,详情页的数据流量十分大。
数据属性基本不变,在详情页的请求中,很多热点文章是重复渲染计算的,正文、标题、作者信息、图片控制以及一些样式和业务逻辑渲染是基本不变的,这部分重复计算耗费了带宽、服务器资源,是比较没有必要的。
用户分布广,网络状况难以保证,头条的用户量很大,覆盖了各种运营商网络和网络状态,网络质量无法得到很好的保证。而CDN能够将数据缓存在各地的边缘节点,用户就近接入了边缘节点,避免在网络质量无法保证的公网上长时间传输,从而提高了响应速度和响应的成功率。
接口数据大,由于正文数据的存在,接口返回的数据常常会很大,如果每一次都实时返回,对网络的压力会比较大,可能会把带宽打满而影响其他服务
所以我们将详情页内容数据分为静态和动态两部分,将正文内容、标题、作者栏等用户主要消费的又基本不变的内容托管到了CDN上。
CDN的全称是ContentDeliveryNetwork,即内容分发网络。其目的是通过在现有的Internet中增加一层新的网络架构,将网站的内容发布到最接近用户的网络“边缘”,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。CDN有别于镜像,因为它比镜像更智能,或者可以做这样一个比喻:CDN=更智能的镜像+缓存+流量导流。因而,CDN可以明显提高Internet网络中信息流动的效率。从技术上全面解决由于网络带宽小、用户访问量大、网点分布不均等问题,提高用户访问网站的响应速度。
托管到CDN之后,全国各地的用户可以直接从最佳节点就获取到详情页数据,也大大节省了带宽成本。
容灾
1.多域名备份
为了防止某个CDN出现故障,导致服务雪崩,服务端会下发多个CDN链接,当用户访问当前CDN节点的出异常时,可以快速自动切换到下个CDN节点。
2.快速超时
一般的超时策略,客户端在请求时,会遍历请求CDN1、2、3。如果这些CDN都请求失败,则整个网络请求算作失败。
但这个方案的问题是,假设请求CDN的超时时间是15s。如果CDN1出现故障,则需要等待15s才能切换到CDN2上,这对于详情页的加载时间来说是不可接受,如果用户网络突然变差,则需要等待45s才能返回失败展示错误页。
基于此我们设计了详情页请求的快速动态超时策略
单次请求CDN的超时时间,根据上次成功请求CDN的值计算,因子1.5(z值)。且最小为1s(x值),最大为4s(y值)。超过这一时间不取消,直接请求下个CDN。
单次请求CDN有一个硬性超时时间4s(w值,w需>=y),超过这一时间请求取消。n个CDN的请求全部取消后反馈用户失败。
几个case:
第1个CDN突然挂掉(假设上次成功请求的耗时为a)下一次请求:第一个CDN很快超时(a_1.5);开始请求第二个CDN(超时时间为a_1.5,但实际上b秒就会返回请求)。用户本次等待时间为a_1.5+b下两次请求:第一个CDN很快超时(b_1.5);开始请求第二个CDN(超时时间为b_1.5,但实际c秒就会返回请求)。用户本次等待时间为b_1.5+c
用户突然进入了一个网络很差的环境(假设上次成功请求的耗时为a)下一次请求:第一个CDN很快超时(a_1.5);开始请求第二个CDN(a_1.5)也超时;开始请求第三个CDN(a_1.5)。最后一个请求会在a_3+w后返回失败(这个值会在12s以内)。
可以看到,通过多域名备份和快速超时的策略,即使用户在网络或者服务异常的情况下,也能快速恢复或者让用户能感知到自身网络问题。
渲染优化
当我们在模板层和网络层优化到极致的时候,我们的就是WebView的渲染速度了!
服务端预渲染
正常来讲,正常的内容数据可能是类似JSON等数据,客户端获取到数据之后,将数据注入给前端,前端还需要将JSON数据跟模板进行组装,拼上HTML标签等模板了之后再呈现到WebView渲染,导致前端渲染上耗时也比较久。
为了提高用户的首屏效率,我们在服务端就会把所有的详情页正文的HTML数据组装好,通过将服务端直出内容注入到页面中时,可以直接给WebView进行渲染,对于其他动态下发的内容(比如相关搜索),前端再进行二次异步处理,提升用户效率。
客户端渲染
一般来说,我们正文中所有内容都是通过WebView渲染,经过上述的优化之后,文章的文字部分渲染效率已经很高了,但是实际场景中,很多文章会包含比较多的图片和视频场景。
在实际场景中,WebView渲染非文字内容会存在以下问题:
相比于文字内容,非文字内容比如说图片和视频类资源的渲染对于WebView来说渲染效率比较差
在详情页中文章有大量图片的场景,对于WebView的渲染内存占用和滑动体验也有问题
最后,如果用户多次打开同一篇文章,这篇文章中的图片也会存在多次加载的问题,无法与客户端进行缓存共享,对用户的流量也是一种浪费。
所以在详情页中,我们会将图片和视频等非文字内容通过原生组件的方式放在客户端进行渲染,既可以提高渲染效率,也可以减少不必要的流量消耗。
原生化渲染还有一个好处,图片越来越成为文章体验的重要部分,对于多图文章,我们在Feed页面也可以智能加载详情页需要的图片,增加用户的文章首屏体验。
白屏优化
讲完了性能优化,最后再分享一下我们对详情页白屏率的一些优化,其实很多用户反馈白屏问题大部分都可能是由于网络等问题导致页面加载时间过长,导致用户从体验上观感是白屏了,这部分通过上面分享的性能优化手段已经能够解决,所以下面只是简单介绍下一些非网络原因的白屏问题。
我们通过白屏检测和上报之后的数据分析之后发现,非网络原因导致的详情页的白屏问题大体是WebView加载的问题。
在iOS中,我们使用的是系统提供的WKWebView,WKWebView是运行在一个进程中的组件,所以当WKWebView上占用内存过大时,WKWebView所在的WebContentProcess会被系统kill掉,反映在用户体验上就是发生了白屏。
根据网上的做法,我们可以在WKWebView提供的回调webViewWebContentProcessDidTerminate函数中通过reload方法重新加载当前页面恢复,但是这种情况只适用于通过loadRequest加载的请求,在详情页中,由于使用了模板化的WebView中,重新reload只能重新reload模板,并不能正常恢复整个详情页,需要客户端重新加载模板之后再重新注入数据。
另外由于我们有预热模板的逻辑,所以可能在进入详情页的时候使用的WKWebView就已经崩溃,在调用JS注入数据时会直接返回失败,失败时,我们会尝试重新加载模板。但后来实际操作中发现一个问题,如果直接调用数据注入的方法,等待系统WebView返回失败的回调耗时比较久,所以后续也调整了数据注入的接口,我们提前在注入的脚本中判断是否存在数据注入的接口,如果不存在,就说明模板存在问题,直接重试即可。
而在Android中,我们采用的是自研内核WebView,也会遇到一些奇奇怪怪的坑。
多线程读模板文件问题,WebView在运行中会读取的文件模板,如果此时另外一个线程同时更新模板文件时,就出现了模板加载问题,所以需要保证模板加载的原子性
Render卡死问题,内核是一个比较复杂的逻辑,内部渲染极少数情况也会出现Render卡死问题,但是在详情页整体用户的量级下,即使只有十万分之一的可能,对用户来说也是一个比较大的问题,此时我们会从业务上做白屏监控进行重试
当然不管是iOS和Android,WebView加载的逻辑都比较复杂,有时候怎么重试也无法成功,这个时候我们会直接降级到加载线上的详情页,优先保证用户的体验。
总结
限于篇幅原因,我们还做了很多其他事情,包括请求精简,push文章预拉取,数据注入的方式优化等等,也做了很多其他的方向的探索,这里不做展开,希望有机会能再分享给大家。
最后总结一下我们在优化详情页打开速度之后的一些想法
数据很重要,我们在优化加载速度之前做的第一件事情其实是建立了一个详情页的数据看板,只有通过数据我们才能真正了解目前线上用户的现状,从真实用户的体验中找到瓶颈和优化点。
用户体验优先,优化方案有很多,除了加载速度之外,还需要从整体应用体验出发,选择对用户最佳的方案
追求极致,其实最开始的优化是比较简单的,但是越到后面越难,需要一点点抠细节,才能达到极致的用户体验
更多分享
字节跳动自研线上引流回放系统的架构演进
iOS大解密:玄之又玄的KVO
今日头条Android'秒'级编译速度优化
今日头条技术团队
今日头条技术团队不仅致力于在业务上不断深耕挖掘,在技术上也一直在追求极致的用户体验。
如果你也向往在一个亿级DAU业务里成长,也期待在技术上有突飞猛进的提升,欢迎你加入我们。
无论你是iOS/Android/前端/后端,我们在深圳/北京/广州等你来,一起做更有挑战的事!简历投递邮箱:tech@bytedance.com;邮件标题:姓名-工作年限-头条技术团队。
欢迎关注字节跳动技术团队

Copyright © 2019- baijiahaobaidu.com 版权所有 湘ICP备2023023988号-9

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务