一、简介
谷歌在 I/O 2019 发布了 Benchmark 库的第一个 alpha 版。Jetpack Benchmark 是一个运行在 Android 设备上的标准 JUnit 插桩测试 (instrumentation tests),它使用 Benchmark 库提供的一套规则进行测量和报告。
Jetpack Benchmark 可以在 Android Studio 中快速对 Kotlin 或 Java 代码进行基准化分析。该库会处理预热,衡量代码性能,并将基准化分析结果输出到 Android Studio 控制台,完成功能的测试与代码性能分析。
二、使用示例
1.添加依赖到模块的 build.gradle
文件中:
project_root/module_dir/build.gradle
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'junit:junit:4.12'
androidTestImplementation "androidx.benchmark:benchmark-junit4:$benchmark_version"
...
}
2.在 androidTest 目录下的清单文件中,停用调试功能。如下所示:
project_root/module_dir/src/androidTest/AndroidManifest.xml
<!--
Important: disable debugging for accurate performance results
In a com.android.library project, this flag must be disabled from this
manifest, as it is not possible to override this flag from Gradle.
-->
<application
android:debuggable="false"
tools:ignore="HardcodedDebugMode"
tools:replace="android:debuggable" />
3.在 androidTest
目录下的测试类中,添加 BenchmarkRule 实现:
- Kotlin实现
@get:Rule
val benchmarkRule = BenchmarkRule();
@Test
fun myBenchmark() {
...
benchmarkRule.measureRepeated {
doSomeWork()
}
...
}
- Java实现
@Rule
public BenchmarkRule benchmarkRule = new BenchmarkRule();
@Test
public void myBenchmark() {
...
BenchmarkState state = benchmarkRule.getState();
while (state.keepRunning()) {
doSomeWork();
}
...
}
4.时钟稳定性
(1) 添加插件到工程的 build.gradle
文件中:
project_root/build.gradle
buildscript {
...
dependencies {
...
classpath "androidx.benchmark:benchmark-gradle-plugin:$benchmark_version"
}
}
(2) 对于要进行分析的模块,在 build.gradle
文件中应用该插件:
project_root/module_dir/build.gradle
apply plugin: com.android.app
apply plugin: androidx.benchmark
...
- 最小化干扰
启动一个全屏的、不透明的 Activity,以确保其在前台运行,避免其它 BenchmarkRunner 的干扰。当然,也可通过 ActivityTestRule 或者 ActivityScenario 启动其它的 Activity。当在所有 @Test 方法执行之前,没有其它 BenchmarkRunner 运行时,该 Activity 会被重新启动。
- CPU锁频
需要 root 权限,确保CPU频率不会过高导致设备过热,也不会过低导致不能充分利用 CPU。
5.抑制错误
正常检测的基本条件:
-
设备为真机
-
设备电量充足
-
设备已被root
-
设备CPU已被锁频
-
Debuggable
已设为false
如果上述任一项检查失败,Benchmark将抛出错误,并中止分析过程和结果的输出。如果想继续执行检测过程,可以将要抑制的错误类型以逗号分隔列表的形式,传递给插桩参数 androidx.benchmark.suppressErrors,但会
在测试名称前附加上 DEBUGGABLE_
:
android {
defaultConfig {
// Set this argument to capture profiling information, instead of measuring performance.
// Can be one of:
// * None
// * Sampled
// * Method
// See full descriptions of available options at: d.android.com/benchmark#profiling
testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'none'
// Enable measuring on an emulator, or devices with low battery
testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'EMULATOR,LOW_BATTERY,DEBUGGABLE'
}
}
6.运行输出
- AS方式
project_root/module_dir/src/androidTest,然后按 Control+Shift+F10 快捷键。或者 project_root/module_dir/src/androidTest/java,鼠标右键, 选择相应的操作。
- Gradle方式
./gradlew benchmark:connectedCheck
7.生成报告
- 保存到设备
androidx.benchmark
插件默认会启用 JSON 输出。默认情况下,JSON 报告会写入设备上磁盘的测试 APK 的外部共享“下载内容”文件夹中,该文件夹通常位于以下位置:
/storage/emulated/0/Download/app_id-benchmarkData.json
- 拷贝到主机
Android Gradle Plugin 3.6 及更高版本,需要将以下属性添加到项目的 gradle.properties 文件中:
android.enableAdditionalTestOutput=true
如果 API 级别是 29 或更高级别,使用 Android Gradle Plugin 3.5 或更低版本,需要在 androidTest 目录下的清单文件中,添加如下属性:
<manifest ... >
<!-- This attribute is "false" by default on apps targeting Android 10. -->
<application
...
android:requestLegacyExternalStorage="true">
...
</application>
</manifest>
三、使用建议
基准化分析对应用中频繁执行的 CPU 工作最有用。例如,RecyclerView
滚动、数据转换/处理以及反复使用的代码。由于基准是循环运行的,所以那些不经常运行的代码,或调用时以不同方式执行的代码,都不太适合进行基准化分析。
1. 缓存
尽量避免只衡量缓存。例如,自定义 View 的布局基准可能只能衡量布局缓存的性能,建议在每个循环中传递不同的布局参数。
2.不经常运行的代码
Android 运行时 (ART) 不太可能对只会在应用启动期间运行一次的代码进行 JIT 编译。因此在循环运行此代码时,建议在应用中对其进行跟踪或性能剖析。
四、原理简析
1.代码结构
2.设计思路
(1) 如何对CPU进行锁频?
通过benchmark-gradle-plugin中的lockClocks.sh脚本:
CPU_TARGET_FREQ_PERCENT=50
GPU_TARGET_FREQ_PERCENT=50
if [ "`command -v getprop`" == "" ]; then
if [ -n "`command -v adb`" ]; then
echo ""
echo "Pushing $0 and running it on device..."
dest=/data/local/tmp/`basename $0`
adb push $0 ${dest}
adb shell ${dest}
adb shell rm ${dest}
exit
else
echo "Could not find adb. Options are:"
echo " 1. Ensure adb is on your \$PATH"
echo " 2. Use './gradlew lockClocks'"
echo " 3. Manually adb push this script to your device, and run it there"
exit -1
fi
fi
# require root
if [[ `id` != "uid=0"* ]]; then
echo "Not running as root, cannot lock clocks, aborting"
exit -1
fi
DEVICE=`getprop ro.product.device`
MODEL=`getprop ro.product.model`
# Find CPU max frequency, and lock big cores to an available frequency
# that's >= $CPU_TARGET_FREQ_PERCENT% of max. Disable other cores.
function_lock_cpu() {
CPU_BASE=/sys/devices/system/cpu
GOV=cpufreq/scaling_governor
# Find max CPU freq, and associated list of available freqs
cpuMaxFreq=0
cpuAvailFreqCmpr=0
cpuAvailFreq=0
enableIndices=''
disableIndices=''
cpu=0
while [ -f ${CPU_BASE}/cpu${cpu}/online ]; do
# enable core, so we can find its frequencies
echo 1 > ${CPU_BASE}/cpu${cpu}/online
# set userspace governor on all CPUs to ensure freq scaling is disabled
echo userspace > ${CPU_BASE}/cpu${cpu}/${GOV}
maxFreq=`cat ${CPU_BASE}/cpu$cpu/cpufreq/cpuinfo_max_freq`
availFreq=`cat ${CPU_BASE}/cpu$cpu/cpufreq/scaling_available_frequencies`
availFreqCmpr=${availFreq// /-}
if [ ${maxFreq} -gt ${cpuMaxFreq} ]; then
# new highest max freq, look for cpus with same max freq and same avail freq list
cpuMaxFreq=${maxFreq}
cpuAvailFreq=${availFreq}
cpuAvailFreqCmpr=${availFreqCmpr}
if [ -z ${disableIndices} ]; then
disableIndices="$enableIndices"
else
disableIndices="$disableIndices $enableIndices"
fi
enableIndices=${cpu}
elif [ ${maxFreq} == ${cpuMaxFreq} ] && [ ${availFreqCmpr} == ${cpuAvailFreqCmpr} ]; then
enableIndices="$enableIndices $cpu"
else
disableIndices="$disableIndices $cpu"
fi
cpu=$(($cpu + 1))
done
# Chose a frequency to lock to that's >= $CPU_TARGET_FREQ_PERCENT% of max
# (below, 100M = 1K for KHz->MHz * 100 for %)
TARGET_FREQ_MHZ=$(( (${cpuMaxFreq} * ${CPU_TARGET_FREQ_PERCENT}) / 100000 ))
chosenFreq=0
for freq in ${cpuAvailFreq}; do
freqMhz=$(( ${freq} / 1000 ))
if [ ${freqMhz} -ge ${TARGET_FREQ_MHZ} ]; then
chosenFreq=${freq}
break
fi
done
# enable 'big' CPUs
for cpu in ${enableIndices}; do
freq=${CPU_BASE}/cpu$cpu/cpufreq
echo 1 > ${CPU_BASE}/cpu${cpu}/online
echo ${chosenFreq} > ${freq}/scaling_max_freq
echo ${chosenFreq} > ${freq}/scaling_min_freq
echo ${chosenFreq} > ${freq}/scaling_setspeed
# validate setting the freq worked
obsCur=`cat ${freq}/scaling_cur_freq`
obsMin=`cat ${freq}/scaling_min_freq`
obsMax=`cat ${freq}/scaling_max_freq`
if [ obsCur -ne ${chosenFreq} ] || [ obsMin -ne ${chosenFreq} ] || [ obsMax -ne ${chosenFreq} ]; then
echo "Failed to set CPU$cpu to $chosenFreq Hz! Aborting..."
echo "scaling_cur_freq = $obsCur"
echo "scaling_min_freq = $obsMin"
echo "scaling_max_freq = $obsMax"
exit -1
fi
done
# disable other CPUs (Note: important to enable big cores first!)
for cpu in ${disableIndices}; do
echo 0 > ${CPU_BASE}/cpu${cpu}/online
done
echo "=================================="
echo "Locked CPUs ${enableIndices// /,} to $chosenFreq / $maxFreq KHz"
echo "Disabled CPUs ${disableIndices// /,}"
echo "=================================="
}
# If we have a Qualcomm GPU, find its max frequency, and lock to
# an available frequency that's >= GPU_TARGET_FREQ_PERCENT% of max.
function_lock_gpu_kgsl() {
if [ ! -d /sys/class/kgsl/kgsl-3d0/ ]; then
# not kgsl, abort
echo "Currently don't support locking GPU clocks of $MODEL ($DEVICE)"
return -1
fi
if [ ${DEVICE} == "walleye" ] || [ ${DEVICE} == "taimen" ]; then
# Workaround crash
echo "Unable to lock GPU clocks of $MODEL ($DEVICE)"
return -1
fi
GPU_BASE=/sys/class/kgsl/kgsl-3d0
gpuMaxFreq=0
gpuAvailFreq=`cat $GPU_BASE/devfreq/available_frequencies`
for freq in ${gpuAvailFreq}; do
if [ ${freq} -gt ${gpuMaxFreq} ]; then
gpuMaxFreq=${freq}
fi
done
# (below, 100M = 1M for MHz * 100 for %)
TARGET_FREQ_MHZ=$(( (${gpuMaxFreq} * ${GPU_TARGET_FREQ_PERCENT}) / 100000000 ))
chosenFreq=${gpuMaxFreq}
index=0
chosenIndex=0
for freq in ${gpuAvailFreq}; do
freqMhz=$(( ${freq} / 1000000 ))
if [ ${freqMhz} -ge ${TARGET_FREQ_MHZ} ] && [ ${chosenFreq} -ge ${freq} ]; then
# note avail freq are generally in reverse order, so we don't break out of this loop
chosenFreq=${freq}
chosenIndex=${index}
fi
index=$(($index + 1))
done
lastIndex=$(($index - 1))
firstFreq=`function_cut_first_from_space_seperated_list $gpuAvailFreq`
if [ ${gpuMaxFreq} != ${firstFreq} ]; then
# pwrlevel is index of desired freq among available frequencies, from highest to lowest.
# If gpuAvailFreq appears to be in-order, reverse the index
chosenIndex=$(($lastIndex - $chosenIndex))
fi
echo 0 > ${GPU_BASE}/bus_split
echo 1 > ${GPU_BASE}/force_clk_on
echo 10000 > ${GPU_BASE}/idle_timer
echo performance > ${GPU_BASE}/devfreq/governor
# NOTE: we store in min/max twice, because we don't know if we're increasing
# or decreasing, and it's invalid to try and set min > max, or max < min
echo ${chosenFreq} > ${GPU_BASE}/devfreq/min_freq
echo ${chosenFreq} > ${GPU_BASE}/devfreq/max_freq
echo ${chosenFreq} > ${GPU_BASE}/devfreq/min_freq
echo ${chosenFreq} > ${GPU_BASE}/devfreq/max_freq
echo ${chosenIndex} > ${GPU_BASE}/min_pwrlevel
echo ${chosenIndex} > ${GPU_BASE}/max_pwrlevel
echo ${chosenIndex} > ${GPU_BASE}/min_pwrlevel
echo ${chosenIndex} > ${GPU_BASE}/max_pwrlevel
obsCur=`cat ${GPU_BASE}/devfreq/cur_freq`
obsMin=`cat ${GPU_BASE}/devfreq/min_freq`
obsMax=`cat ${GPU_BASE}/devfreq/max_freq`
if [ obsCur -ne ${chosenFreq} ] || [ obsMin -ne ${chosenFreq} ] || [ obsMax -ne ${chosenFreq} ]; then
echo "Failed to set GPU to $chosenFreq Hz! Aborting..."
echo "cur_freq = $obsCur"
echo "min_freq = $obsMin"
echo "max_freq = $obsMax"
echo "index = $chosenIndex"
exit -1
fi
echo "Locked GPU to $chosenFreq / $gpuMaxFreq Hz"
}
# cut is not available on some devices (Nexus 5 running LRX22C).
function_cut_first_from_space_seperated_list() {
list=$1
for freq in $list; do
echo $freq
break
done
}
# kill processes that manage thermals / scaling
stop thermal-engine
stop perfd
stop vendor.thermal-engine
stop vendor.perfd
setprop vendor.powerhal.init 0
setprop ctl.interface_restart android.hardware.power@1.0::IPower/default
function_lock_cpu
function_lock_gpu_kgsl
# Memory bus - hardcoded per-device for now
if [ ${DEVICE} == "marlin" ] || [ ${DEVICE} == "sailfish" ]; then
echo 13763 > /sys/class/devfreq/soc:qcom,gpubw/max_freq
else
echo "Unable to lock memory bus of $MODEL ($DEVICE)."
fi
echo "$DEVICE clocks have been locked - to reset, reboot the device"
(2) 如何衡量代码性能的?
跟踪源码可知,BenchmarkState 类的 keepRunningInternal() 方法是检测的入口,检测过程分为 NOT_STARTED、WARMUP、RUNNING、FINISHED 四种状态,具体请仔细阅读代码中的注释。
-
NOT_STARTED:先记录检测总用时的开始时间点。如果配置了 dryRunMode 或者 startupMode 参数,调用 beginBenchmark() 方法开始检测,否则调用 beginWarmup() 方法做下 gc,记录一些时间、次数信息,将状态切换为 WARMUP。
-
WARMUP:先计算距最近一次检测的时间间隔 lastDuration,刷新最新的检测时间点。如果通过 WarmupManager 类的 onNextIteration(...) 方法判断Warmup过程已完成,则调用 beginBenchmark() 方法开始检测,记录一些时间、次数信息,将状态切换为 RUNNING。
-
RUNNING:如果达到了 WarmupManager 类计算出的迭代次数,则调用 startNextTestRun() 方法,先保存减去 BenchmarkRule.runWithTimingDisabled(...) 的值 pausedDurationNs 后的检测结果,再判断是否执行了需要的循环次数(dryRunMode:1 、startupMode:10、默认:50),满足条件则调用 endBenchmark() 方法,将状态切换为 FINISHED。
-
FINISHED:在完成检测后,若执行到了此状态,将抛出异常。
class BenchmarkState {
// 省略部分代码...
@PublishedApi
internal fun keepRunningInternal(): Boolean {
when (state) {
NOT_STARTED -> {
if (Errors.UNSUPPRESSED_WARNING_MESSAGE != null) {
throw AssertionError(Errors.UNSUPPRESSED_WARNING_MESSAGE)
}
if (!firstBenchmark && Arguments.startupMode) {
throw AssertionError(
"Error - multiple benchmarks in startup mode. Only one " +
"benchmark may be run per 'am instrument' call, to ensure result " +
"isolation."
)
}
firstBenchmark = false
if (totalRunTimeStartNs == 0L) {
// This is the beginning of the benchmark, we remember it.
totalRunTimeStartNs = System.nanoTime()
}
if (performThrottleChecks &&
!CpuInfo.locked &&
!IsolationActivity.sustainedPerformanceModeInUse &&
!Errors.isEmulator
) {
ThrottleDetector.computeThrottleBaseline()
}
ThreadPriority.bumpCurrentThreadPriority()
if (Arguments.dryRunMode || Arguments.startupMode) {
beginBenchmark()
} else {
beginWarmup()
}
return true
}
WARMUP -> {
warmupIteration++
// Only check nanoTime on every iteration in WARMUP since we
// don't yet have a target iteration count.
val time = System.nanoTime()
val lastDuration = time - startTimeNs
startTimeNs = time
throwIfPaused() // check each loop during warmup
if (warmupManager.onNextIteration(lastDuration)) {
endTraceSection() // paired with start in beginWarmup()
beginBenchmark()
}
return true
}
RUNNING -> {
iterationsRemaining--
if (iterationsRemaining <= 0) {
throwIfPaused() // only check at end of loop to save cycles
return startNextTestRun()
}
return true
}
FINISHED -> throw IllegalStateException("The benchmark has finished.")
else -> throw IllegalStateException("The benchmark is in unknown state.")
}
}
private fun beginWarmup() {
beginTraceSection("Warmup")
// Run GC to avoid memory pressure from previous run from affecting this one.
// Note, we don't use System.gc() because it doesn't always have consistent behavior
Runtime.getRuntime().gc()
startTimeNs = System.nanoTime()
warmupIteration = 0
state = WARMUP
}
private fun beginBenchmark() {
if (ENABLE_PROFILING && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// TODO: support data dir for old platforms
val f = File(
InstrumentationRegistry.getInstrumentation().context.dataDir,
"benchprof"
)
Log.d(TAG, "Tracing to: " + f.absolutePath)
Debug.startMethodTracingSampling(f.absolutePath, 16 * 1024 * 1024, 100)
}
maxIterations = OVERRIDE_ITERATIONS ?: computeIterationsFromWarmup()
pausedDurationNs = 0
iterationsRemaining = maxIterations
repeatCount = 0
thermalThrottleSleepSeconds = 0
state = RUNNING
beginTraceSection("Benchmark")
startTimeNs = System.nanoTime()
}
private fun startNextTestRun(): Boolean {
val currentTime = System.nanoTime()
results.add((currentTime - startTimeNs - pausedDurationNs) / maxIterations)
repeatCount++
if (repeatCount >= REPEAT_COUNT) {
if (performThrottleChecks &&
throttleRemainingRetries > 0 &&
sleepIfThermalThrottled(THROTTLE_BACKOFF_S)
) {
// We've slept due to thermal throttle - retry benchmark!
throttleRemainingRetries -= 1
results.clear()
repeatCount = 0
} else {
// Benchmark finished!
endBenchmark()
return false
}
}
pausedDurationNs = 0
iterationsRemaining = maxIterations
startTimeNs = System.nanoTime()
return true
}
private fun endBenchmark() {
endTraceSection() // paired with start in beginBenchmark()
if (ENABLE_PROFILING) {
Debug.stopMethodTracing()
}
ThreadPriority.resetBumpedThread()
warmupManager.logInfo()
results.chunked(10).forEachIndexed { i, list ->
Log.d(
TAG, "Results[%2d:%2d]: %s".format(
i * 10,
(i + 1) * 10,
list.joinToString()
)
)
}
internalStats = Stats(results)
state = FINISHED
totalRunTimeNs = System.nanoTime() - totalRunTimeStartNs
}
companion object {
internal const val TAG = "Benchmark"
private const val STUDIO_OUTPUT_KEY_PREFIX = "android.studio.display."
private const val STUDIO_OUTPUT_KEY_ID = "benchmark"
private const val ENABLE_PROFILING = false
private const val NOT_STARTED = 0 // The benchmark has not started yet.
private const val WARMUP = 1 // The benchmark is warming up.
private const val RUNNING = 2 // The benchmark is running.
private const val FINISHED = 3 // The benchmark has stopped.
// Values determined empirically.
@VisibleForTesting
internal val REPEAT_COUNT = when {
Arguments.dryRunMode -> 1
Arguments.startupMode -> 10
else -> 50
}
private val OVERRIDE_ITERATIONS = when {
Arguments.dryRunMode || Arguments.startupMode -> 1
else -> null
}
private val TARGET_TEST_DURATION_NS = TimeUnit.MICROSECONDS.toNanos(500)
private const val MAX_TEST_ITERATIONS = 1000000
private const val MIN_TEST_ITERATIONS = 1
private const val THROTTLE_MAX_RETRIES = 3
private const val THROTTLE_BACKOFF_S = 90L
private var firstBenchmark = true
// 省略部分代码...
}
// 省略部分代码...
}
(3) 在检测过程中,如何忽略指定代码的性能损耗?
可阅读 BenchmarkRule 类的 runWithTimingDisabled(...) 方法的注释:
class BenchmarkRule : TestRule {
// 省略部分代码...
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY)
val scope = Scope()
/**
* Handle used for controlling timing during [measureRepeated].
*/
inner class Scope internal constructor() {
/**
* Disable timing for a block of code.
*
* Used for disabling timing for work that isn't part of the benchmark:
* - When constructing per-loop randomized inputs for operations with caching,
* - Controlling which parts of multi-stage work are measured (e.g. View measure/layout)
* - Disabling timing during per-loop verification
*
* ```
* @Test
* fun bitmapProcessing() = benchmarkRule.measureRepeated {
* val input: Bitmap = runWithTimingDisabled { constructTestBitmap() }
* processBitmap(input)
* }
* ```
*/
inline fun <T> runWithTimingDisabled(block: () -> T): T {
getOuterState().pauseTiming()
val ret = block()
getOuterState().resumeTiming()
return ret
}
/**
* Allows the inline function [runWithTimingDisabled] to be called outside of this scope.
*/
@PublishedApi
internal fun getOuterState(): BenchmarkState {
return getState()
}
}
// 省略部分代码...
}
class BenchmarkState {
// 省略部分代码...
/**
* Stops the benchmark timer.
*
* This method can be called only when the timer is running.
*
* ```
* @Test
* public void bitmapProcessing() {
* final BenchmarkState state = mBenchmarkRule.getState();
* while (state.keepRunning()) {
* state.pauseTiming();
* // disable timing while constructing test input
* Bitmap input = constructTestBitmap();
* state.resumeTiming();
*
* processBitmap(input);
* }
* }
* ```
*
* @throws [IllegalStateException] if the benchmark is already paused.
*
* @see resumeTiming
*/
fun pauseTiming() {
check(!paused) { "Unable to pause the benchmark. The benchmark has already paused." }
pausedTimeNs = System.nanoTime()
paused = true
}
/**
* Resumes the benchmark timer.
*
* This method can be called only when the timer is stopped.
*
* ```
* @Test
* public void bitmapProcessing() {
* final BenchmarkState state = mBenchmarkRule.getState();
* while (state.keepRunning()) {
* state.pauseTiming();
* // disable timing while constructing test input
* Bitmap input = constructTestBitmap();
* state.resumeTiming();
*
* processBitmap(input);
* }
* }
*
* @throws [IllegalStateException] if the benchmark is already running.
*
* ```
*
* @see pauseTiming
*/
fun resumeTiming() {
check(paused) { "Unable to resume the benchmark. The benchmark is already running." }
pausedDurationNs += System.nanoTime() - pausedTimeNs
pausedTimeNs = 0
paused = false
}
// 省略部分代码...
}
(4) 如何维持屏幕常亮,以确保结果的可靠性?
在执行 IsolationActivity类 的 onCreate(...) 方法时,注册 ActivityLifecycleCallbacks 并触发执行 onActivityCreated(...) 方法,以强制唤醒设备、保持屏幕常亮,从而确保结果的可靠性:
class IsolationActivity : android.app.Activity() {
private var destroyed = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.isolation_activity)
// disable launch animation
overridePendingTransition(0, 0)
if (firstInit) {
if (!CpuInfo.locked && isSustainedPerformanceModeSupported()) {
sustainedPerformanceModeInUse = true
}
application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
// trigger the one missed lifecycle event, from registering the callbacks late
activityLifecycleCallbacks.onActivityCreated(this, savedInstanceState)
// 省略部分代码...
firstInit = false
}
// 省略部分代码...
}
// 省略部分代码...
}
private val activityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
// 省略部分代码...
// Forcibly wake the device, and keep the screen on to prevent benchmark failures.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
val keyguardManager =
activity.getSystemService(KEYGUARD_SERVICE) as KeyguardManager
keyguardManager.requestDismissKeyguard(activity, null)
activity.setShowWhenLocked(true)
activity.setTurnScreenOn(true)
activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
@Suppress("DEPRECATION")
activity.window.addFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
)
}
}
// 省略部分代码...
}
参考:
https://developer.android.google.cn/studio/profile/benchmark
https://developer.android.google.cn/jetpack/androidx/releases/benchmark
https://zhuanlan.zhihu.com/p/158616297
https://medium.com/@iamanbansal/exploring-jetpack-benchmark-de107165bf43