Optimize frame rate with adaptive refresh rate

When an animation is initiated in Android, the display often boosts to the maximum refresh rate to ensure a smooth experience. For small animations such as progress bars and audio visualizers, this high refresh rate is unnecessary, and results in high power consumption.

Starting in Android 15, with the adaptive refresh rate (ARR) feature, enabled devices can reduce high refresh rate residency on two fronts:

  • With new platform frame rate management optimizations, apps can render at a lower frame rate by default and only boost to a high frame rate when necessary.
  • The display refresh rate dynamically matches the content render rate without jank.

While most apps should benefit from ARR without any modifications, you can also override the default frame rate behavior as needed.

This page describes the following:

  • How each View's frame rate is determined.
  • The general policy for how ARR determines what the frame rate is set to.
  • How you can manually override the default frame rate behavior.

The View voting mechanism

In Android's View system, each View in the UI hierarchy can express its preferred frame rate. These preferences are collected and combined to determine a final frame rate for each frame. This is achieved through a voting mechanism where each View votes based on its frame rate attribute, which can be a category or a specific rate. Views typically vote when drawn or updated. These votes are combined to determine a final frame rate, which is then sent to the lower-level layer as a hint for rendering.

Currently, most Views default to a "Normal" frame rate, which is often set to 60 Hz. For higher frame rates, you can use specific APIs to customize preferences, with the system generally selecting the highest frame rate. For more information about using these APIs, see the Set the frame rate or category section. The general policies surrounding frame rates are described in the General ARR policy section.

Frame rate categories

In the View class, there are different frame rate categories that can be used in the vote. The description of each category is as follows:

  • REQUESTED_FRAME_RATE_CATEGORY_DEFAULT: This value can be set to return to default behavior, indicating that this View has no data for the frame rate.
  • REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE: The View will explicitly not influence the frame rate. This means that, even if the View is active, the framework will not consider it when determining the frame rate
  • REQUESTED_FRAME_RATE_CATEGORY_NORMAL: Indicates a middle frame rate suitable for animations that don't require higher frame rates, or don't benefit from high smoothness. This is normally 60 Hz or close to it.
  • REQUESTED_FRAME_RATE_CATEGORY_HIGH: Indicates a frame rate suitable for animations that require a high frame rate, which may increase smoothness but may also increase power usage.

A View votes only if it requires redrawing. The final frame rate is determined by the highest vote. For example, if all votes are for "Normal," "Normal" is selected. When both "Normal" and "High" votes occur, "High" is chosen.

Frame rate

In addition to frame rate categories, a View can also specify a preferred frame rate, such as 30, 60, or 120 Hz. When multiple frame rate votes are cast, the final frame rate is determined by the following rules:

  • Multiples of each other: If the voted frame rates are multiples of one another, the highest value is chosen. For example, if there are two votes - 30 Hz and 90 Hz — 90 Hz is selected as the final frame rate.
  • Not multiples of each other:
    • If any of the votes is greater than 60 Hz, it counts as a "High" vote.
    • If all the votes are 60 Hz or lower, it counts as a "Normal" vote.

Additionally, if there is a combination of both frame rate values and frame rate categories, the higher value typically determines the final render rate. For example, with a combination of a 60 Hz vote and a "High" vote, or a 120 Hz vote and a "Normal" vote, the render rate would typically be set to 120 Hz.

In addition to the votes from an app, there may also be other hints sent to the lower-level layer from different components within the same frame. Many of these can originate from System UI components, such as the notification shade, status bar, navigation bar, and others. The final frame rate values are determined based on the votes from multiple components.

Set the frame rate or category

Under certain circumstances, you may have a preferred frame rate for a View. For example, you can set the preferred frame rate to "High" for a View to increase the frame rate if an animation appears unsmooth. Additionally, if there is a slow or static animation over a video (typically played at 24 or 30 Hz), you may prefer the animation to run at a rate lower than "Normal" to reduce power consumption.

You can use the setRequestedFrameRate() and getRequestedFrameRate() APIs to designate the preferred frame rate or category of a given View.

Kotlin

// Set the preferred frame rate category to a View

// set the frame rate category to NORMAL
view.requestedFrameRate = View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL
// set the frame rate category to HIGH
view.requestedFrameRate = View.REQUESTED_FRAME_RATE_CATEGORY_HIGH
// reset the frame rate category
view.requestedFrameRate = View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT

// Set the preferred frame rate to a View

// set the frame rate to 30
view.requestedFrameRate = 30f
// set the frame rate to 60
view.requestedFrameRate = 60f
// set the frame rate to 120
view.requestedFrameRate = 120f

Java

// Set the preferred frame rate category to a View

// set the frame rate category to NORMAL
view.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
// set the frame rate category to HIGH
view.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
// reset the frame rate category
view.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);

// Set the preferred frame rate to a View

// set the frame rate to 30
view.setRequestedFrameRate(30);
// set the frame rate to 60
view.setRequestedFrameRate(60);
// set the frame rate to 120
view.setRequestedFrameRate(120);

For example usage, see TextureView.

General ARR policy

In the previous section, we discussed that most animations are displayed at 60 Hz by default, as each View has "Normal" set as the preferred frame rate. However, there are exceptions where the frame rate is increased to "High" to ensure smoother animations.

The general ARR policy is as follows:

  • Touch boost: When a touch event (MotionEvent.ACTION_DOWN) is detected, the refresh rate is boosted to "High" for some time after the touch has been released to maintain responsiveness.
  • Fling gestures: Fling gestures are handled differently—the refresh rate decreases gradually as the fling velocity slows down. You can find details about this behavior in the Scrolling improvement section.
  • App launch and window transitions: The refresh rate is also boosted for some time during app launches, window initialization, and window transitions to ensure a smooth visual experience.
  • Animations: Animations that involve movement or size changes automatically receive a higher refresh rate to enhance smoothness when the position or size of a View changes.
  • SurfaceView and TextureView: Frame rates explicitly set for TextureView and SurfaceView are respected and applied accordingly.

Enable and disable touch boost

You can enable and/or disable touch boost at the Window level. By default, when a user touches and lifts their finger from the screen, the render rate increases for some time. The setFrameRateBoostOnTouchEnabled() and getFrameRateBoostOnTouchEnabled() APIs allow you to prevent the render rate from increasing when a specific Window is touched.

Kotlin

// disable touch boost on a Window
window.isFrameRateBoostOnTouchEnabled = false 
// enable touch boost on a Window
window.isFrameRateBoostOnTouchEnabled = true
// check if touch boost is enabled on a Window
val isTouchBoostEnabled = window.isFrameRateBoostOnTouchEnabled

Java

// disable touch boost on a Window
window.setFrameRateBoostOnTouchEnabled(false)
// enable touch boost on a Window
window.setFrameRateBoostOnTouchEnabled(true)
// check if touch boost is enabled on a Window
window.getFrameRateBoostOnTouchEnabled()

Scrolling improvement

One key use case for optimizing frame rate dynamically is to improve the scrolling (fling) experience. Many applications rely heavily on users swiping up to view new content. The ARR scrolling enhancement dynamically adjusts the refresh rate as the fling gesture slows down, gradually reducing the frame rate. This provides a more efficient rendering while maintaining smooth scrolling.

This improvement applies specifically to scrollable UI components, including ScrollView, ListView, and GridView, and may not be available for all custom implementations.

The ARR scrolling feature is available for RecyclerView and NestedScrollView. To enable this feature in your app, upgrade to the latest versions of AndroidX.recyclerview and AndroidX.core. See the following table for details.

Library

Version

AndroidX.recyclerview

1.4.0

AndroidX.core

1.15.0

Set the velocity information

If you have a custom scrollable component and want to take advantage of the scrolling feature, call setFrameContentVelocity() on every frame while smooth scrolling or flinging. See the following code snippet for an example:

Kotlin

// set the velocity to a View (1000 pixels/Second)
view.frameContentVelocity = 1000f
// get the velocity of a View
val velocity = view.frameContentVelocity

Java

// set the velocity to a View
view.setFrameContentVelocity(velocity);

// get the velocity of a View
final float velocity = view.getFrameContentVelocity()

For more examples, see RecyclerView and ScrollView. To correctly set the velocity, calculate the content velocity (pixels per second) manually if the required information cannot be obtained from Scroller or OverScroller.

Note that, if setFrameContentVelocity() and getFrameContentVelocity() are called on Views that are not scrollable components, they won't have any effect, as movement automatically triggers an increased frame rate based on the current policy.

The velocity information is crucial for adjusting the render rate. For example, consider the fling gesture. In the beginning, the velocity of a fling can be high, necessitating a higher render rate to ensure smoothness. As the gesture progresses, the velocity decreases, allowing the render rate to be lowered.

Enable and disable ARR

ARR is enabled by default to enhance power efficiency. While you can disable this feature, it is not recommended, as the app would consume more power. Only consider disabling this feature if you encounter issues that significantly impact user experience.

To enable or disable ARR, use the setFrameRatePowerSavingsBalanced() API on a Window, or use the isFrameRatePowerSavingsBalanced() API through your styles.xml file.

The following snippet shows how to enable or disable ARR on a Window:

Kotlin

// disable ARR on a Window
window.isFrameRatePowerSavingsBalanced = false 
// enable ARR on a Window
window.isFrameRatePowerSavingsBalanced = true  
// check if ARR is enabled on a Window
val isAdaptiveRefreshRateEnabled = window.isFrameRatePowerSavingsBalanced

Java

// disable ARR on a Window
window.setFrameRatePowerSavingsBalanced(false)
// enable ARR on a Window
window.setFrameRatePowerSavingsBalanced(true)
// check if ARR is enabled on a Window
window.isFrameRatePowerSavingsBalanced()

To disable ARR through the styles.xml file, add the following item to your style in res/values/styles.xml:

<style name="frameRatePowerSavingsBalancedDisabled">
    <item name="android:windowIsFrameRatePowerSavingsBalanced">false</item>
</style>