度量参考
Expo Observe 跟踪的每个性能指标的参考,包括概念和数据处理。
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
有关 Expo Observe 收集的性能指标、用于组织事件(会话和用户)的核心概念以及收集的数据如何保留的参考资料。
🌐 Reference for the performance metrics Expo Observe collects, the core concepts used to organize events (sessions and users), and how collected data is retained.
概念
🌐 Concepts
会话
🌐 Session
一个会话在应用进程启动时开始,在应用进程终止时结束。每个会话都有一个唯一标识符,并包含在该次应用启动期间收集的所有指标。
🌐 A session starts when the app process is launched and ends when the app process is terminated. Each session has a unique identifier and contains all metrics collected during that app launch.
用户
🌐 User
一个用户由每个应用安装唯一的匿名 ID 识别。此 ID:
🌐 A user is identified by an anonymous ID that is unique per app installation. This ID:
- 在应用首次安装时生成
- 在应用更新后仍然存在
- 如果用户卸载并重新安装应用,设置将被重置
- 不是个人可识别信息 (PII)
这使你能够查看同一用户在多个会话中的指标,而无需收集个人数据。
🌐 This allows you to see metrics across multiple sessions for the same user without collecting personal data.
指标
🌐 Metrics
信息 所有持续时间指标均以秒为单位报告。
冷启动时间
🌐 Cold launch time
测量内容: 从进程创建到系统完成内存分配、启动全新的运行时环境、从磁盘加载应用的代码和资源以及初始化其组件然后渲染用户界面所需的时间。这是启动时间最慢的类型,通常发生在全新安装、应用升级、设备重启或操作系统为了回收内存而终止应用之后。它是仅限原生的指标,这意味着你的 JavaScript 代码不会影响该指标,但 React Native 运行时初始化包括在内。
此指标会自动收集。
🌐 This metric is collected automatically.
如何改进:
- 移除未使用的本地模块。
- 避免使用静态初始化器(Objective-C 中的
+load方法,C++ 中的静态构造函数),以及添加它们的原生模块或配置插件。 - 保持应用的内存和CPU使用率低,以便操作系统在后台时不会终止进程。这本身不会影响此指标,但会使随后的启动是预热而不是冷启动。
- 如果你在非零
fallbackToCacheTimeout的情况下使用expo-updates,应用启动将被阻塞,等待更新检查。将此值保持为0(默认),或将checkOnLaunch设置为NEVER或ERROR_RECOVERY_ONLY以避免延迟冷启动。
我们的建议: 不超过1.5秒。
温启动时间
🌐 Warm launch time
它测量的内容: 热启动发生在操作系统已经在内存中拥有应用进程时,只需要将其带回前台并重新创建视图层次结构。与冷启动不同,大多数本地资源和服务已经在内存中,因此这种类型的启动速度显著更快。
应用不能预热自身:操作系统根据系统压力和最近的使用情况决定哪些进程保留在内存中。你可以影响预热启动的持续时间,但不能决定是否会发生预热启动。
🌐 Apps can't pre-warm themselves: the OS decides which processes stay in memory based on system pressure and recent use. You can influence the duration of a warm launch, but not whether one happens.
此指标会自动收集。
🌐 This metric is collected automatically.
如何改进:
- 移除未使用的本地模块。
- 减少视图层次结构中的视图数量。操作系统在热启动时必须重新创建视图树,因此深度嵌套或过于庞大的树需要更长的时间来恢复。
我们的建议: 不超过0.5秒。
打包加载时间
🌐 Bundle load time
**测量内容:**加载 JavaScript 字节码并进行评估的持续时间。此过程从打包包开始加载时开始,到打包包完成评估(在调用runApplication之前)时结束。
此指标会自动收集。
🌐 This metric is collected automatically.
如何改进:
- 减少打包包大小:
- 使用 tree shaking(从 Expo SDK 54 开始默认启用)并遵循有助于 Metro 剥离不必要代码的规则。请参见 Tree shaking 和代码移除。
- 分析你的 JavaScript 包以移除未使用和大型依赖。请参阅 使用 Expo Atlas 分析 JavaScript 包。
- 使用
React.lazy()对大型屏幕和组件进行懒加载。请参阅 优化 JavaScript 加载。 - 避免在顶层作用域阻塞 JavaScript 线程:
- 不要进行大量计算。
- 延迟任何同步 I/O 操作(存储读取和写入)。
我们的建议: 不超过0.3秒。
首次渲染时间 (TTR)
🌐 Time to first render (TTR)
它的测量内容: 从应用完成原生启动到根 React 组件首次在屏幕上渲染的时间。这是 React 在启动画面隐藏后实际渲染内容的时刻。每个应用的目标应该是尽可能快地显示有意义的内容,即使它只是一个骨架加载屏幕。
当你用 AppMetricsRoot 封装根布局时,会自动收集此指标(参见 入门)。如果需要,你也可以手动调用 AppMetrics.markFirstRender()。
🌐 This metric is collected automatically when you wrap your root layout with AppMetricsRoot (see Get started). You can also call AppMetrics.markFirstRender() manually if needed.
如何改进:
- 减少打包加载时间(见上文)。
- 避免同步 I/O 操作(存储读取和写入)。
- 避免在网络请求上阻塞。
- 保持初始渲染树小(延迟加载大型组件)。
- 使用轻量级屏幕作为初始路线。
- 最小化阻碍渲染的
useEffect和useLayoutEffect链条。
**我们的建议:**包括冷启动时间在内不超过2秒。
可交互时间 (TTI)
🌐 Time to interactive (TTI)
它所衡量的内容: 从应用冷启动/热启动开始,到用户可以实际点击、滚动和以其他方式与应用互动之间的时间。它是最重要的启动指标,因为这是用户感知“应用已准备就绪”的标准。
此指标不会自动报告。要开始测量它,请在屏幕准备好供用户交互时调用 AppMetrics.markInteractive(),例如在数据初始加载后运行的 useEffect 中。如果你的应用使用深度链接,主屏幕可能并不总是初始屏幕,因此我们建议在其他屏幕上也调用此函数。每次启动只记录第一次调用,因此多次调用是安全的(例如,当用户在屏幕之间导航时)。如果你使用 expo-router,该指标的事件会自动包含当前路由名称。
🌐 This metric is not reported automatically. To start measuring it, call AppMetrics.markInteractive() once the screen is ready for user interaction, for example in a useEffect that runs after your initial data has loaded. If your app uses deep links, the main screen may not always be the initial screen, so we recommend calling this function on other screens too. Only the first call per launch is recorded, so it is safe to call it multiple times (for example, when the user navigates between screens). If you use expo-router, the metric's event automatically includes the current route name.
是什么让一个应用“互动”?
以下所有事项都必须为真:
🌐 All of these must be true:
- 内容呈现在屏幕上(不仅仅是启动画面或骨架屏)。
- 触控处理程序已附加并且可以响应。
- 导航功能正常。
如何提高测量准确性: 只有在屏幕内容加载完成并且触摸处理程序可用后才调用 AppMetrics.markInteractive(),而不是仅在组件挂载时调用。如果你的屏幕在可用之前需要获取数据,请在数据准备好后再进行调用。
如何改进:
- 减少首次渲染的时间(见上文)。
- 在显示交互式内容之前,避免瀑布式数据获取。
- 优化初始网络请求。
- 避免渲染大型列表(使用 FlashList 或 LegendList)。
- 减少可能阻塞 JavaScript 线程和交互的繁重工作(I/O 操作、状态水合、JSON 解析)。
- 如果可能,先显示缓存或本地数据。
**我们的建议:**包括冷启动时间在内不超过3秒。
自动事件参数
🌐 Automatic event params
每个 TTI 事件都包含额外参数以帮助排查问题:
🌐 Each TTI event includes extra params to help triage issues:
expo.frameRate.slowFrames(计数):渲染时间达到或超过 17 毫秒的帧。如果相对于启动时长这个数值较高,表示启动期间主线程一直处于繁忙状态。可能是因为布局工作量大、同步桥调用,或同时渲染的组件过多。expo.frameRate.frozenFrames(计数):渲染时间为700毫秒或更长的帧。这些是应用明显卡住的严重冻结情况。即使是在启动期间出现一次,也是一个严重问题。通常由同步I/O、大型JSON解析或主线程上的阻塞网络调用引起。expo.frameRate.totalDelay(秒):所有帧超出其目标持续时间的总累计时间。这是衡量“流畅度”的最佳单一指标。将其与 TTI 对比:如果 TTI 为 2.5 秒且totalDelay为 0.1 秒,启动虽然慢但流畅(时间花在了合理的工作上)。如果totalDelay为 1.5 秒,应用在大多数启动过程中表现卡顿,用户会看到一个抖动的屏幕。expo.device.lowPowerMode(布尔值):在报告 TTI 时操作系统的省电模式(iOS 上的低电量模式,Android 上的电池优化模式)是否处于激活状态。省电模式会限制 CPU、GPU 和后台活动,因此一旦过滤掉此标志后 TTI 回归消失,这通常是环境因素而非代码更改导致的。expo.device.batteryLevel(数值,0–1):TTI 时的电池电量分数。对于排除在低电量时会积极管理性能的设备上的热量/降频影响很有用。当操作系统未报告数值时省略。expo.device.batteryCharging(布尔值):设备是否通过有线连接或无线充电。充电往往会提高 iOS 和一些安卓厂商设备的持续 CPU 性能上限,因此非充电样本是更保守的对比群体。expo.device.thermalState(字符串):nominal、fair、serious、critical、unknown之一。持续的serious/critical状态会导致操作系统限制 CPU/GPU 的性能,并可能显著减慢启动速度,与任何应用的更改无关。expo.network.connected(布尔值):设备在 TTI 时是否具有可上网的网络。如果 TTI 仅在true时下降,则原因可能是网络相关的启动路径;如果在false时下降,则说明应用在显示缓存内容之前做了比应有更多的工作。expo.network.type(字符串):wifi、cellular、ethernet、none、other、unknown中的一个。用于比较蜂窝网络与 Wi-Fi 用户群——通常大的差异表明启动工作受网络限制。VPN 流量会报告为其底层传输协议(通常是wifi或cellular),因为 VPN 是通过该协议建立隧道的。该值在 Android 和 iOS 上故意设置为相同,以便仪表板无需针对不同平台进行分支处理。
如何解读它们:
- 高TTI + 低总延迟: 启动慢但平滑。优化阻塞启动序列的因素(包大小、数据获取、初始化链)。
- 高 TTI + 高总延迟 + 许多慢帧: 主线程竞争。将工作卸载出去并简化初始渲染树。
- 高TTI + 高延迟 + 卡顿帧: 有东西严重阻塞。检查同步I/O、大量JSON解析或阻塞的API调用。
自定义事件参数
🌐 Custom event params
你可以通过将参数传递给 markInteractive() 来将你自己的参数附加到 TTI 事件。这对于按应用特定维度(例如用户群组、租户、功能标记变体或屏幕加载的内容类型)来划分 TTI 非常有用。
🌐 You can attach your own params to the TTI event by passing them to markInteractive(). This is useful for slicing TTI by app-specific dimensions such as user cohort, tenant, feature flag variant, or the type of content the screen loaded.
AppMetrics.markInteractive({ params: { tenant: 'acme', cohort: 'beta', cacheHit: true, }, });
你也可以覆盖附加到事件的路由名称,否则该名称是由 Expo Router 检测到的初始路由填充的。当屏幕的逻辑名称与路由路径不同、是动态路由,或者没有使用 Expo Router 时,这非常有用:
🌐 You can also override the route name attached to the event, which is otherwise populated from the initial route detected by Expo Router. This useful when the screen's logical name differs from the router path, is a dynamic route, or when not using Expo Router:
AppMetrics.markInteractive({ routeName: '/feed', params: { cacheHit: true }, });
参数值可以是字符串、数字、布尔值或其他可 JSON 序列化的值。
🌐 Param values can be strings, numbers, booleans, or other JSON-serializable values.
数据处理
🌐 Data handling
离线收集
🌐 Offline collection
设备离线时收集的指标会存储在设备上。当应用移动到后台且有连接时,这些指标会自动发送到服务器。你也可以随时调用 ExpoObserve.dispatchEvents() 来手动刷新事件。
🌐 Metrics collected while the device is offline are stored on the device. They are automatically sent to the server when the app moves to the background, provided there is connectivity. You can also call ExpoObserve.dispatchEvents() to flush events manually at any time.
数据保留
🌐 Data retention
度量数据至少保留 60 天。
🌐 Metric data is retained for a minimum of 60 days.
采样
🌐 Sampling
默认情况下,所有安装都会发送它们的指标。若只想让部分安装发送指标,请在调用 configure() 时设置 sampleRate。每个安装的决定是确定性的,因此某个安装在应用启动之间始终是样本内或样本外。
🌐 By default, all installations dispatch their metrics. To dispatch from a fraction of installations instead, set sampleRate when calling configure(). The decision is deterministic per installation, so an installation is consistently in-sample or out-of-sample across app launches.
环境
🌐 Environment
所有指标均按环境分组。环境值默认从 process.env.NODE_ENV 获取(如果未设置,则回退到 'production'),也可以通过 configure({ environment }) 覆盖。环境是附加到每个指标的元数据标签,不会影响指标是否被发送。
🌐 All metrics are grouped by environment. The environment value is derived from process.env.NODE_ENV by default (falling back to 'production' if unset), or can be overridden via configure({ environment }). The environment is a metadata tag attached to each metric and does not affect whether metrics are dispatched.
调试构建
🌐 Debug builds
从调试构建中收集的指标在分发之前会被丢弃,除非通过 configure() 将 dispatchInDebug 设置为 true(有关信息,请参见 在开发中启用指标)。如果本地应用是调试构建或 JS 包是开发包(__DEV__ 为 true),则构建将被视为调试构建。此检测独立于 environment 的值。
🌐 Metrics collected from debug builds are dropped before dispatching unless dispatchInDebug is set to true via configure() (for information, see Enable metrics in development). A build is treated as a debug build if either the native app is a debug build or the JS bundle is a development bundle (__DEV__ is true). This detection is independent of the environment value.
禁用调度
🌐 Disabling dispatching
你可以使用 configure({ dispatchingEnabled: false }) 全局禁用所有派发。禁用时,任何未处理的指标都会在未派发的情况下被丢弃,并且在将其设置回 true 之前不会派发任何其他指标。
🌐 You can disable all dispatching globally using configure({ dispatchingEnabled: false }). While disabled, any pending metrics are dropped without being dispatched and no further metrics are dispatched until it is set back to true.