测量渲染时间
您可以使用该库测量三种渲染时间
- 应用启动渲染时间
- 当应用的 native 部分启动时,计时器开始计时
- 当应用中的第一个屏幕被挂载并完全渲染时,计时器停止计时
- 导航渲染时间
- 当在屏幕 A 上按下某个 `Touchable` 时,计时器开始计时
- 应用导航到屏幕 B
- 当屏幕 B 被挂载并完全渲染时,计时器停止计时
- 屏幕重新渲染时间
- 当屏幕上发生某个 UI 事件(例如下拉刷新)时,计时器开始计时
- 屏幕上发生一些事情(例如,网络调用)
- 当屏幕再次完全渲染时,计时器停止计时
1. 测量应用启动渲染时间
当您完成Android 和 iOS初始化时,此测量的计时器已经开始计时。
为了停止计时器,您首先需要识别应用中用户在启动时可以到达的屏幕。这些屏幕必须是用户可交互的(即,可能不希望在此处使用启动画面)。如果您的应用有“主页”屏幕,那可能就是它。如果您的应用在没有用户登录时有“欢迎”或“登录”屏幕,这也符合条件。如果您可以通过小部件或通知深入链接到应用中的各种屏幕,它们也都可以算作符合条件。
然后,用 PerformanceMeasureView
包装所有这些屏幕组件返回的 JSX。例如:
tsx
const HomeScreen = () => {const {data} = useQuery(...)const homeItems = data?.homeItemsreturn (<PerformanceMeasureView interactive={homeItems !== undefined} screenName="HomeScreen">{homeItems === undefined? <LoadingIndicator /> : <HomeListView items={homeItems}/>}</PerformanceMeasureView>)}
tsx
const HomeScreen = () => {const {data} = useQuery(...)const homeItems = data?.homeItemsreturn (<PerformanceMeasureView interactive={homeItems !== undefined} screenName="HomeScreen">{homeItems === undefined? <LoadingIndicator /> : <HomeListView items={homeItems}/>}</PerformanceMeasureView>)}
该库将自动识别在应用中渲染的第一个 PerformanceMeasureView
,并将其解释为启动时的主要着陆屏幕。然后,它会等待此屏幕的 native UI 视图被渲染,并生成 RenderPassReport
作为输出。
简单情况:最多 2 个渲染过程
大多数屏幕会经历 2 个渲染过程
- 一个加载指示器(增量渲染过程)
- 最终渲染的屏幕(最终渲染过程)
正如之前状态机部分所述,您需要告知库这些渲染过程。在上面的示例中,您是通过 interactive
属性来实现的。当 homeItems === undefined
时,您将 interactive = false
,告诉状态机向用户显示 LoadingIndicator
的渲染过程不是可交互的。仅当获取了 homeItems
且 interactive
属性设置为 true
时,才会进行最终渲染过程。该库将在其为这两个渲染过程生成的报告中包含此 interactive
属性。
如果您的屏幕是静态屏幕,不会经历加载渲染过程,则可以直接设置 interactive: true
(默认为 false
)。
高级情况:超过 2 个渲染过程
上面的示例假设您希望在屏幕在 homeItems
可用后有意义地渲染时立即停止分析。如果您的 useQuery
调用的 fetchPolicy
是 cache-and-network
,这可能意味着分析器将在屏幕从缓存数据有意义地渲染时停止。屏幕的任何后续重新渲染(例如,当网络响应到达时)都不会生成任何报告,因为 interactive
属性没有更改。这对于某些用例可能是可以接受的,因为它会奖励您利用缓存来为用户提供更快的体验。但是,您可能仍然希望记录缓存的渲染时间和完整的网络渲染时间。这种三渲染过程的情况无法通过单个布尔值 interactive
属性来捕获。在这种情况下,您可以将屏幕建模为具有 3 个渲染过程
- 一个加载指示器(不可交互的渲染过程)
- 缓存的渲染(可交互的渲染过程)
- 网络渲染(另一个可交互的渲染过程)
您可以通过 renderPassName
属性(可选)为它们提供显式名称来区分这两个可交互的渲染过程。
实际上,设置 interactive={true}
等效于设置 interactive={true} renderPassName="interactive"
,而设置 interactive ={false}
等效于设置 interactive={false} renderPassName="loading"
。当仅提供 interactive
属性时,该库使用合理的渲染过程名称默认值。
2. 测量导航渲染时间
此用例的计时器在按下某个 ScreenA
上的 Touchable
并且调用了到 ScreenB
的 navigate
调用时开始计时。您可以通过 useStartProfiler
钩子来完成此操作。
tsx
const ScreenA = ({navigation}) => {const startNavigationTTITimer = useStartProfiler();return (<>{/* some JSX */}<TouchableWithoutFeedbackonPress={uiEvent => {startNavigationTTITimer({source: 'ScreenA',uiEvent,});navigation.navigate('ScreenB');}}/></>);};
tsx
const ScreenA = ({navigation}) => {const startNavigationTTITimer = useStartProfiler();return (<>{/* some JSX */}<TouchableWithoutFeedbackonPress={uiEvent => {startNavigationTTITimer({source: 'ScreenA',uiEvent,});navigation.navigate('ScreenB');}}/></>);};
和之前一样,您可以用 PerformanceMeasureView
包装目标 ScreenB
来停止计时器。前面部分中与多个渲染过程相关的所有概念也适用于此用例。
请注意,此包的配套库——performance-react-navigation-base、performance-react-navigation-drawer、performance-react-navigation-bottom-tabs——附带了一些方便的实用程序,如 createProfiledBottomTabNavigator
和 useProfiledNavigation
。这些实用程序使启动导航渲染计时器的过程稍微不那么冗长。如果它们适用于您的用例,您可能需要查看它们。
3. 测量屏幕重新渲染时间
当当前屏幕内的某个 UI 事件导致屏幕重新绘制时,屏幕重新渲染开始。考虑一下列表屏幕中的下拉刷新或搜索屏幕中查询词发生更改等场景。
这次您需要使用 useResetFlow
钩子而不是 useStartProfiler
钩子。此钩子需要传递一个 destination
属性来标识正在重新绘制的屏幕。默认情况下,sourceScreen
与 destinationScreen
相同,这意味着用户从正在重新绘制的屏幕触发了重新绘制。如果不是这种情况,您可以选择使用不同的 sourceScreen
;例如,如果重新绘制是由模态窗口或不同选项卡中的按钮触发的。
使用 useResetFlow
还要求您将 componentInstanceId
传递给 PerformanceMeasureView
/ReactNavigationPerformanceView
。这允许库将重新启动的流程与相应的 MeasureView
匹配
tsx
const HomeScreen = () => {const {resetFlow, componentInstanceId} = useResetFlow();const {data, refetch, networkStatus} = useQuery(...)const homeItems = data?.homeItemsconst renderStateProps: RenderStateProps = {interactive: data !== undefined,renderPassName: data === undefined ? 'loading' : (isNetworkRequestInFlight(networkStatus) ? 'cached_render' : 'network_render')}return (<PerformanceMeasureView componentInstanceId={componentInstanceId} screenName="HomeScreen" {...renderStateProps}><FlatList{ /* configure your FlatList */ }onRefresh={() => {resetFlow({destination: 'ScreenA'})refetch()}}/></PerformanceMeasureView>)}
tsx
const HomeScreen = () => {const {resetFlow, componentInstanceId} = useResetFlow();const {data, refetch, networkStatus} = useQuery(...)const homeItems = data?.homeItemsconst renderStateProps: RenderStateProps = {interactive: data !== undefined,renderPassName: data === undefined ? 'loading' : (isNetworkRequestInFlight(networkStatus) ? 'cached_render' : 'network_render')}return (<PerformanceMeasureView componentInstanceId={componentInstanceId} screenName="HomeScreen" {...renderStateProps}><FlatList{ /* configure your FlatList */ }onRefresh={() => {resetFlow({destination: 'ScreenA'})refetch()}}/></PerformanceMeasureView>)}
此用例与之前的导航用例类似,只是您在同一屏幕上启动和停止渲染计时器。不涉及导航。因此,同一屏幕同时包含通过 useResetFlow
钩子的启动调用,以及通过 PerformanceMeasureView
组件使用情况的停止调用。
上面的示例还展示了如何使用 renderPassName
属性来表示这些复杂的渲染过程场景。
从渲染时间计算 TTI
已分析的屏幕可能会多次重新渲染。因此,该库可能会生成多个 RenderPassReports
。但是,当库发出第一个 interactive
属性设置为 true
的 RenderPassReport
时,可以认为屏幕已达到可交互状态。您可以将该渲染过程的 timeToRender
也解释为可交互时间(TTI)。