1 /*
2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package androidx.metrics.performance
17 
18 import android.os.Build
19 import android.view.View
20 import android.view.Window
21 import androidx.annotation.RestrictTo
22 import androidx.annotation.UiThread
23 import java.lang.IllegalStateException
24 
25 /**
26  * This class is used to both accumulate and report information about UI "jank" (runtime performance
27  * problems) in an application.
28  *
29  * There are three major components at work in JankStats:
30  *
31  * **Identifying Jank**: This library uses internal heuristics to determine when jank has occurred,
32  * and uses that information to know when to issue jank reports so that developers have information
33  * on those problems to help analyze and fix the issues.
34  *
35  * **Providing UI Context**: To make the jank reports more useful and actionable, the system
36  * provides a mechanism to help track the current state of the UI and user. This information is
37  * provided whenever reports are logged, so that developers can understand not only when problems
38  * occurred, but what the user was doing at the time, to help identify problem areas in the
39  * application that can then be addressed. Some of this state is provided automatically, and
40  * internally, by various AndroidX libraries. But developers are encouraged to provide their own
41  * app-specific state as well. See [PerformanceMetricsState] for more information on logging this
42  * state information.
43  *
44  * **Reporting Results**: On every frame, the JankStats client is notified via a listener with
45  * information about that frame, including how long the frame took to complete, whether it was
46  * considered jank, and what the UI context was during that frame. Clients are encouraged to
47  * aggregate and upload the data as they see fit for analysis that can help debug overall
48  * performance problems.
49  *
50  * Note that the behavior of JankStats varies according to API level, because it is dependent upon
51  * underlying capabilities in the platform to determine frame timing information. Below API level
52  * 16, JankStats does nothing, because there is no way to derive dependable frame timing data.
53  * Starting at API level 16, JankStats uses rough frame timing information that can at least provide
54  * estimates of how long frames should have taken, compared to how long they actually took. Starting
55  * with API level 24, frame durations are more dependable, using platform timing APIs that are
56  * available in that release. And starting in API level 31, there is even more underlying platform
57  * information which helps provide more accurate timing still. On all of these releases (starting
58  * with API level 16), the base functionality of JankStats should at least provide useful
59  * information about performance problems, along with the state of the application during those
60  * frames, but the timing data will be necessarily more accurate for later releases, as described
61  * above.
62  */
63 @Suppress("SingletonConstructor")
64 class JankStats private constructor(window: Window, private val frameListener: OnFrameListener) {
65     private val holder: PerformanceMetricsState.Holder
66 
67     /**
68      * JankStats uses the platform FrameMetrics API internally when it is available to track frame
69      * timings. It turns this data into "jank" metrics. Prior to API 24, it uses other mechanisms to
70      * derive frame durations (not as dependable as FrameMetrics, but better than nothing).
71      *
72      * Because of this platform version limitation, most of the functionality of JankStats is in the
73      * impl class, which is instantiated when necessary based on the runtime OS version. The
74      * JankStats API is basically a thin wrapper around the implementations in these classes.
75      */
76     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal val implementation: JankStatsBaseImpl
77 
78     init {
79         val decorView: View =
80             window.peekDecorView()
81                 ?: throw IllegalStateException(
82                     "window.peekDecorView() is null: " +
83                         "JankStats can only be created with a Window that has a non-null DecorView"
84                 )
85         holder = PerformanceMetricsState.create(decorView)
86         implementation =
87             when {
88                 Build.VERSION.SDK_INT >= 31 -> {
89                     JankStatsApi31Impl(this, decorView, window)
90                 }
91                 Build.VERSION.SDK_INT >= 26 -> {
92                     JankStatsApi26Impl(this, decorView, window)
93                 }
94                 Build.VERSION.SDK_INT >= 24 -> {
95                     JankStatsApi24Impl(this, decorView, window)
96                 }
97                 else -> {
98                     JankStatsApi16Impl(this, decorView)
99                 }
100             }
101         implementation.setupFrameTimer(true)
102     }
103 
104     /**
105      * Whether this JankStats instance is enabled for tracking and reporting jank data. Enabling
106      * tracking causes JankStats to listen to system frame-timing information and record data on a
107      * per-frame basis that can later be reported to the JankStats listener. Tracking is enabled by
108      * default at creation time.
109      */
110     var isTrackingEnabled: Boolean = true
111         /**
112          * Enabling tracking causes JankStats to listen to system frame-timing information and
113          * record data on a per-frame basis that can later be reported to the JankStats listener.
114          * Tracking is enabled by default at creation time.
115          */
116         @UiThread
117         set(value) {
118             if (value != field) {
119                 implementation.setupFrameTimer(value)
120                 field = value
121             }
122         }
123 
124     /**
125      * This multiplier is used to determine when frames are exhibiting jank.
126      *
127      * The factor is multiplied by the current refresh rate to calculate a frame duration beyond
128      * which frames are considered, and reported, as having jank. For example, an app wishing to
129      * ignore smaller-duration jank events should increase the multiplier. Setting the value to 0,
130      * while not recommended for production usage, causes all frames to be regarded as jank, which
131      * can be used in tests to verify correct instrumentation behavior.
132      *
133      * By default, the multiplier is 2.
134      */
135     var jankHeuristicMultiplier: Float = 2.0f
136         set(value) {
137             // reset calculated value to force recalculation based on new heuristic
138             JankStatsBaseImpl.frameDuration = -1
139             field = value
140         }
141 
142     /** Called internally (by Impl classes) with the frame data, which is passed onto the client. */
logFrameDatanull143     internal fun logFrameData(volatileFrameData: FrameData) {
144         frameListener.onFrame(volatileFrameData)
145     }
146 
147     companion object {
148         /**
149          * Creates a new JankStats object and starts tracking jank metrics for the given window.
150          *
151          * @throws IllegalStateException `window` must be active, with a non-null DecorView. See
152          *   [Window.peekDecorView].
153          * @see isTrackingEnabled
154          */
155         @JvmStatic
156         @UiThread
157         @Suppress("ExecutorRegistration")
createAndTracknull158         fun createAndTrack(window: Window, frameListener: OnFrameListener): JankStats {
159             return JankStats(window, frameListener)
160         }
161     }
162 
163     /**
164      * This interface should be implemented to receive per-frame callbacks with jank data.
165      *
166      * Internally, the [FrameData] objected passed to [OnFrameListener.onFrame] is reused and
167      * populated with new data on every frame. This means that listeners implementing
168      * [OnFrameListener] cannot depend on the data received in that structure over time and should
169      * consider the [FrameData] object **obsolete when control returns from the listener**. Clients
170      * wishing to retain data from this call should **copy the data elsewhere before returning**.
171      */
interfacenull172     fun interface OnFrameListener {
173 
174         /**
175          * The implementation of this method will be called on every frame when an OnFrameListener
176          * is set on this JankStats object.
177          *
178          * The FrameData object **will be modified internally after returning from the listener**;
179          * any data that needs to be retained should be copied before returning.
180          *
181          * @param volatileFrameData The data for the most recent frame.
182          */
183         fun onFrame(volatileFrameData: FrameData)
184     }
185 }
186