如果没有适当的性能监测,你的应用程序可能会不必要地占用宝贵的资源,可能会造成收入损失,而这些损失本来是很容易避免的。虽然有很多工具和平台可用于对托管应用程序进行基准测试,但移动应用程序往往被忽略。
在本指南中,我们将介绍分析安卓应用程序的基本原理。我们将介绍在对Android应用程序进行剖析时应该注意什么,如何使用流行的工具开始,以及如何减少资源过度使用。让我们开始吧!
什么是Android剖析?
剖析是一种软件开发实践,有助于识别应用程序中的性能和资源管理瓶颈。
安卓应用程序是为了在安卓设备上运行,而这些设备的硬件资源通常是有限的。因此,你必须优化你的应用程序的资源消耗,为你的用户提供尽可能好的体验。没有安卓剖析,性能优化几乎是不可能的。
安卓剖析应该关注什么?
在对你的安卓应用进行剖析时,你可以关注多个领域,比如,内存。作为移动设备上最关键但又有限的资源之一,不恰当的内存管理会导致应用程序不响应错误(ANR)和应用程序崩溃。
处理是控制你的用户在浏览你的应用程序时的体验的东西。不恰当的管理会导致用户界面滞后、应用程序速度减慢,最糟糕的情况是完全冻结。
大多数安卓应用依靠远程服务器来提供内容和信息。不当的网络管理会给内容加载时间增加不必要的延迟,给你的用户造成不好的体验。
最后,由于所有移动设备都以某种形式的电池运行,你需要优化你的应用程序,以尽可能少地消耗电池。消耗大量电池的应用程序通常会很快被用户卸载。
如何对一个安卓应用进行剖析
你可以采取多种方法对安卓应用进行分析,但在本节中,我们将介绍三种方法。
通过开发者工具进行设备上的剖析
你可以使用每部安卓手机上提供的开发者工具来即时分析GPU性能。你首先需要做以下工作。
- 在你的手机上启用开发者选项
- 进入设置>开发者选项
- 在 "监控"部分,选择 "Profile GPU Rendering"选项
- 在弹出的对话框中,选择 "屏幕上"(On Screen As Bars)选项
- 打开你想要的应用程序的配置文件
你会注意到屏幕底部有类似下面这样的条形图。
图片来源。安卓开发者
这张图中的每个竖条都代表你的应用程序的用户界面的一个框架。竖条的高度表示设备在屏幕上渲染该帧所需的时间。该图还包含一些信息,如渲染生命周期中每个组件所花费的时间,用不同颜色的条表示。你可以在Android开发者的官方网站上了解更多信息。
安卓工作室
Android Studio是事实上的Android应用开发IDE,所以它有大量的分析能力。使用Android Studio,你可以对从内存到电池的几乎任何东西进行分析。每个指标都有一个单独的剖析部分,并提供一系列的调整和定制功能。我们将在后面的章节中对Android Studio进行更详细的介绍。
Dalvik调试监控服务器(DDMS)
如果你不使用Android Studio,或者你不满意Android所提供的设备上的剖析功能,还有另一个选择。Android SDK包含一个独立的Java应用程序,你可以用它来实时监控你的Android应用程序的性能。
这个被称为Dalvik Debug Monitor Server的剖析工具可以直接从命令行启动。DDMS作为你的应用程序和你的命令行之间的桥梁,直接连接到你手机中的虚拟机。DDMS运行应用程序,将应用程序的调试器的输出直接流向你的命令行。
DDMS是一个非常先进的工具,然而,需要注意的是,这个工具在Android Studio v3.0中被废弃了。推荐DDMS的替代品是新的Android Profiler,我们将在后面讨论。无论如何,如果你正在使用早期版本的Android Studio,或者你正在寻找手动调试Android应用程序的方法,DDMS可以派上用场。
你可以用DDMS完成很多事情,包括屏幕捕捉、端口转发、来电和短信欺骗、位置数据欺骗,以及访问Logcat、进程和其他应用信息。
开始使用基本剖析
Android Studio是一个非常详细的Android开发和调试的工具。在这一节中,我们将提供基本的见解,说明你如何用Android Studio提供的剖析工具来剖析你的Android应用的各个方面。
安卓剖析器
Android剖析器是由Android Studio提供的一套工具,用于剖析Android应用程序。你可以通过菜单栏上的查看>工具窗口>剖析器来访问它。另外,你也可以点击工具栏上的Profile图标。
当你打开Android Profiler时,它看起来像下面的代码。
有一个共享的时间线,同时对你的应用程序进行CPU、内存、网络和能源的剖析。要开始对每个资源进行详细分析,你可以点击每个单独的时间线。
请注意,要访问这些时间线,你需要将Android Profiler连接到一个正在运行的会话。要做到这一点,你需要将一个物理或虚拟的Android设备连接到你的系统,并启用调试功能,然后启动一个应用程序。Android Studio将识别正在运行的应用程序并生成其实时时间线。
内存剖析
内存剖析器是Android Studio中最经常使用的剖析工具之一。观察你的应用程序是如何利用可用内存的,这对防止内存泄漏和膨胀至关重要。
你还可以使用内存剖析器来寻找可能表明你的应用程序性能问题的内存分配模式。此外,你可以转储你的应用程序的堆,以了解哪些对象占用了你的设备的内存。相关的堆转储的集合可以帮助你找出内存泄漏的问题。
在各种类型的用户互动中记录内存分配活动,可以帮助你了解你的应用程序在哪里一次分配了太多的对象,以及你是否忘记释放内存,从而导致内存膨胀。
内存剖析部分看起来像下面的图片。
该工具为你提供了一个时间轴,显示了各种属性,如。
- 每个类别正在使用的内存,用颜色表示,即Java、本地、图形等。
- 用Y轴上的数字表示的分配对象的数量
- 用垃圾桶图标表示的垃圾收集事件
当你得到你的应用程序所做的内存分配的高层次概述时,你也可以使用中间窗格中的三个选项来确定单个内存相关的活动。
堆转储显示在记录堆转储时,哪些对象已经被创建并占用了内存。你可以了解在内存中分配的对象的类型,它们的数量,它们正在使用的内存,以及更多。
一个堆转储样本看起来就像下面这个。
如果你选择记录Java或Kotlin对象分配以进行进一步分析,该工具将显示记录的数据,如下所示。
使用搜索工具,你可以在这个列表中搜索,以确定一个类是否被分配,这在调试特定代码的行为时很有用。
当你搜索你的应用程序的名称时,它看起来像下面这样。
Android Studio为你提供了这些选项来剖析你的应用程序的内存使用情况。然而,为了最好地利用这些工具,你需要制定一个剖析策略。
我建议以固定的时间间隔记录和比较几个堆转储,以了解你的应用程序在哪里泄露了内存。此外,你应该在应用程序大量使用和少量使用时记录对象分配,以观察这个数字是否不合理地高,这可能表明你的代码中有内存管理问题。
CPU 剖析
记录你的安卓应用的CPU活动可以帮助你了解你的应用是否能很好地管理其工作负载。CPU剖析工具列出了你的应用程序的活动线程,并绘制了它们随时间变化的活动。下面是CPU剖析器工具显示结果的一个例子。
绿色的横条用来表示一个线程的CPU活动。如果该线程停止了应用程序的流程以接受输入,横杠将变为黄色,如果该线程处于睡眠状态,则为灰色。
你可以使用这些数据来识别一个线程是否使用了比它需要的更多的CPU时间。你还可以直观地看到每一帧在屏幕上的渲染时间,这将指出需要重做的活动以提高性能。
网络分析
当你的应用程序处理大量的网络交互时,网络剖析器工具就会派上用场。你可能需要确定哪个请求失败了,或者哪个端点比平时花费更多时间来服务你的请求。
通过网络分析器,你可以记录发送和接收网络请求的顺序,交换的数据,以及发生交互的网络速度。
在下面的例子中,当登录活动开始时,从Unsplash下载了一个图像文件。
蓝线表示下载速度,橙线表示上传速度。如果你使用HttpURLConnection 或okHTTP libraries 发送和接收请求,你也可以在这个时间线上查看单个请求的细节,这在调试网络响应时很有用。
电池分析
安卓分析器还内置了一个被称为能源分析器的电池使用情况分析工具,它可以直观地看到你的应用程序在一段时间内对设备电池使用的影响。你可以尝试在你的应用程序中执行重型任务,以检查它是否对设备的电池消耗有较大的影响。
在下面的例子中,应用程序在运行时间的前五秒保持唤醒锁。你可以观察到,尽管没有进行实际的重度处理,但在这段时间内,电池的使用率很高。按照这种方法,能源分析器有助于识别Android应用程序的过度能源使用。
安卓资源管理的最佳实践
虽然我们可以使用剖析来识别我们的安卓应用的问题,但从一开始就尽量减少或避免这些问题总是更好。在本节中,我们将确定一些最佳实践,以帮助你适当地管理你的应用程序的资源使用。
提示1:通过委托给后台线程来减轻UI线程的负担
Android运行时支持多线程编程。根据其架构,Android应用的UI是在主线程上渲染的,这就是为什么它被称为UI线程。
如果你试图在UI线程上执行资源密集型活动,如下载文件或处理图像,它将减少可用于UI渲染活动的处理器时间,从而使你的应用程序的UI变得滞后和缓慢。
为了避免这种情况,你应该始终为繁重的工作分配一个可以在后台安全运行的工作线程,以缓解UI线程的任何滞后或减速。Android运行时提供了多个本地库,你应该考虑在你的应用程序中使用这些库,只要适用。
提示2:避免将布局嵌套到两到三层的深处
Android UI是膨胀的,或者说是以views 和viewgroups 的层次结构呈现的。views 是你在屏幕上看到的视觉元素,如按钮、开关等,而viewgroups 是用来容纳和排列views 的容器。
正如你所猜测的那样,所有的views 和viewgroups 都会消耗运行时内存的空间,必须经过处理才能在屏幕上呈现。此外,在一个view 或viewgroup 对象上运行的处理,也会在其所有的子对象上运行。如果你的应用程序的用户界面是深度嵌套的,这就给设备增加了一个惊人的工作量,使你的用户界面变慢,并影响到用户。
为了避免这种情况,试着用最简单的层次结构来设计你的用户界面。避免使用太多的LinearLayouts ,因为这限制了你在其中安排views 的自由。相反,我更喜欢ConstraintLayout ,它可以帮助你建立复杂的UI安排,而不需要进行深度嵌套。
提示#3:尽可能多地重复使用UI元素
许多UI元素,如导航栏和侧边栏,在整个应用程序中被重复使用。许多新手开发者忽视了这一点,在需要的地方重新创建这些组件。例如,让我们假设下面的代码是我们的Title 栏。
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/app_logo" />
</FrameLayout>
虽然你可以在你的活动中直接包含Title bar,就像下面的代码片段一样,但这样做并不是关于资源管理的最佳选择。
<!-- MainActivity.xml -->
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Title bar here -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/app_logo" />
</FrameLayout>
<!-- Rest of the activity.. -->
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:padding="10dp" />
...
</LinearLayout>
相反,你应该为Title 栏的用户界面创建一个单独的XML文件,并在需要的地方将其纳入你的代码。
<!-- MainActivity.xml -->
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Title bar here -->
<include layout="@layout/title_bar" />
<!-- Rest of the activity.. -->
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:padding="10dp" />
...
</LinearLayout>
虽然这大大增加了代码的可读性,但你可以通过使用merge 标签来减少布局的不必要的父容器,从而使它更上一层楼。为了更好地理解这一点,让我们举个例子,一个包含两个TextViews 的布局。
<!-- @layout/banner.xml -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/world" />
</ LinearLayout>
如果你要将其包含在另一个布局中,你将总是有一个不必要的LinearLayout ,包裹着TextViews 。由于XML布局文件总是需要一个根父viewgroup ,你无法摆脱它,不必要地增加了UI布局中的嵌套。为了解决这个问题,你可以使用merge 标签来摆脱你的banner.xml 文件中的父LinearLayout 。
<!-- @layout/banner.xml -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/world" />
</merge>
现在,当你在你的主布局中包含这个布局时,系统会忽略merge 元素,而直接将两个TextViews 放在include标签的位置上,使你的UI布局的层次更加扁平化,并提高你的UI的性能。
Tip #4:好好利用上下文,减少不必要的内存泄漏
Android的资源是通过一个叫做Context的接口聚合和访问的。每个活动都有自己的Context,能够访问该活动生命周期中的特定资源。除了这些,安卓应用也有自己的Context,它与应用的生命周期相联系,在性质上更加全局。
这些Context被用来访问Android资源,如SharedPreferences ,设备上的数据库,等等。然而,为了避免资源泄漏,每当你在内存中创建一个资源访问对象时,你必须记得使用适当的Context。
例如,如果你使用一个活动的Context初始化一个数据库访问对象,该对象的范围将只限于该活动。如果你试图在活动之外使用它,你将不得不不必要地在内存中保留该活动的 Context。相反,你应该考虑使用应用程序的 Context 来初始化全局性的资源对象。
结论
开发Android应用程序需要创新和优化的完美平衡。与任何类型的开发一样,你需要确保你不会因为编写糟糕的代码而浪费资源。Android剖析可以帮助你识别和解决这种情况。
在本指南中,我们详细谈论了Android剖析,讨论了你可以监测Android应用程序性能的各个领域。我们还看了一些开始剖析你的应用程序的最流行的方法,以及在开发你的下一个Android应用程序时要记住的一些最佳做法。
我希望本指南能帮助你进入安卓剖析领域,并将你的安卓应用开发技能提升到新的水平。编码愉快