跳到主要内容

常见问题

我们需要用 <PerformanceMeasureView> 包裹每个屏幕吗?我们是否可以在配置 react-navigation 导航器时自动为每个屏幕注入它?

在设置 react-navigation 导航器时,有人可能会倾向于用 PerformanceMeasureView 组件包裹应用程序中的所有屏幕。就像这样

tsx
function withPerformanceMeasureView<TProps>(Screen: (props: TProps) => React.ReactElement, screenName: string) {
const ProfiledScreen = (props: TProps) => {
return (
<PerformanceMeasureView screenName={screenName}>
<Screen {...props} />
</PerformanceMeasureView>
);
};
return ProfiledScreen;
}
function NavigationTree() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name={NavigationKeys.SCREEN_1}
component={withPerformanceMeasureView(Screen1, NavigationKeys.SCREEN_1)}
/>
<Stack.Screen
name={NavigationKeys.SCREEN_2}
component={withPerformanceMeasureView(Screen2, NavigationKeys.SCREEN_1)}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
tsx
function withPerformanceMeasureView<TProps>(Screen: (props: TProps) => React.ReactElement, screenName: string) {
const ProfiledScreen = (props: TProps) => {
return (
<PerformanceMeasureView screenName={screenName}>
<Screen {...props} />
</PerformanceMeasureView>
);
};
return ProfiledScreen;
}
function NavigationTree() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name={NavigationKeys.SCREEN_1}
component={withPerformanceMeasureView(Screen1, NavigationKeys.SCREEN_1)}
/>
<Stack.Screen
name={NavigationKeys.SCREEN_2}
component={withPerformanceMeasureView(Screen2, NavigationKeys.SCREEN_1)}
/>
</Stack.Navigator>
</NavigationContainer>
);
}

我们建议不要这样做。像这样包裹屏幕组件会在该组件渲染任何东西(包括初始加载指示器)的那一刻将其标记为 interactive。这可能不是你想要的。

在大多数情况下,目标屏幕将获取某些资源,并在一定延迟后渲染为“交互式”状态。在屏幕“交互式”之前,它将渲染一个中间的“非交互式”状态(又名加载屏幕)。PerformanceMeasureView 需要通过 interactive: booleanrenderPassName: string 属性获取此信息。有关更多详细信息,请参阅 README。提供此信息将允许库计算你可能真正关心的内容:屏幕有意义地渲染并变为交互式所需的时间。获得此洞察力的一个权衡是,这个过程在大多数应用程序中可能无法自动化。在大多数应用程序中,需要根据每个屏幕的资源获取逻辑来评估每个屏幕上的 interactiverenderPassName 属性的值。

但是,如果你正在构建一个“静态”应用程序,该应用程序立即渲染每个屏幕的交互式版本,而无需经历增量渲染过程,那么你可能可以像上面的示例那样包裹所有屏幕。因此,在这种情况下,请使用你最佳的判断力。

我们需要每次在安排屏幕导航时手动调用 useStartProfiler hook 返回的函数吗?我们是否可以观察 react-navigation 事件并自动化此过程?

每当请求导航操作时,react-navigation 都会发出某些 事件。有人可能想知道我们是否可以观察这些事件,并通过 useStartProfiler hook 自动通知分析库开始性能分析。但是,我们不想这样做,因为这样做会导致输出报告出现不准确的情况。

我们希望尽可能地接近用户实际从 UI 请求时记录“流程开始”事件。理想情况下,这需要在按下给定的原生视图(例如,按钮)时发生(选项 1)。但是,如果不提供需要在整个应用程序代码库中侵入性引入的自定义 ProfiledTouchable 组件,则无法做到这一点。这种自定义组件还需要抛弃所有投入到库存 Touchable 组件中的好处(和多年的边缘情况覆盖),这感觉就像是在重新发明轮子。

下一个最佳选择是在调用 JS Touchable.onPress 回调时记录该事件(选项 2)。这不如选项 1 准确,因为它没有考虑通过桥从原生层传播到 JS 层的触摸事件,以及在 JS 事件队列中被消耗所造成的延迟。

幸运的是,Touchable.onPress 函数接收一个 GestureResponderEvent 类型的对象作为第 0 个参数。此对象带有一个 nativeEvent.timestamp: number 属性。此 timestamp 是我们通过选项 1 需要的确切时间。因此,如果在使用选项 2 时向库提供此(可选)GestureResponderEvent 对象,则库可以在内部有效地切换到选项 1 计算,从而为我们提供极其准确的结果。此外,我们可以计算原生触摸事件和 JS onPress 调用之间的时间,以了解原生到 JS 通信通道的繁忙程度;这通过输出 RenderPassReports 中的 timeToConsumeTouchEvent 属性传达。如果你发现较高的 timeToRenderMillis 值,此信息可以提供额外的调试信息。

观察 react-navigation 事件(选项 3)意味着我们在计算中添加了更多不准确性。react-navigation 的实现细节在大多数情况下,当我们调用 navigation.navigate(在 Touchable.onPress 回调中)和发出相应的 react-navigation 事件之间增加了可测量的延迟。如果你通过另一个事件系统(例如,Redux)进一步解耦这两个代码片段,则此延迟会变得更糟。这些延迟足够大,足以使此选项成为性能监控用例的致命缺陷。

此外,这些事件不携带 GestureResponderEvent 对象,因此我们甚至无法逆向工程实际按下原生视图的时间。我们调查了向 react-navigation 本身添加补丁,以便适用的事件携带此元数据需要多少工作,但这并非易事。例如,抽屉导航器的 iOS 实现使用的可触摸对象(请参阅 此处 的代码)使用 react-native-gesture-handler 提供的 BaseButton 组件,该组件不会通过其 onPress 回调发送 GestureResponderEvent 对象。因此,实现此选项 3 需要对我们不一定控制的许多 OSS 库进行补丁,并且依赖关系图将非常脆弱。因此,考虑到正确实现选项 3 所需的维护和开发工作,它没有通过筛选。

也就是说,可以理解的是,在 Touchable.onPress 回调中进行两个调用是容易出错(且令人讨厌)的:一个用于安排 react-navigation 操作,另一个用于通知 rn-performance 该事件。为了帮助解决这个问题,配套的 react-native-performance-navigation 库带有一个 useProfiledNavigation hook,以简化此过程。

我们是否可以在目标屏幕中调用函数来结束分析器计时器,而不是将其包装在组件中?

不,我们不能。这样做会在渲染 JS 组件时结束计时器,这与通过原生视图将屏幕实际物化到屏幕上的时间点不同。用 <PerformanceMeasureView> 包裹目标屏幕允许我们在屏幕上注入一个不可见的自定义视图(作为实际屏幕内容的兄弟),并等待它被原生渲染。我们将其用作屏幕实际渲染时间的近似值。请注意,我们实际上仍然没有测量真实视图的渲染时间;但是由于注入的视图是兄弟,因此这是一个相当不错的近似值。至少它比在渲染 JS 组件时结束计时器要好得多。