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