Your rendering code is clean, your draw calls are batched, and your assets are compressed — yet frame drops still hit on mid-range devices the moment your scene gets busy. The problem isn’t your GPU work. It’s that the CPU governor doesn’t know your game loop exists, so it scales frequency reactively instead of proactively. Android’s Dynamic Performance Framework (ADPF) fixes this by giving you a direct channel to the scheduler, and this guide shows you exactly how to wire it in.

Quick Summary

  • Minimum requirements: Android 12 (API 31) for GameMode API; API 31 for PerformanceHintManager; API 33 for full session feedback loop
  • Create a hint session by calling PerformanceHintManager.createHintSession() with your game loop thread IDs and a 16,666,666 ns target duration
  • Instrument every frame: call reportActualWorkDuration() with a System.nanoTime() delta at frame end
  • Register a GameModeManager listener to receive OEM interventions and adjust your target duration accordingly
  • Validate with Perfetto tracing to confirm the scheduler responds to your hints on real hardware

Why the CPU Governor Ignores Your Game Without Performance Hints

Android’s default CPU governor uses a conservative frequency-scaling policy built for general app workloads. It ramps up frequency after it detects sustained load, which means the first few frames of a heavy scene pay the cost of under-provisioned CPU time before the governor catches up. For a 16.67ms frame budget, that lag is enough to drop frames.

ADPF’s Performance Hint API changes the model. Instead of waiting for the governor to react, you tell the system what your workload looks like before each frame executes. The scheduler uses that forward-looking data to pre-scale CPU resources on the cores your game threads run on. On big.LITTLE architectures (which cover most modern Android SoCs), this also influences which cluster your threads land on.

How ADPF Structures the GameMode API and PerformanceHintManager

ADPF is the umbrella framework. Two APIs live inside it, and they operate at different layers — a distinction most documentation blurs.

GameMode API (API 31): This is a passive, OEM-configurable system. Users or device manufacturers set a game mode — GAME_MODE_STANDARD, GAME_MODE_PERFORMANCE, or GAME_MODE_BATTERY — through system UI or OEM overlays. Your app queries or listens for these states. The GameMode API doesn’t directly tune CPU frequency; it signals intent that OEMs act on through XML configuration.

PerformanceHintManager (API 31, full feedback loop at API 33): PerformanceHintManager is an Android API introduced in API level 31 that allows apps to send CPU scheduling hints to the operating system, enabling the scheduler to boost or throttle CPU frequency based on the app’s actual and target frame work durations. This is the active runtime layer. You create a hint session, report real frame durations each tick, and the scheduler adjusts dynamically.

These two APIs don’t conflict — they complement each other. Game Mode tells the system what profile the user wants; your hint session tells the scheduler exactly how much CPU work each frame actually requires.

Step-by-Step: Implementing a PerformanceHintManager Hint Session

  1. Initialize PerformanceHintManager during Activity startup. Call Context.getSystemService(PerformanceHintManager.class) and null-check the result. On devices that don’t support ADPF, this returns null and your code should fall back gracefully without crashing.
  2. Identify your game loop thread IDs. Only include threads that do actual CPU work in the game loop — your main game thread and render thread. Adding background I/O threads or asset loading threads inflates reported work duration and causes the governor to over-provision CPU, wasting battery.
  3. Create the hint session with a 60fps target duration. Call performanceHintManager.createHintSession(threadIds, 16_666_666L). The second argument is your target work duration in nanoseconds. For 60fps, that’s 16,666,666 ns. Store this PerformanceHintSession reference for the life of the game session — don’t recreate it per frame.
  4. Instrument your game loop to measure actual CPU work time. At the start of each frame, capture long frameStart = System.nanoTime(). At the end of CPU work (before any blocking wait on vsync), capture the delta and call hintSession.reportActualWorkDuration(actualDuration).
  5. Update target duration when frame rate targets change. When the game transitions to a 30fps cutscene or loading screen, call hintSession.updateTargetWorkDuration(33_333_333L). Failing to do this leaves the scheduler targeting 60fps during phases where you only need 30fps, draining battery unnecessarily.

Which threads should I include in my hint session?

Include only the threads whose CPU execution time directly contributes to frame completion. On a big.LITTLE SoC, passing the wrong thread IDs means the scheduler may boost the wrong core cluster entirely, and you’ll see no improvement in frame pacing. Audit your thread model before calling createHintSession() — this is one of the most common silent failures during ADPF integration.

What target duration should I set for 60fps?

Set 16,666,666 nanoseconds. Don’t use a rounded value like 16,000,000 — the precision matters because the scheduler uses this number to calculate frequency headroom. If your game targets 90fps, set 11,111,111 ns. If it targets 120fps, use 8,333,333 ns.

How often should I call reportActualWorkDuration?

Every frame, without exception. Skipping frames where work finishes early deprives the system of variance data it needs to smooth frequency scaling. The scheduler builds a workload model over time; gaps in reporting degrade that model and can cause the governor to under-provision on the next heavy frame.

Handling Game Mode Interventions Without Breaking Your Hint Session

Register a listener with GameManager to receive Game Mode state changes. Your Activity or GameActivity subclass should call gameManager.addGameModeListener(executor, listener) and respond to each state.

Game Mode Response Guide:

  • GAME_MODE_STANDARD: Maintain your current target duration and hint session as-is.
  • GAME_MODE_PERFORMANCE: The system may allow higher CPU headroom. Keep your hint session active — the scheduler needs your per-frame data to allocate that headroom correctly. Don’t assume PERFORMANCE mode automatically hits 60fps without active hints.
  • GAME_MODE_BATTERY: OEMs may cap CPU frequency. Adjust your target duration upward (e.g., to 33,333,333 ns for 30fps) and reduce quality settings. Fighting the cap with an aggressive 60fps target duration will produce inaccurate hints and degrade performance further.

The distinction that matters here: GAME_MODE_PERFORMANCE is an OEM signal, not a guarantee. It doesn’t automatically deliver 60fps. Your hint session is still the mechanism that tells the scheduler how to allocate the performance budget that mode makes available.

Managing Hint Session Lifecycle Across Foreground and Background Transitions

Pause hint reporting in onPause(). Sending hints while your app is backgrounded wastes system resources and can interact badly with ANR watchdog timers. Resume reporting in onResume().

Call hintSession.close() when the game session ends — at logout, level exit, or process teardown. This releases the session handle and avoids resource leaks. If the process is killed in the background and the user returns, re-initialize the session from scratch in onResume(). Don’t assume the handle survives a background kill.

Validating Your Hint Session with Perfetto Tracing

Emulator testing won’t show you real ADPF behavior. OEM scheduler implementations vary, and the hint session feedback loop only works as intended on physical hardware. Test on at least two devices with different SoCs before drawing conclusions about hint effectiveness.

Enable Perfetto tracing and look for the PerformanceHintManager track alongside your main thread and render thread slices. You should see CPU frequency boosts aligning with your hint session boundaries. If frequency doesn’t respond to your hints, the most likely cause is incorrect thread ID registration — verify that the thread IDs you passed to createHintSession() match the actual TIDs of your running game loop threads.

Android GPU Inspector can complement this by showing you GPU frame boundaries, helping you separate CPU-bound frame drops from GPU-bound ones. ADPF only helps with CPU scheduling; if your bottleneck is GPU-side, hint sessions won’t move the needle.

Frequently Asked Questions

Does PerformanceHintManager work on all Android devices?

No. Context.getSystemService(PerformanceHintManager.class) returns null on devices that don’t support ADPF. Always null-check before calling any session methods. The API is available from API level 31, but OEM support for the underlying scheduler integration varies.

What happens if I don’t call reportActualWorkDuration?

The scheduler loses its workload model and reverts to reactive frequency scaling. You’ll still have a hint session open, but without reported durations, the system can’t adjust CPU frequency ahead of frame demands. The session becomes a no-op from a performance standpoint.

How does GameMode API interact with ADPF hint sessions?

They operate on different layers and don’t conflict. GameMode sets a system-level performance profile configured by OEMs. Your hint session communicates per-frame CPU workload to the scheduler within whatever constraints that profile establishes. You need both: Game Mode sets the ceiling, hint sessions tell the scheduler where to operate within it.

What API level do I need for GameMode API versus PerformanceHintManager?

GameMode API requires API 31 (Android 12). PerformanceHintManager is available from API 31, but the full session feedback loop with reportActualWorkDuration works best on API 33 (Android 13) where the scheduler integration is more complete.

Can I use ADPF with the Android Game Development Kit?

Yes. The Android Game Development Kit (AGDK) includes C/C++ bindings for ADPF through the android/performance_hint.h header, making it straightforward to integrate hint sessions into native game engines without JNI overhead on every frame.

Get the companion Android Studio sample project that shows a complete hint session integrated with GameMode callbacks, ready to run on any API 31+ device. Subscribe to the agiledroid.com newsletter for follow-up guides on thermal status callbacks and adaptive frame pacing on Android 14+.