衡量 Service Worker 对实际性能的影响

服务工作线程(至少从性能角度来看)的一大优势是能够主动控制资源的缓存。能够缓存所有必要资源的 Web 应用应该会为回访者大幅加快加载速度。但这些改进对真实用户而言实际效果如何?您又如何衡量这一点?

Google I/O Web 应用(简称 IOWA)是一款渐进式 Web 应用,它利用了服务工件提供的大部分新功能,为用户提供类似应用的丰富体验。该公司还使用 Google Analytics 从庞大而多元化的用户群体中捕获关键的效果数据和使用情况模式。

本案例研究探讨了 IOWA 如何使用 Google Analytics 来解答关键的性能问题,并报告服务工件的实际影响。

从问题开始

每当您在网站或应用中实现分析时,请务必先确定您希望通过收集的数据回答哪些问题。

虽然我们有几个问题想要解答,但在本案例中,我们将重点关注其中两个更有趣的问题。

1. 与所有浏览器中提供的现有 HTTP 缓存机制相比,服务工件缓存的性能是否更高?

我们已经预料到,重复访问者访问网页的速度会比新访问者更快,因为浏览器可以缓存请求,并在重复访问时立即提供这些请求。

服务工作器提供替代缓存功能,可让开发者精细控制缓存的内容和方式。在 IOWA 中,我们优化了服务工作线程实现,以便缓存每个资源,这样回访者就可以完全离线使用应用。

但这项工作能否比浏览器默认执行的操作更好?如果是,效果会好多少?1

2. 服务工件对网站加载体验有何影响?

换句话说,无论传统网页加载指标衡量的实际加载时间如何,网站的加载感觉速度有多快?

显然,回答有关体验感受的问题并非易事,而且没有任何指标能够完美地代表这种主观情感。尽管如此,有些指标肯定比其他指标更有用,因此选择合适的指标非常重要。

选择合适的指标

默认情况下,Google Analytics 会跟踪网站 1% 的访问者的网页加载时间(通过 Navigation Timing API),并通过“平均网页加载时间”等指标提供这些数据。

平均网页加载时间非常适合回答第一个问题,但不太适合回答第二个问题。一方面,load 事件不一定对应于用户实际能够与应用互动的时间。此外,加载时间完全相同的两个应用在感觉上可能完全不同。例如,与仅显示空白页几秒钟的网站相比,显示启动画面或加载指示器的网站可能给人以加载速度更快的感觉。

在 IOWA 中,我们展示了启动画面倒计时动画,在我看来,该动画在应用的其余部分在后台加载时非常成功地为用户提供了娱乐体验。因此,跟踪启动画面显示所需时间更有助于衡量用户感知的加载性能。我们选择了首次绘制所用时间指标来获取此值。

确定要回答的问题并找出有助于回答这些问题的指标后,接下来便是实现 Google Analytics 并开始衡量。

Google Analytics 实现

如果您之前使用过 Google Analytics,那么可能熟悉推荐的 JavaScript 跟踪代码段。如下所示:

<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>

以上代码中的第 1 行会初始化全局 ga() 函数(如果尚不存在),最后一行会异步下载 analytics.js 库。

中间部分包含以下两行:

ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');

这两个命令可跟踪访问您网站的用户访问了哪些网页,但无法提供更多信息。如果您想跟踪其他用户互动,则必须自行跟踪。

对于 IOWA,我们还希望跟踪以下两项指标:

  • 从网页首次开始加载到像素显示在屏幕上所经过的时间。
  • 网页是否由 Service Worker 控制。有了这些信息,我们就可以细分报告,比较启用和未启用服务工件的结果。

捕获首次绘制的时间

某些浏览器会记录将第一个像素绘制到屏幕上的确切时间,并将该时间提供给开发者。与通过 Navigation Timing API 公开的 navigationStart 值相比,该值可让我们非常准确地计算出用户首次请求网页到首次看到内容之间的时间。

正如我之前提到的,首次绘制时间是一项重要的衡量指标,因为这是用户体验您网站加载速度的第一个时间点。这是用户获得的第一印象,良好的第一印象可以对用户体验的其余部分产生积极影响。2

为了在公开该值的浏览器中获取首次绘制值,我们创建了 getTimeToFirstPaintIfSupported 实用函数:

function getTimeToFirstPaintIfSupported() {
  // Ignores browsers that don't support the Performance Timing API.
  if (window.performance && window.performance.timing) {
    var navTiming = window.performance.timing;
    var navStart = navTiming.navigationStart;
    var fpTime;

    // If chrome, get first paint time from `chrome.loadTimes`.
    if (window.chrome && window.chrome.loadTimes) {
      fpTime = window.chrome.loadTimes().firstPaintTime * 1000;
    }
    // If IE/Edge, use the prefixed `msFirstPaint` property.
    // See http://msdn.microsoft.com/ff974719
    else if (navTiming.msFirstPaint) {
      fpTime = navTiming.msFirstPaint;
    }

    if (fpTime && navStart) {
      return fpTime - navStart;
    }
  }
}

有了这个函数,我们现在可以编写另一个函数,用于发送非互动事件,并将首次绘制时间作为其值:3

function sendTimeToFirstPaint() {
  var timeToFirstPaint = getTimeToFirstPaintIfSupported();

  if (timeToFirstPaint) {
    ga('send', 'event', {
      eventCategory: 'Performance',
      eventAction: 'firstpaint',
      // Rounds to the nearest millisecond since
      // event values in Google Analytics must be integers.
      eventValue: Math.round(timeToFirstPaint)
      // Sends this as a non-interaction event,
      // so it doesn't affect bounce rate.
      nonInteraction: true
    });
  }
}

编写这两个函数后,我们的跟踪代码如下所示:

// Creates the tracker object.
ga('create', 'UA-XXXXX-Y', 'auto');

// Sends a pageview for the initial pageload.
ga('send', 'pageview');

// Sends an event with the time to first paint data.
sendTimeToFirstPaint();

请注意,根据上述代码的运行时间,像素可能已绘制到屏幕上,也可能尚未绘制到屏幕上。为确保始终在第一次绘制发生后运行此代码,我们将对 sendTimeToFirstPaint() 的调用推迟到了 load 事件之后。事实上,我们决定推迟发送所有分析数据,直到网页加载完毕后再发送,以确保这些请求不会与其他资源的加载相冲突。

// Creates the tracker object.
ga('create', 'UA-XXXXX-Y', 'auto');

// Postpones sending any hits until after the page has fully loaded.
// This prevents analytics requests from delaying the loading of the page.
window.addEventListener('load', function() {
  // Sends a pageview for the initial pageload.
  ga('send', 'pageview');

  // Sends an event with the time to first paint data.
  sendTimeToFirstPaint();
});

上述代码会向 Google Analytics 报告 firstpaint 次,但这只是其中的一半。我们仍然需要跟踪服务工作线程状态;否则,我们将无法比较由服务工作线程控制的页面和未受控制的页面的首次绘制时间。

确定 Service Worker 状态

为了确定服务工件的当前状态,我们创建了一个实用函数,该函数会返回以下三个值之一:

  • 受控:服务工作线程正在控制该网页。对于 IOWA,这还意味着所有资源都已缓存,并且页面可在离线状态下正常运行。
  • 支持:浏览器支持 Service Worker,但 Service Worker 尚未控制网页。这是首次访问者应有的预期状态。
  • 不受支持:用户的浏览器不支持 Service Worker。
function getServiceWorkerStatus() {
  if ('serviceWorker' in navigator) {
    return navigator.serviceWorker.controller ? 'controlled' : 'supported';
  } else {
    return 'unsupported';
  }
}

此函数会为我们获取服务工件状态;下一步是将此状态与我们发送到 Google Analytics 的数据相关联。

使用自定义维度跟踪自定义数据

默认情况下,Google Analytics 提供了多种方法,可让您根据用户、会话或互动属性将总流量细分为多个群组。这些属性称为“维度”。Web 开发者关注的常见维度包括浏览器操作系统设备类别

服务工件的状态不是 Google Analytics 默认提供的维度;不过,Google Analytics 确实支持您创建自己的自定义维度,并按照自己的意愿对其进行定义。

对于 IOWA,我们创建了一个名为 Service Worker Status 的自定义维度,并将其范围设为“命中”(即每次互动)。4您在 Google Analytics 中创建的每个自定义维度都会在相应媒体资源中获得一个唯一的编号,您可以在跟踪代码中通过该编号引用该维度。例如,如果我们刚刚创建的维度的编号为 1,则可以按如下方式更新逻辑,以发送 firstpaint 事件来包含服务工件状态:

ga('send', 'event', {
  eventCategory: 'Performance',
  eventAction: 'firstpaint',
  // Rounds to the nearest millisecond since
  // event values in Google Analytics must be integers.
  eventValue: Math.round(timeToFirstPaint)
  // Sends this as a non-interaction event,
  // so it doesn't affect bounce rate.
  nonInteraction: true,

  // Sets the current service worker status as the value of
  // `dimension1` for this event.
  dimension1: getServiceWorkerStatus()
});

这种方法可行,但只会将服务工件的状态与此特定事件相关联。由于服务工状态对于任何互动都可能很有用,因此最好将其包含在发送到 Google Analytics 的所有数据中。

为了在所有命中(例如所有网页浏览、事件等)中包含此信息,我们会先在跟踪器对象本身上设置自定义维度值,然后再向 Google Analytics 发送任何数据。

ga('set', 'dimension1', getServiceWorkerStatus());

此值一经设置,就会随当前网页加载的所有后续命中一起发送。如果用户稍后再次加载该网页,getServiceWorkerStatus() 函数可能会返回一个新值,并且该值将设置在跟踪器对象上。

关于代码清晰度和可读性,需要注意以下几点:由于查看此代码的其他人可能不知道 dimension1 的含义,因此最好创建一个变量,将有意义的维度名称映射到 analytics.js 将使用的值。

// Creates a map between custom dimension names and their index.
// This is particularly useful if you define lots of custom dimensions.
var customDimensions = {
  SERVICE_WORKER_STATUS: 'dimension1'
};

// Creates the tracker object.
ga('create', 'UA-XXXXX-Y', 'auto');

// Sets the service worker status on the tracker,
// so its value is included in all future hits.
ga('set', customDimensions.SERVICE_WORKER_STATUS, getServiceWorkerStatus());

// Postpones sending any hits until after the page has fully loaded.
// This prevents analytics requests from delaying the loading of the page.
window.addEventListener('load', function() {
  // Sends a pageview for the initial pageload.
  ga('send', 'pageview');

  // Sends an event with the time to first paint data.
  sendTimeToFirstPaint();
});

正如我之前所提到的,通过每一次命中发送服务工件状态维度,我们便可以在报告任何指标时使用该维度。

如您所见,IOWA 的所有网页浏览量中,有近 85% 来自支持服务工作器的浏览器。

结果:回答我们的疑问

开始收集数据来解答问题后,我们就可以生成数据报告来查看结果。(注意:此处显示的所有 Google Analytics 数据都代表 2016 年 5 月 16 日至 22 日 IOWA 网站的实际网站流量)。

我们首先提出的问题是:服务工作器缓存的性能是否优于所有浏览器中现有的 HTTP 缓存机制?

为了解答这个问题,我们创建了一个自定义报告,用于查看不同维度下的平均网页加载时间指标。此指标非常适合回答此问题,因为 load 事件仅在下载所有初始资源后触发。因此,它直接反映了网站所有关键资源的总加载时间。5

我们选择的维度包括:

  • 我们的自定义 Service Worker 状态维度。
  • 用户类型,用于指明用户是首次访问网站还是回访。(注意:新访问者不会缓存任何资源;回访者可能会缓存资源。)
  • 设备类别,以便我们比较移动设备和桌面设备的结果。

为了控制与 Service Worker 无关的因素导致加载时间结果出现偏差的可能性,我们将查询范围限制为仅包含支持 Service Worker 的浏览器。

如您所见,由 Service Worker 控制的应用访问速度比未受控制的访问速度快得多,即使是来自可能已缓存大多数网页资源的回访用户也是如此。还有一个有趣的发现是,平均而言,使用服务工作器的移动设备访问者比新桌面访问者加载速度更快。

“…由 Service Worker 控制的应用访问加载速度比未受控制的访问快得多…”

您可以参阅以下两个表格了解详情:

平均网页加载时间(桌面设备)
Service Worker 状态 用户类型 平均网页加载时间(毫秒) 样本大小
操控了 回访者 2568 30860
支持 回访者 3612 1289
支持 新访者 4664 21991
平均网页加载时间(移动设备)
Service Worker 状态 用户类型 平均网页加载时间(毫秒) 样本大小
操控了 回访者 3760 8162
支持 回访者 4843 676
支持 新访者 6158 5779

您可能想知道,浏览器支持 Service Worker 的回访者怎么会处于未受控状态。导致这种情况的原因有以下几种:

  • 用户在初次访问时离开了页面,而服务工作线程还没有机会完成初始化。
  • 用户通过开发者工具卸载了服务工件。

这两种情况都比较少见。我们可以通过查看第四列中的网页加载样本值来了解这一点。请注意,中间行中的样本数量比另外两行中的样本数量要少得多。

第二个问题是:服务工件对网站加载体验有何影响?

为了回答这个问题,我们为平均事件价值指标创建了另一个自定义报告,并过滤了结果,使其仅包含 firstpaint 事件。我们使用了设备类别维度和自定义的服务工件状态维度。

与我的预期相反,移动设备上的 Service Worker 对首次绘制时间的影响要小于对总体网页加载时间的影响。

“…移动设备上的服务工件对首次绘制时间的影响远小于对总体网页加载时间的影响。”

为了探究原因,我们必须深入研究数据。平均值适合大致了解情况,但要想真正了解这些数据在不同用户中的分布情况,我们需要查看 firstpaint 次的分配情况。

在 Google Analytics 中获取指标的分布

若要获取 firstpaint 次数的分布情况,我们需要访问每个事件的具体结果。遗憾的是,Google Analytics 无法轻松实现这一点。

Google Analytics 允许我们按所需的任何维度细分报告,但不允许按指标细分报告。这并不是说无法实现,而是说我们必须对实现进行更多自定义才能获得预期结果。

由于报告结果只能按维度细分,因此我们必须将指标值(在本例中为 firstpaint 时间)设置为事件的自定义维度。为此,我们创建了另一个名为“指标值”的维度,并更新了 firstpaint 跟踪逻辑,如下所示:

var customDimensions = {
  SERVICE_WORKER_STATUS: 'dimension1',
  <strong>METRIC_VALUE: 'dimension2'</strong>
};

// ...

function sendTimeToFirstPaint() {
  var timeToFirstPaint = getTimeToFirstPaintIfSupported();

  if (timeToFirstPaint) {
    var fields = {
      eventCategory: 'Performance',
      eventAction: 'firstpaint',
      // Rounds to the nearest millisecond since
      // event values in Google Analytics must be integers.
      eventValue: Math.round(timeToFirstPaint)
      // Sends this as a non-interaction event,
      // so it doesn't affect bounce rate.
      nonInteraction: true
    }

    <strong>// Sets the event value as a dimension to allow for breaking down the
    // results by individual metric values at reporting time.
    fields[customDimensions.METRIC_VALUE] = String(fields.eventValue);</strong>

    ga('send', 'event', fields);
  }
}

Google Analytics 网页界面目前不提供可直观呈现任意指标值分布的途径,但借助 Google Analytics Core Reporting APIGoogle Charts 库,我们可以查询原始结果,然后自行构建直方图。

例如,以下 API 请求配置用于使用非受控服务工件在桌面设备上获取 firstpaint 值的分布。

{
  dateRanges: [{startDate: '2016-05-16', endDate: '2016-05-22'}],
  metrics: [{expression: 'ga:totalEvents'}],
  dimensions: [{name: 'ga:dimension2'}],
  dimensionFilterClauses: [
    {
      operator: 'AND',
      filters: [
        {
          dimensionName: 'ga:eventAction',
          operator: 'EXACT',
          expressions: ['firstpaint']
        },
        {
          dimensionName: 'ga:dimension1',
          operator: 'EXACT',
          expressions: ['supported']
        },
        {
          dimensionName: 'ga:deviceCategory',
          operator: 'EXACT',
          expressions: ['desktop']
        }
      ],
    }
  ],
  orderBys: [
    {
      fieldName: 'ga:dimension2',
      orderType: 'DIMENSION_AS_INTEGER'
    }
  ]
}

此 API 请求会返回一个值数组,该数组如下所示(注意:这只是前五个结果)。结果会从小到大排序,因此这些行代表最快的时间。

API 响应结果(前五行)
ga:dimension2 ga:totalEvents
4 3
5 2
6 10
7 8
8 10

下面用简单的英语说明了这些结果的含义:

  • 有 3 个事件的 firstpaint 值为 4 毫秒
  • 有 2 个事件的 firstpaint 值为 5 毫秒
  • 有 10 个事件的 firstpaint 值为 6 毫秒
  • 有 8 个事件的 firstpaint 值为 7 毫秒
  • 有 10 个事件的 firstpaint value 为 8 毫秒
  • 等等

通过这些结果,我们可以推断出每个事件的 firstpaint 值,并创建该分布的直方图。我们对运行的每个查询都执行了此操作。

以下是使用非受控(但受支持)的 Service Worker 在桌面设备上分发内容时的效果:

桌面设备上的首次绘制分布图(受支持)

上述分布的中位数 firstpaint 时间为 912 毫秒

此曲线的形状是加载时间分布图的典型特征。与下方的直方图形成对比,该直方图显示了在服务工作器控制页面的访问中,首次绘制事件的分布情况。

桌面设备上的首次绘制分布时间(对照组)

请注意,当 Service Worker 控制页面时,许多访问者都体验到了几乎即时的首次绘制,中位数为 583 毫秒

“…当 Service Worker 控制页面时,许多访问者都体验到了几乎即时的首次绘制…”

为了更好地了解这两种分布之间的对比情况,下图显示了这两种分布的合并视图。显示非受控服务工访问量的直方图叠加在显示受控访问量的直方图上,这两个直方图又叠加在显示这两者总和的直方图上。

桌面设备上的首次绘制时间分布

我发现这些结果中有一个有趣的现象,即在初始峰值之后,使用受控 Service Worker 的分配仍然呈现钟形曲线。我预计曲线会先出现一个较大的峰值,然后逐渐下降,但没想到会出现第二个峰值。

在调查可能导致此问题的原因时,我发现即使 Service Worker 可以控制某个网页,其线程也可能处于非活动状态。浏览器这样做是为了节省资源 - 显然,您不需要让您曾经访问过的每个网站的每个 Service Worker 都处于活动状态,随时准备就绪。这说明了分布的尾部。对于某些用户,Service Worker 线程启动时会出现延迟。

不过,从分布图中可以看出,即使存在这种初始延迟,使用服务工件的浏览器提交内容的速度也比通过网络提交内容的浏览器更快。

移动设备上的显示效果如下:

移动设备上的首次绘制时间分布

虽然几乎即时的首次绘制时间仍有大幅缩短,但尾部变得更大、更长。这可能是因为,在移动设备上启动空闲的服务工作器线程所需的时间比在桌面设备上要长。这也解释了为什么平均 firstpaint 时间的差异没有我预期的那么大(如上所述)。

“…在移动设备上,启动空闲的 Service Worker 线程所需的时间比在桌面设备上要长。”

以下是移动设备和桌面设备上首次绘制时间中位数的这些变化细分,按服务工作器状态分组:

首次绘制时间中位数(毫秒)
Service Worker 状态 桌面设备 移动设备
操控了 583 1634
受支持(不受控制) 912 1933

虽然构建这些分布可视化图表比在 Google Analytics 中创建自定义报告需要多花一些时间和精力,但与仅使用平均值相比,它们能让我们更清楚地了解服务工如何影响我们网站的性能。

Service Worker 的其他影响

除了对性能的影响之外,服务工件还会通过 Google Analytics 可衡量的其他几种方式影响用户体验。

离线访问

借助服务工件,用户可以在离线状态下与您的网站互动。虽然某种程度的离线支持对任何渐进式 Web 应用都至关重要,但确定它对您而言有多重要,在很大程度上取决于离线使用量。但我们如何衡量这一点?

向 Google Analytics 发送数据需要连接到互联网,但无需在互动发生的确切时间发送数据。Google Analytics 支持通过指定时间偏移(通过 qt 参数)来事后发送互动数据。

在过去两年中,IOWA 一直在使用服务工脚本,该脚本会在用户离线时检测 Google Analytics 的失败命中,并稍后使用 qt 参数重放这些命中。

为了跟踪用户是处于在线状态还是离线状态,我们创建了一个名为 Online 的自定义维度,并将其值设置为 navigator.onLine,然后监听 onlineoffline 事件并相应地更新该维度。

为了了解用户在使用 IOWA 时处于离线状态的频率,我们创建了一个细分受众群体,以定位至少有一次离线互动的用户。事实证明,这几乎占到了用户总数的 5%。

推送通知

服务工作线程允许用户选择接收推送通知。在 IOWA 中,当用户日程中的某个会话即将开始时,系统会向用户发送通知。

与任何形式的通知一样,在为用户提供价值和让用户感到厌烦之间找到平衡非常重要。为了更好地了解具体情况,请务必跟踪用户是否选择接收这些通知、他们在收到通知时是否与之互动,以及之前选择接收通知的用户是否更改了偏好设置并选择退出。

在 IOWA 中,我们只会发送与用户的个性化时间表相关的通知,而只有已登录的用户才能创建时间表。这将可接收通知的用户群体限制为已登录的用户(通过名为“已登录”的自定义维度进行跟踪),且其浏览器支持推送通知(通过名为“通知权限”的自定义维度进行跟踪)。

以下报告基于“用户数”指标和“通知权限”自定义维度,按在某个时间点登录且浏览器支持推送通知的用户进行细分。

很高兴看到,超过半数的已登录用户选择接收推送通知。

应用安装横幅

如果某个渐进式 Web 应用符合条件且用户经常使用,系统可能会向该用户显示应用安装横幅,提示其将该应用添加到主屏幕。

在 IOWA 中,我们使用以下代码跟踪了向用户显示这些提示的频率(以及用户是否接受了这些提示):

window.addEventListener('beforeinstallprompt', function(event) {
  // Tracks that the user saw a prompt.
  ga('send', 'event', {
    eventCategory: 'installprompt',
    eventAction: 'fired'
  });

  event.userChoice.then(function(choiceResult) {
    // Tracks the users choice.
    ga('send', 'event', {
      eventCategory: 'installprompt',
      // `choiceResult.outcome` will be 'accepted' or 'dismissed'.
      eventAction: choiceResult.outcome,
      // `choiceResult.platform` will be 'web' or 'android' if the prompt was
      // accepted, or '' if the prompt was dismissed.
      eventLabel: choiceResult.platform
    });
  });
});

在看到应用安装横幅的用户中,约有 10% 的用户选择将应用添加到主屏幕。

可能的跟踪改进(下次)

我们今年从 IOWA 收集的分析数据非常有价值。但事后总是能发现漏洞和改进机会,为下次做得更好打好基础。完成今年的分析后,我发现有两点我们本可以做得更好,希望希望实施类似策略的读者可以考虑一下:

1. 跟踪更多与加载体验相关的事件

我们跟踪了与技术指标对应的多个事件(例如 HTMLImportsLoadedWebComponentsReady 等),但由于大部分加载都是异步进行的,因此这些事件触发的时间点不一定与整个加载体验中的特定时刻相对应。

我们未跟踪的主要加载相关事件(但希望已跟踪)是启动画面消失且用户可以看到页面内容的时刻。

2. 在 IndexedDB 中存储 Google Analytics 客户端 ID

默认情况下,analytics.js 会将客户端 ID 字段存储在浏览器的 Cookie 中;遗憾的是,Service Worker 脚本无法访问 Cookie。

这给我们在尝试实现通知跟踪时带来了问题。我们希望在每次向用户发送通知时,从服务工件发送事件(通过衡量协议),然后跟踪如果用户点击该通知并返回应用,该通知的再互动成功情况。

虽然我们通常可以通过 utm_source 广告系列参数跟踪通知的成效,但无法将特定再互动会话与特定用户相关联。

我们本可以通过 IndexedDB 在跟踪代码中存储客户端 ID,这样该值便可供服务工件脚本访问,从而解决此限制。

3. 让 Service Worker 报告在线/离线状态

检查 navigator.onLine 可让您了解浏览器是否能够连接到路由器或局域网,但不一定能告诉您用户是否真正连接到网络。由于我们的离线 Google Analytics 服务工作器脚本只是重放失败的命中(而没有对其进行修改或将其标记为失败),因此我们可能低估了线下使用情况。

今后,我们应同时跟踪 navigator.onLine 的状态,以及服务工作器是否因初始网络故障而重放了命中。这样,我们就可以更准确地了解真实的离线使用情况。

总结

这项案例研究表明,使用服务工件确实提高了 Google I/O 网站应用在各种浏览器、网络和设备上的加载性能。这项研究还表明,通过查看各种浏览器、网络和设备上的加载数据分布,您可以更深入地了解该技术如何处理真实场景,并发现您可能未预料到的性能特征。

以下是 IOWA 研究的一些主要收获:

  • 平均而言,对于新访问者和回访者而言,在有 Service Worker 控制页面的情况下,页面加载速度要比没有 Service Worker 时快得多。
  • 许多用户访问由 Service Worker 控制的页面时,页面几乎会立即加载。
  • 服务工作器在闲置时需要一些时间才能启动。不过,与不使用 Service Worker 相比,停用的 Service Worker 的性能仍然更好。
  • 与桌面设备相比,非活动状态的服务工件的启动时间在移动设备上更长。

虽然在一个特定应用中观察到的效果提升通常有助于向更广泛的开发者社区报告,但请务必注意,这些结果仅适用于 IOWA 的网站类型(活动网站)和 IOWA 的受众群体类型(主要是开发者)。

如果您要在应用中实现服务工件,请务必实现自己的衡量策略,以便评估自己的性能并防止日后出现回归问题。如果您有任何发现,请与我们分享,让所有人都能受益!

脚注

  1. 仅将服务工件缓存实现的性能与仅使用 HTTP 缓存的网站性能进行比较并不完全公平。由于我们针对 Service Worker 优化了 IOWA,因此没有花太多时间针对 HTTP 缓存进行优化。如果我们这样做了,结果可能有所不同。如需详细了解如何针对 HTTP 缓存优化您的网站,请参阅高效优化内容
  2. 根据您的网站加载样式和内容的方式,浏览器可能会在内容或样式可用之前进行绘制。在这种情况下,firstpaint 可能对应于空白的白色屏幕。如果您使用 firstpaint,请务必确保它与网站资源加载过程中的一个有意义的时刻相对应。
  3. 从技术层面讲,我们可以发送时间命中(默认情况下属于非互动事件)来捕获此类信息,而不是发送事件。事实上,Google Analytics 中添加时间戳命中数据的目的就是为了跟踪此类加载指标;不过,时间戳命中数据在处理时会被大量抽样,因此其值无法在细分中使用。鉴于目前存在的这些限制,非互动事件仍然更适合。
  4. 如需更好地了解在 Google Analytics 中为自定义维度指定什么范围,请参阅 Google Analytics 帮助中心的自定义维度部分。了解 Google Analytics 数据模型也很重要,该模型由用户、会话和互动(命中)组成。如需了解详情,请观看 Google Analytics Academy 中有关 Google Analytics 数据模型的课程
  5. 这不包括在加载事件之后延迟加载的资源。