Android 应用基准分析

先推荐一个作者的开源项目 最快的Json解析方式 参考
benchmark数据参考

应用基准分析 是衡量时间维度的框架,是App界的鲁大师跑分,常用于耗时判断,冷启动,热启动,框架对比 预热对比等方面

开局一张图 下面再编

Microbenchmark(微观基准测试)


集成方式

第一步创建一个benchmark module

解析工程结构

我们创建好module之后 会为我们创建一个case模板,是基于AndroidJunit4的

/**
 * Benchmark, which will execute on an Android device.
 *
 * The body of [BenchmarkRule.measureRepeated] is measured in a loop, and Studio will
 * output the result. Modify your code to see how it affects performance.
 */
@RunWith(AndroidJUnit4::class)
class ExampleBenchmark {

    @get:Rule
    val benchmarkRule = BenchmarkRule()

    @Test
    fun log() {
        benchmarkRule.measureRepeated {
            Log.d("LogBenchmark", "the cost of writing this log method will be measured")
        }
    }
}

主角对象BenchmarkRule对象 下面再讲 这里放过,再看看项目的依赖情况

最主要的配置就是 

benchmark-junit4

这个要测试就必须 停掉调试功能,这个项目也是默认模板给我们配置好的

<application
    android:debuggable="false"
    tools:ignore="HardcodedDebugMode"
    tools:replace="android:debuggable"/>

配置错误

基准库会检测以下条件是否得到满足,确保项目和环境设置达到发布性能:

  • Debuggable 已设为 false
  • 正在使用的是物理设备,而不是模拟器。
  • 如果设备启用了 root 权限,时钟已被锁定。
  • 设备的电池电量充足。

如果上述任一项检查失败,基准将抛出错误以避免不准确的测量结果。

如需抑制这些显示为警告的错误,同时阻止它们抛出错误并中止基准,请将您需抑制的错误类型以逗号分隔列表的形式传递给插桩参数 androidx.benchmark.suppressErrors

testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'EMULATOR,LOW_BATTERY,DEBUGGABLE'

一切准备妥当,下面就执行helloworld

Hello World

运行case 

这里我们就可以从时间维度上得到衡量标准, 以及简单的对象开闭数量上做出评判

那这里就有疑问了,和我们普通的

SystemClock.elapsedRealtimeNanos()

做减法有什么区别?

这里就要了解BenchmarkRule的实现了

BenchmarkRule
 

public inline fun BenchmarkRule.measureRepeated(crossinline block: BenchmarkRule.Scope.() -> Unit) {
    // Note: this is an extension function to discourage calling from Java.

    // Extract members to locals, to ensure we check #applied, and we don't hit accessors
    val localState = getState()
    val localScope = scope

    while (localState.keepRunningInline()) {
        block(localScope)
    }
}

这里貌似是一个状态控制的死循环

找到getState() 

BenchmarkState()

到此我们可以看出 插桩采样的概念了

在计算次数 用了多种模式和参数共同计算出最大次数

次数的区间 在[1,1000000] 百万级别,这里就没必要深究了

Scope概念

public 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)
         * }
         * ```
         */
        public inline fun <T> runWithTimingDisabled(block: () -> T): T {
            getOuterState().pauseTiming()
            // Note: we only bother with tracing for the runWithTimingDisabled function for
            // Kotlin callers, as it's more difficult to corrupt the trace with incorrectly
            // paired BenchmarkState pause/resume calls
            val ret: T = try {
                // TODO: use `trace() {}` instead of this manual try/finally,
                //  once the block parameter is marked crossinline.
                Trace.beginSection("runWithTimingDisabled")
                block()
            } finally {
                Trace.endSection()
            }
            getOuterState().resumeTiming()
            return ret
        }

        /**
         * Allows the inline function [runWithTimingDisabled] to be called outside of this scope.
         */
        @PublishedApi
        internal fun getOuterState(): BenchmarkState {
            return getState()
        }
    }

这个scope 是 benchmarkRule.measureRepeated dsl回调block参数,
有一个重要的方法就是runWithTimingDisabled 其逻辑为抛弃这个包裹内部计算时间

常用于隔离初始化对象 带来的时间消耗,比如gson 初始化较慢,那么我们为了测试预热之后的时间,可以如下操作
 

@Test
    fun testGson() {
        benchmarkRule.measureRepeated {
            val source = runWithTimingDisabled {
                resource.readText()
            }
            lateinit var gson: Gson
            runWithTimingDisabled {
                gson = Gson()
            }
            val result = gson.fromJson(source, KRResponse::class.java)
            println("gson $resourceName size: ${result?.users?.size}")
        }
    }

Macrobenchmark(宏观基准测试)

关于 和microBenchmark的区别
Macrobenchmark Macrobenchmark 库可衡量更大规模的最终用户互动,例如启动、与界面交互和动画。此库可让您直接控制受测试的性能环境。借助它,您还可以通过控制编译、启动和停止应用来直接衡量实际的应用启动或滚动。 Macrobenchmark 库可在外部从您通过测试构建的测试应用注入事件并监控结果。因此,您在编写基准测试时,不要直接调用应用代码,而要像用户那样在应用中导航。 Microbenchmark 借助 Microbenchmark 库,您可以直接在一个循环中对应用代码进行基准测试。该库旨在衡量 CPU 工作情况,衡量结果将用于评估最佳情况下的性能(例如,即时 [JIT] 编译已预热,磁盘访问已缓存),使用内部循环或特定热函数时就可能获得这种最佳性能。此外,该库只能衡量您可以直接单独调用的代码。 如果您的应用需要处理复杂的数据结构,或者采用了一些会在应用运行期间进行多次调用的特定计算密集型算法,就可能适合进行基准测试。您还可以衡量界面的各个部分。例如,您可以衡量绑定一个 RecyclerView 项目的成本有多高、膨胀布局需要花多长时间,以及从性能的角度来看,View 类的“布局和衡量”遍历的要求有多高。 不过,您无法衡量进行基准测试的用例对整体用户体验所产生的影响。在某些情况下,您无法通过基准测试判断卡顿或应用启动时间等瓶颈是否得到了改善。因此,首先使用 Android 性能分析器找出这类瓶颈至关重要。找到要调查和优化的代码后,您便可以快速且更轻松地反复运行会进行基准测试的循环,并生成噪声较少的结果,让您能够专注于需要改进的方面。 Microbenchmark 库只能报告应用的相关信息,而非整个系统的相关信息。因此,它最适合用来分析应用在特定情况下的性能,而不是分析系统的整体性能问题。

Hello World

和微观基准测试module配置差别
1.依赖区别 是 androidx.benchmark:benchmark-macro-junit4:1.1.1

2.不再是将所有名为 testImplementation 或 androidTestImplementation 而是 implementation

3,.增加指向的实体运行app 

targetProjectPath = ":app"
experimentalProperties["android.experimental.self-instrumenting"] = true

4. 不再是BenchmarkRule对象 而是 MacrobenchmarkRule

生成的test 类如下:

@RunWith(AndroidJUnit4::class)
class ExampleStartupBenchmark {
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "com.xxf.benchmarkdemo",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) {
        pressHome()
        startActivityAndWait()
    }
}

这里模板代码场景是 从桌面图标点击到指定包的第一个activity 的采样基准测试

运行报错 这里抑制警告,添加到testInstrumentationRunner同级

testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'EMULATOR,LOW_BATTERY,DEBUGGABLE,NOT-PROFILEABLE'

运行起来 你就会发现 手机一直在启动第一个activity

MacrobenchmarkRule

测试入口方法

参数解释
Params: packageName - 正在测量的应用的包名称。

metrics - 要衡量的指标列表。

compilationMode - 捕获测量之前使用的编译模式,例如 CompilationMode.Partial,默认为 CompilationMode.DEFAULT。

startupMode - 可选模式,用于强制使用 MacrobenchmarkScope.startActivityAndWait(和类似变体)执行的应用启动为指定类型。例如,COLD 启动会在 measureBlock 之前终止进程,以确保启动将完成完整的进程创建。通常,对于非启动基准测试,保留为 null

iterations - measureBlock 在测量期间运行的次数。请注意,由于 compilationMode 需要预热迭代,因此总迭代计数可能不匹配。

setupBlock - 在 measureBlock 之前每次迭代执行应用操作的块。例如,导航到将测量滚动的 UI。

measureBlock - 执行应用操作以对每次迭代进行基准测试的块。

MacroBenchmarkScope

MacroBenchMarkScrope提供了几个启动activity 的方式 还包括pressHome 和killProcess

官方关于更多测试示意 https://github.com/android/performance-samples.git

相关推荐

  1. Android 应用程序 ANR 问题分析总结

    2023-12-21 01:46:03       6 阅读
  2. Android Jetpack:概述、优劣分析及其应用场景探索

    2023-12-21 01:46:03       23 阅读
  3. Android OpenCV 概述、优缺点及应用场景分析

    2023-12-21 01:46:03       14 阅读
  4. Android基础-应用的启动流程

    2023-12-21 01:46:03       6 阅读
  5. Android基础-消息分发机制

    2023-12-21 01:46:03       8 阅读
  6. Android基础-事件分发机制

    2023-12-21 01:46:03       9 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-21 01:46:03       17 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-21 01:46:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-21 01:46:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-21 01:46:03       18 阅读

热门阅读

  1. TensorFlow 入门:Hello TensorFlow 编程

    2023-12-21 01:46:03       39 阅读
  2. Git初始

    Git初始

    2023-12-21 01:46:03      36 阅读
  3. 在GBASE南大通用ADO.NET 中调用一个存储过程

    2023-12-21 01:46:03       34 阅读
  4. C 语言运算符详解

    2023-12-21 01:46:03       35 阅读
  5. 解决mp4视频无法拖动进度条的问题

    2023-12-21 01:46:03       108 阅读
  6. git基本命令

    2023-12-21 01:46:03       49 阅读