Released:
Android 11 (API Level 30) - Thermal API
Android 12 (API Level 31) - NDK API
(Preview) Android 15 (DP1) - getThermalHeadroomThresholds()
The potential performance of your app is limited by the thermal state of the device, which can vary based on characteristics such as weather, recent usage, and the device's thermal design. Devices can only maintain a high level of performance for a limited amount of time before being thermally throttled. A key goal of your implementation should be to achieve performance goals without exceeding thermal limitations. Thermal API makes it possible without the need for device specific optimizations. Furthermore, when debugging performance issues, knowing if the thermal state of your device is limiting performance is important.
Game engines usually have runtime performance parameters that can adjust the workload the engine puts on the device. For example, these parameters can set the number of worker threads, worker-thread affinity for big and small cores, GPU fidelity options, and framebuffer resolutions. In Unity Engine, game developers can adjust the workload by changing the Quality Settings using the Adaptive Performance plugin. For Unreal Engine, use the Scalability Settings to adjust quality levels dynamically.
When a device approaches an unsafe thermal state, your game can avoid being throttled by decreasing the workload through these parameters. To avoid throttling, you should monitor the thermal state of the device and proactively adjust the game engine workload. Once the device overheats, the workload must drop below the sustainable performance level in order to dissipate heat. After the thermal headroom decreases to safer levels, the game can increase the quality settings again but make sure to find a sustainable quality level for optimal play time.
You can monitor the thermal state of the device by polling the
getThermalHeadroom
method. This method predicts how long the device can maintain the current
performance level without overheating. If the time is less than the amount
needed to run the workload, then your game should decrease the workload to a
sustainable level. For example, the game can shift to smaller cores, reduce the
frame rate, or lower fidelity.
Acquire Thermal Manager
In order to use Thermal API, first you'll need to acquire the Thermal Manager
C++
AThermalManager* thermal_manager = AThermal_acquireManager();
Java
PowerManager powerManager = (PowerManager)this.getSystemService(Context.POWER_SERVICE);
Forecast the Thermal Headroom x seconds ahead for more control
You can ask the system to forecast the temperature x seconds ahead with the current workload. This gives you a more fine-grained control and more time to react by reducing the workload to prevent thermal throttling from kicking in.
The result ranges from 0.0f (no throttling, THERMAL_STATUS_NONE
) to 1.0f
(heavy throttling, THERMAL_STATUS_SEVERE
).
If you have different graphics quality levels in your games, you can follow our
Thermal Headroom Guidelines.
C++
float thermal_headroom = AThermal_getThermalHeadroom(10);
ALOGI("ThermalHeadroom in 10 sec: %f", thermal_headroom);
Java
float thermalHeadroom = powerManager.getThermalHeadroom(10);
Log.d("ADPF", "ThermalHeadroom in 10 sec: " + thermalHeadroom);
Alternatively, rely on thermal status for clarification
Each device model may be designed differently. Some devices may be able to distribute heat better and thus be able to withstand higher thermal headroom before being throttled. If you want to read a simplified grouping of ranges of thermal headroom, you can check on the thermal status to make sense of the thermal headroom value on the current device.
C++
AThermalStatus thermal_status = AThermal_getCurrentThermalStatus(thermal_manager);
ALOGI("ThermalStatus is: %d", thermal_status);
Java
int thermalStatus = powerManager.getCurrentThermalStatus();
Log.d("ADPF", "ThermalStatus is: " + thermalStatus);
Get notified when thermal status changes
You can also avoid polling the thermalHeadroom
until the thermalStatus
hits
a certain level (for example: THERMAL_STATUS_LIGHT
).
To do so, you can register a callback to let the system notify you whenever the
status has changed.
C++
int result = AThermal_registerThermalStatusListener(thermal_manager, callback);
if ( result != 0 ) {
// failed, check whether you have previously registered callback that
// hasn’t been unregistered
}
Java
// PowerManager.OnThermalStatusChangedListener is an interface, thus you can
// also define a class that implements the methods
PowerManager.OnThermalStatusChangedListener listener = new
PowerManager.OnThermalStatusChangedListener() {
@Override
public void onThermalStatusChanged(int status) {
Log.d("ADPF", "ThermalStatus changed: " + status);
// check the status and flip the flag to start/stop pooling when
// applicable
}
};
powerManager.addThermalStatusListener(listener);
Remember to remove the listener when done
C++
int result = AThermal_unregisterThermalStatusListener(thermal_manager, callback);
if ( result != 0 ) {
// failed, check whether the callback has been registered previously
}
Java
powerManager.removeThermalStatusListener(listener);
Cleanup
Once you're done, you'll need to clean up the thermal_manager that you acquired. If you're using Java, the PowerManager reference can be automatically garbage collected for you. But if you're using the Java API through JNI and have retained a reference, remember to clean up the reference!
C++
AThermal_releaseManager(thermal_manager);
For a complete guide on how to implement Thermal API on a native C++ game using both the C++ API (NDK API) and Java API (through JNI), check out the Integrate Thermal API section in the Adaptability codelab section.
Thermal headroom guidelines
You can monitor the thermal state of the device by polling the
getThermalHeadroom
method. This method predicts how long the device can maintain the current
performance level before reaching THERMAL_STATUS_SEVERE
.
For example, if getThermalHeadroom(30)
returns 0.8, it indicates that in 30
seconds, the headroom is expected to reach 0.8, where there is 0.2 distance away
from severe throttling, or 1.0. If the time is less than the amount needed to
run the workload, then your game should decrease the workload to a sustainable
level. For example, the game can reduce the frame rate, lower fidelity, or
reduce network connectivity work.
Thermal statuses and meaning
- If the device is not being thermally throttled:
- Some throttling, but no significant impact on performance:
- Significant throttling that impacts performance:
Device limitations of the Thermal API
There are some known limitations or additional requirements of Thermal API, due to implementations of the thermal API on older devices. The limitations and how to work around them are as follows:
- Do not call the
GetThermalHeadroom()
API too frequently. Doing so will result in the API returning NaN. You should call it at most once per second. - If the initial value of
GetThermalHeadroom()
is NaN, the API is not available on the device - If
GetThermalHeadroom()
returns a high value (e.g: 0.85 or more) andGetCurrentThermalStatus()
still returnsTHERMAL_STATUS_NONE
, the status is likely not updated. Use heuristics to estimate the correct thermal throttling status or just usegetThermalHeadroom()
withoutgetCurrentThermalStatus()
.
Heuristics example:
- Check that Thermal API is supported.
isAPISupported()
checks the value of the first call togetThermalHeadroom
to ensure that it is not 0 or NaN and skips using the API if the first value is either 0 or NaN. - If
getCurrentThermalStatus()
returns a value other thanTHERMAL_STATUS_NONE
, the device is being thermally throttled. - If
getCurrentThermalStatus()
keeps returningTHERMAL_STATUS_NONE
, it doesn't necessarily mean the device isn't being thermally throttled. It could mean thatgetCurrentThermalStatus()
is not supported on the device. Check the return value ofgetThermalHeadroom()
to ensure the condition of the device. - If
getThermalHeadroom()
returns a value of > 1.0, the status could actually beTHERMAL_STATUS_SEVERE
or higher, reduce workload immediately and maintain lower workload untilgetThermalHeadroom()
returns lower value - If
getThermalHeadroom()
returns a value of 0.95, the status could actually beTHERMAL_STATUS_MODERATE
or higher, reduce workload immediately and keep the watchout to prevent higher reading - If
getThermalHeadroom()
returns a value of 0.85, the status could actually beTHERMAL_STATUS_LIGHT
, keep the watchout and reduce workload if possible
Pseudocode:
bool isAPISupported() {
float first_value_of_thermal_headroom = getThermalHeadroom();
if ( first_value_of_thermal_headroom == 0 ||
first_value_of_thermal_headroom == NaN ) {
// Checked the thermal Headroom API's initial return value
// it is NaN or 0,so, return false (not supported)
return false;
}
return true;
}
if (!isAPISupported()) {
// Checked the thermal Headroom API's initial return value, it is NaN or 0
// Don’t use the API
} else {
// Use thermalStatus API to check if it returns valid values.
if (getCurrentThermalStatus() > THERMAL_STATUS_NONE) {
// The device IS being thermally throttled
} else {
// The device is not being thermally throttled currently. However, it
// could also be an indicator that the ThermalStatus API may not be
// supported in the device.
// Currently this API uses predefined threshold values for thermal status
// mapping. In the future you may be able to query this directly.
float thermal_headroom = getThermalHeadroom();
if ( thermal_headroom > 1.0) {
// The device COULD be severely throttled.
} else if ( thermal_headroom > 0.95) {
// The device COULD be moderately throttled.
} else if ( thermal_headroom > 0.85) {
// The device COULD be experiencing light throttling.
}
}
}
Diagram: