淄博周村网站建设报价,如何让新网站,企业应用软件开发,中学建设校园网站方案背景作为一个内容类应用#xff0c;看新闻读资讯一直是头条用户的核心需求#xff0c;页面的打开速度直接关系到用户使用头条的核心体验#xff0c;在头条中#xff0c;为了更多的承载足够丰富的样式和逻辑下保持多端体验的统一#xff0c;详情页的内容我们是通过 WebView… 背景作为一个内容类应用看新闻读资讯一直是头条用户的核心需求页面的打开速度直接关系到用户使用头条的核心体验在头条中为了更多的承载足够丰富的样式和逻辑下保持多端体验的统一详情页的内容我们是通过 WebView 来承载的但 WebView 本身的性能相比 Native 来说比较差因此今日头条技术团队一直致力于优化详情页的加载速度。经过不断的优化目前今日头条中详情页在线上的打开体验从肉眼上基本已经感知不到加载过程。在接下来这篇文章里我们会逐步拆解和介绍我们对详情页加载优化的思路和实践。先让我们来看看优化前后的效果吧~今日头条详情页加载体验优化前今日头条详情页加载体验优化后数据建立性能当我们开始着手优化页面加载速度之前我们需要明确一个问题怎样才是用户真正体验到的页面加载时间。首先我们可以看下面这个公式页面加载时间 页面加载完成时间 - 页面开始加载时间页面开始加载时间很好确定当用户点击了 Feed 上的卡片我们就可以认为页面开始加载了。问题是怎么定义页面加载完成了呢从客户端的角度上看无论是 iOS 还是 AndroidWebView 都提供了一个 loadFinsih 的回调但在实际应用中我们发现loadFinish 回调并不能反应用户的真实体验。一般来说WebView 渲染需要经过下面几个步骤解析 HTML 文件加载 JavaScript 和 CSS 文件解析并执行 JavaScript构建 DOM 结构加载图片等资源页面加载完毕而 loadFinish 实际上是在页面加载完毕阶段而 DOM 构建完成时页面结构就已经基本渲染完成所以从用户真实体验的角度出发我们以 DOM 结构构建完成(即 domReady)的时间点作为页面加载完成时间点。白屏在详情页浏览过程中除了页面加载速度之外还有一个特别影响用户体验的问题就是页面的白屏也是早期的时候用户反馈比较多的问题但有很多场景都可能导致详情页发生白屏比如说网络异常WebView 异常等等需要从用户体验的角度出发去检测用户发生白屏的情况。目前可以想到最直观的方案就是对 WebView 进行截图遍历截图的像素点的颜色值如果非白屏颜色的颜色点超过一定的阈值就可以认为不是白屏目前需要考虑的是这个方案的性能问题和检测时机。iOS 中提供了 WebView 快照的接口获取当前 WebView 渲染的内容底层采用异步回调的实现方式API 耗时 10ms 左右用户基本无感知。- (void)takeSnapshotWithConfiguration:(nullable WKSnapshotConfiguration *)snapshotConfiguration completionHandler:(void (^)(UIImage * _Nullable snapshotImage, NSError * _Nullable error))completionHandler API_AVAILABLE(ios(11.0));Android 中系统提供的获取视图内容的接口为 getDrawingCacheAPI 耗时在 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 的全称是 Content Delivery Network即内容分发网络。其目的是通过在现有的Internet中增加一层新的网络架构将网站的内容发布到最接近用户的网络“边缘”使用户可以就近取得所需的内容提高用户访问网站的响应速度。CDN 有别于镜像因为它比镜像更智能或者可以做这样一个比喻CDN 更智能的镜像 缓存 流量导流。因而CDN 可以明显提高Internet网络中信息流动的效率。从技术上全面解决由于网络带宽小、用户访问量大、网点分布不均等问题提高用户访问网站的响应速度。托管到 CDN 之后全国各地的用户可以直接从最佳节点就获取到详情页数据也大大节省了带宽成本。容灾1. 多域名备份为了防止某个 CDN 出现故障导致服务雪崩服务端会下发多个 CDN 链接当用户访问当前 CDN 节点的出异常时可以快速自动切换到下个 CDN 节点。2. 快速超时一般的超时策略客户端在请求时会遍历请求 CDN 1、2、3。如果这些 CDN 都请求失败则整个网络请求算作失败。但这个方案的问题是假设请求 CDN 的超时时间是 15s。如果 CDN 1 出现故障则需要等待 15s 才能切换到 CDN 2上这对于详情页的加载时间来说是不可接受如果用户网络突然变差则需要等待 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 中我们使用的是系统提供的 WKWebViewWKWebView 是运行在一个独立进程中的组件所以当 WKWebView 上占用内存过大时WKWebView 所在的 WebContent Process 会被系统 kill 掉反映在用户体验上就是发生了白屏。根据网上的做法我们可以在 WKWebView 提供的回调 webViewWebContentProcessDidTerminate 函数中通过 reload 方法重新加载当前页面恢复但是这种情况只适用于通过 loadRequest 加载的请求在详情页中由于使用了模板化的 WebView 中重新 reload 只能重新 reload 模板并不能正常恢复整个详情页需要客户端重新加载模板之后再重新注入数据。另外由于我们有预热模板的逻辑所以可能在进入详情页的时候使用的 WKWebView 就已经崩溃在调用 JS 注入数据时会直接返回失败失败时我们会尝试重新加载模板。但后来实际操作中发现一个问题如果直接调用数据注入的方法等待系统 WebView 返回失败的回调耗时比较久所以后续也调整了数据注入的接口我们提前在注入的脚本中判断是否存在数据注入的接口如果不存在就说明模板存在问题直接重试即可。而在 Android 中我们采用的是自研内核 WebView也会遇到一些奇奇怪怪的坑。多线程读模板文件问题WebView 在运行中会读取的文件模板如果此时另外一个线程同时更新模板文件时就出现了模板加载问题所以需要保证模板加载的原子性Render 卡死问题内核是一个比较复杂的逻辑内部渲染极少数情况也会出现 Render 卡死问题但是在详情页整体用户的量级下即使只有十万分之一的可能对用户来说也是一个比较大的问题此时我们会从业务上做白屏监控进行重试当然不管是 iOS 和 Android WebView 加载的逻辑都比较复杂有时候怎么重试也无法成功这个时候我们会直接降级到加载线上的详情页优先保证用户的体验。总结限于篇幅原因我们还做了很多其他事情包括请求精简push 文章预拉取数据注入的方式优化等等也做了很多其他的方向的探索这里不做展开希望有机会能再分享给大家。最后总结一下我们在优化详情页打开速度之后的一些想法数据很重要我们在优化加载速度之前做的第一件事情其实是建立了一个详情页的数据看板只有通过数据我们才能真正了解目前线上用户的现状从真实用户的体验中找到瓶颈和优化点。用户体验优先优化方案有很多除了加载速度之外还需要从整体应用体验出发选择对用户最佳的方案追求极致其实最开始的优化是比较简单的但是越到后面越难需要一点点抠细节才能达到极致的用户体验今日头条技术团队今日头条技术团队不仅致力于在业务上不断深耕挖掘在技术上也一直在追求极致的用户体验。如果你也向往在一个亿级DAU业务里成长也期待在技术上有突飞猛进的提升欢迎你加入我们。无论你是 iOS/Android/前端/后端我们在深圳/北京/广州等你来一起做更有挑战的事简历投递邮箱techbytedance.com 邮件标题 姓名-工作年限-头条技术团队 。