Pury — 一个新的 Android App 性能分析工具

简介: 本文讲的是Pury — 一个新的 Android App 性能分析工具,手机应用存在的目的,就是在帮助用户做他们想做的事情的同时,提供最好的用户体验 —— 而用户体验的重中之重是应用的性能。但有时候开发者们却以性能为借口,既没有达到既定目标
本文讲的是Pury — 一个新的 Android App 性能分析工具,

手机应用存在的目的,就是在帮助用户做他们想做的事情的同时,提供最好的用户体验 —— 而用户体验的重中之重是应用的性能。但有时候开发者们却以性能为借口,既没有达到既定目标,又写着低质量并难以维护的代码。在这里我想引用 Michael A. Jackson 的一句话:

“程序优化守则第一条:别去做它。程序优化守则第二条(仅限于专业人员):别去做它,现在还不是时候。”

在开始任何优化之前,我们要先认清问题的症结所在。 第一步,我们先收集和App性能表现的常规数据。比如,从调用startActivity() 到数据显示在屏幕上的时间。又比如,加载下一页 RecyclerView 的内容所需要的时间。我们把这个时间和一个可以接受的阀值进行比较就可以发现有没有什么问题需要改进了。当应用程序使用的时间比预计的要长的时候,我们就需要深入的查看并找出是哪些方法(函数)或者API(应用程序接口)出了问题。

幸运的是,我们有一些工具来分析安卓应用程序(的性能):

  1. Hugo[1] 是一个库,它提供注解驱动的方法调用日志。你只需要在你的方法上用 @DebugLog 注解,然后它就会记录参数,返回值以及运行所需的时间。我很喜欢这个库,但是这个库仅仅适用于单个方法的调用,所以并不能用来测量从调用startActivity() 到数据显示在屏幕上的时间。
  2. Android Studio 工具包,比如 System Trace,就是一个非常精确且提供很多信息的工具。但你需要花很多时间来收集、分析数据。
  3. 后端解决方案,比如 JMeter[2]。这些工具有很多功能,但需要很多时间来学习怎么使用它们。不过话说回来,你经常需要在大量并发负载下分析一个应用程序吗?这看起来并不像一个常见的情况。

缺少的工具

如果你深入思考一下关于应用程序速度的问题,可以发现其中的一大部分可以被分成两类:

  1. 某个特定方法或者 API 的调用。这类问题可以用 Hugo 一类的软件来解决。
  2. 两个不同事件之间的时间。这可能发生在独立的、但逻辑上关联的两段代码之间。Android Studio 工具包可以解决这个问题,但就像前面说过的,你需要在这上面花很多时间。

当我在搜寻可用的分析工具并思考所有的可能性的时候,我意识到至少一样工具是没有的,所以我列出了以下需求:

  1. 分析程序的开始和结束应该是两个独立触发的事件,这样我们就可以按照需求来灵活使用它了。
  2. 如果我们想要监视应用的性能表现,仅仅有开始和结束是不够的。有些时候我们想要知道中间到底发生了什么。所有关于中间过程的信息应该被汇总在一个大的报告中。这让分析和分享数据变得更加简单。
  3. 有时候,有些脚本是经常被重复调用的,比如,为 RecyclerView 加在下一页的内容。这时候,仅仅对这段脚本进行一次分析是不够的 —— 我们需要一些统计数据,比如平均、最快和最慢的时间,来进行更深入的研究。

这就是为什么我开发了 Pury 。

Pury 简介

Pury 是一个用来分析多个独立事件之间的时间的库。事件可以用注解或者调用方法来触发。一个脚本的所有事件都被汇总到一个报告中。

用 Pury 打开一个示例应用的输出:

App Start --> 0ms
  Splash Screen --> 5ms
    Splash Load Data --> 37ms
    Splash Load Data <-- 1042ms, execution = 1005ms
  Splash Screen  1043ms 
    onCreate() --> 1077ms 
    onCreate()  1101ms 
    onStart() <-- 1131ms, execution = 30ms
  Main Activity Launch <-- 1182ms, execution = 139ms
App Start <-- 1182ms

就像你看到的, Pury 测量应用启动的时间,包括中间阶段,比如在等待屏幕时加载数据和活动生命周期内的方法。每个阶段的开始和结束时间以及执行所需的时间。除了常规的分析,它也可以用来监视程序的性能,来确保一些改动不会带来意外的延迟。

某次运行结果的一个分页:

Get Next Page --> 0ms
  Load --> avg = 1.80ms, min = 1ms, max = 3ms, for 5 runs
  Load <-- avg = 258.40ms, min = 244ms, max = 278ms, for 5 runs
  Process --> avg = 261.00ms, min = 245ms, max = 280ms, for 5 runs
  Process <-- avg = 114.20ms, min = 99ms, max = 129ms, for 5 runs
Get Next Page <-- avg = 378.80ms, min = 353ms, max = 411ms, for 5 runs

在这个例子中,你可以看到, Pury 收集了加载下一页 5 次的信息,并输出了平均值。 Pury 记录并显示了每次开始和结束的时间,以及运行的时间。

内部结构及不足

在深入介绍文档之前,我想简单介绍一下 Pury 的内部结构以及它的不足。这会帮助(你们)了解方法的参数以及报错的信息。

性能测试都是由 Profiler 来完成的。每个 Profiler 都包含了一个 Runs 列表。多个 Profilers 可以并行运行,但每个 Profiler 只能同时运行一个 Run 。当一个 Profiler 内所有的 Run 都运行完成时,就会有一个报告自动生成。 Runs 的数量由 runsCounter 参数来决定。

[//]:<>

两个并列运行的 Profilers。第一个只有一个 Run 并且处于活跃的 stage 中。第二个有一个停止的 Run 和一个活跃的 Run,每个Run 都包含了一个含有两个 nested stage 的 root stage。活跃的 stage 是绿色的,停止 stage 是红色的。

Run 内部有一个 root state (根状态)。每个状态都有一个名字,一个序列号和一个不限定数量的、嵌套的 nested stage (子状态)。每个 stage 只能有一个活跃的 nested stage 。如果你停止了一个 parent stage (父状态),那么所有这个状态的 nested stage 也会停止。

使用 Pury

就像之前提到的, Pury 测量多个独立事件之间的时间。事件可以由注解或调用方法来触发。以下是三个基本的注解:

1. StartProfiling — 触发一个事件来启动 Stage 或者 Run. 分析会在方法运行之前就开始。

@StartProfiling(profilerName = "List pagination", runsCounter = 3, stageName = "Loading", stageOrder = 0)
  private void loadNextPage() { }

StartProfiling 可以接受最多 5 个参数:

  • profilerName — 分析者的名字将和标识 Profiler 的 runsCounter 一起显示在结果中。
  • runsCounter — Profiler 等待执行的任务的数量。结果只会在所有任务都完成只会才会显示。
  • stageName — 用来标记一个即将执行的状态。名字会显示在结果中。
  • stageOrder — 显示状态顺序。新开始的状态的序号必须大于嵌套最内层活跃状态的序号。同时,第一个状态的序号必须是 0。
  • enabled — 当这个变量的值为“否”时,注解将被略过。

我想强调一点。 Profiler 是由 profilerName 和 runsCounter 组合在一起进行识别的。如果你使用了相同的 profilerName , 但是不同的 runsCounter ,你将会得到两份独立的、不同的报告, 而不是一个。

2. StopProfiling — 触发一个事件来停止 Stage 或 Run. 分析会在方法运行结束后停止。当 Stage 或 Run 停止了,所有 nested stage 都会停止。

@StopProfiling(profilerName = "List pagination", runsCounter = 3, stageName = "Loading")
  private void displayNextPage() { }

它有和 StartProfiling 相同的参数,除了 stageOrder 。

3. MethodProfiling — StartProfiling 和 StopProfiling 的结合。

@MethodProfiling(profilerName = "List pagination", runsCounter = 3, stageName = "Process", stageOrder = 1)
  private List processNextPage() { }

除了一个小地方需要注意之外,它有和 StartProfiling 相同的参数。 如果 stageName 是空的,那么它将会有方法的名字和类中产生。这么做是为了在不输入参数的情况下使用 MethodProfiling 并得到一个有意义的结果。

因为 Java 7 并不支持可重复的注解,我为以上的注解写了一个注解集:

@StartProfilings(StartProfiling[] value)

@StopProfilings(StopProfiling[] value)

@MethodProfilings(MethodProfiling[] value)

就像之前提到的,你可以直接调用一个方法来开始或结束分析:

Pury.startProfiling();

Pury.stopProfiling();

参数和对应的注解是完全相同的 —— 当然,除了 enabled 。

记录结果

Pury 使用默认的记录器,但同时也允许你设置你自己喜欢的记录器。你要做的就是实现 Logger 端口并在 Pury.setLogger() 中进行设置。

public interface Logger {
    void result(String tag, String message);
    void warning(String tag, String message);
    void error(String tag, String message);
}

在默认情况下, result 被记录在 Log.d 中, warning 被记录在 Log.w 中, error 被记录在 Log.e 中。

怎样开始使用 Pury?

要开始使用 Pury, 你只需要做两个简单的步骤。 第一,使用 AspectJ 插件, 市面上有不止一种这样的插件。我使用的是WeaverLite[3], Pury 也使用这个插件。它非常轻便且易于使用。

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.nikitakozlov:weaverlite:1.0.0'
    }
}
apply plugin: 'com.nikitakozlov.weaverlite'

你可以在调试或发布版本中使用/禁用它。默认设置如下:

weaverLite {
    enabledForDebug = true
    enabledForRelease = false
}

第二,包括以下依赖:

dependencies {
   compile 'com.nikitakozlov.pury:annotations:1.0.1'
   debugCompile 'com.nikitakozlov.pury:pury:1.0.2'
}

如果你想在发布的时候分析, 在第二个依赖中使用 compile 来代替 compileDebug 。

小建议

在没有设置一些常数的时候,管理多于5个状态是非常浪费时间的,所有我总是创建一个类,将某个分析情境需要用到的所有东西都集中在这个类里。就像这样:

public final class StartApp {
    public static final String PROFILER_NAME = "App Start";
    public static final String TOP_STAGE ="App Start";
    public static final int TOP_STAGE_ORDER = 0;
    public static final String SPLASH_SCREEN = "Splash Screen";
    public static final int SPLASH_SCREEN_ORDER = TOP_STAGE_ORDER + 1;
    public static final String MAIN_ACTIVITY_LAUNCH = "Main Activity Launch";
    public static final int MAIN_ACTIVITY_LAUNCH_ORDER = SPLASH_SCREEN_ORDER + 1;
    public static final String MAIN_ACTIVITY_CREATE = "onCreate()";
    public static final int MAIN_ACTIVITY_CREATE_ORDER = MAIN_ACTIVITY_LAUNCH_ORDER + 1;
}

就像你所看到的,每个 ORDER 常数都是基于 parent stage,这样非常的方便。你还可以给 runsCounter 添加一些常数来保证你每次用的都一样。你可以添加一个 enabled 标记来轻松的禁用某个特定情境。

结论

Pury 是一个简洁的分析工具,它仅有三个注解需以及一点它们背后逻辑要学习。我希望你们不要把它想象的过分复杂。如果有什么问题的话,你们可以在这里我的 GitHub[4] 里找到例子。

我很希望收到你们关于这个解决方案的看法。如果你们有任何的建议,欢迎在 GitHub[5] 上创建一个 issue。你也可以通过Gitter[6] 来联系我。





原文发布时间为:2016年10月14日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
22天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【4月更文挑战第3天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin的兴起,其在Android开发中的地位逐渐上升,但关于其与Java在性能方面的对比,尚无明确共识。本文通过深入分析并结合实际测试数据,探讨了Kotlin与Java在Android平台上的性能表现,揭示了在不同场景下两者的差异及其对应用性能的潜在影响,为开发者在选择编程语言时提供参考依据。
|
27天前
|
缓存 监控 Java
构建高效Android应用:从优化用户体验到提升性能
在竞争激烈的移动应用市场中,为用户提供流畅和高效的体验是至关重要的。本文深入探讨了如何通过多种技术手段来优化Android应用的性能,包括UI响应性、内存管理和多线程处理。同时,我们还将讨论如何利用最新的Android框架和工具来诊断和解决性能瓶颈。通过实例分析和最佳实践,读者将能够理解并实施必要的优化策略,以确保他们的应用在保持响应迅速的同时,还能够有效地利用系统资源。
|
1月前
|
缓存 移动开发 Android开发
提升安卓应用性能的实用策略
在移动开发领域,应用的性能优化是一个持续的挑战。对于安卓开发者而言,确保应用流畅、快速并且电池使用效率高,是吸引和保持用户的关键因素之一。本文将深入探讨针对安卓平台的性能优化技巧,包括内存管理、代码效率、UI渲染以及电池寿命等方面的考量。这些策略旨在帮助开发者构建出更高效、响应更快且用户体验更佳的安卓应用。
|
1月前
|
数据库 Android开发 UED
提升安卓应用性能的十大技巧
【2月更文挑战第30天】在移动设备上,应用程序的性能直接影响用户体验。本文将分享10个优化安卓应用性能的技巧,包括代码优化、内存管理、UI设计和使用性能分析工具等,帮助开发者提高应用的运行速度和响应时间,从而提升用户满意度。
|
1月前
|
Java 编译器 Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【2月更文挑战第30天】 随着Kotlin成为开发Android应用的首选语言,开发者社区对于其性能表现持续关注。本文通过深入分析与基准测试,探讨Kotlin与Java在Android平台上的性能差异,揭示两种语言在编译效率、运行时性能和内存消耗方面的具体表现,并提供优化建议。我们的目标是为Android开发者提供科学依据,帮助他们在项目实践中做出明智的编程语言选择。
|
1月前
|
监控 测试技术 Android开发
提升安卓应用性能的实用策略
【2月更文挑战第24天】 在竞争激烈的应用市场中,性能优化是提高用户体验和应用成功的关键。本文将探讨针对安卓平台的性能优化技巧,包括内存管理、多线程处理和UI渲染效率的提升。我们的目标是为开发者提供一套实用的工具和方法,以诊断和解决性能瓶颈,确保应用流畅运行。
|
1月前
|
安全 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【2月更文挑战第24天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin在Android开发中的普及,了解其与Java在性能方面的差异变得尤为重要。本文通过深入分析和对比两种语言的运行效率、启动时间、内存消耗等关键指标,揭示了Kotlin在实际项目中可能带来的性能影响,并提供了针对性的优化建议。
31 0
|
3天前
|
Android开发 芯片 开发者
Android MediaTek bootloader 的序列号长度 & 移除非字母和数字限制 SN-Writer工具支持写入
Android MediaTek bootloader 的序列号长度 & 移除非字母和数字限制 SN-Writer工具支持写入
10 0
|
1月前
|
数据可视化 关系型数据库 编译器
【C/C++ 单线程性能分析工具 Gprof】 GNU的C/C++ 性能分析工具 Gprof 使用全面指南
【C/C++ 单线程性能分析工具 Gprof】 GNU的C/C++ 性能分析工具 Gprof 使用全面指南
109 2
|
3天前
|
Web App开发 JavaScript 前端开发
JavaScript中的性能优化:代码优化技巧与性能分析工具
【4月更文挑战第22天】本文探讨JavaScript性能优化,包括代码优化技巧和性能分析工具。建议避免全局查找、减少DOM操作、使用事件委托、优化循环和异步编程以提升代码效率。推荐使用Chrome DevTools、Lighthouse和jsPerf等工具进行性能检测和优化。持续学习和实践是提升JavaScript应用性能的关键。