第 15 章 Android 性能优化
Android 设备作为一种移动设备,不管是内存还是 CPU 的性能都受到了一定的限制,无法像 PC 那样具有超大的内存和高性能的 CPU。所以 Android 程序不可能无限制的使用内存和 CPU 资源,过多的使用内存会导致程序内存溢出,即 OOM。过多的使用 CPU 资源,一般指做大量耗时任务,会导致手机卡顿程序无响应,即 ANR。
15.1 Android 的性能优化方法
布局优化、绘制优化、内存泄漏优化、响应速度优化
15.1.1 布局优化
思想就是尽量减少布局文件的层级。层级少了,Android 绘制时的工作量少了,性能自然就高了。
首先删除布局中无用的控件和层级。比较复杂的布局,比如需要嵌套才能实现的,使用 ConstraintLayout 来实现,完美消除嵌套,代替 RelativeLayout。FrameLayout 和 LinearLayout 都是一种简单高效的 ViewGroup。
布局优化的另一种手段: <include>
标签、<merge>
标签和 ViewStub。<include>
标签用于布局重用;<merge>
标签和 <include>
配合使用,可以减少布局的层级;ViewStub 提供了按需加载的功能,当需要时才会将 ViewStub 中的布局加载到内存,提高了程序的初始化效率。
<include>
标签
将一个指定的布局文件加载到当前的布局文件中
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<include layout="@layout/view_toolbar_with_textview" />
...
</LinearLayout>
通过这种方式,就可以不用再写一遍重复的布局了。
<include>
标签只支持 android:layout_
开头的属性,比如 android:layout_width、android:layout_height
;android:id
是个特例,如果 <include>
标签指定了 id,被包含的布局文件跟元素也指定了 id,以 <include>
指定的 id 为准。
注意当 <include>
标签指定了 android:layout_
开头的属性,那么要求 android:layout_width、android:layout_height
必须存在,负责其他的指定的属性无效。
<merge>
标签
一般和<include>
标签一起使用从而减少布局的层级。上边示例中当前布局是一个竖直的 LinearLayout,这是如果被包含的布局文件也是用了竖直的 LinearLayout,此时显然被包含的布局的 LinearLayout 是多余的,可以通过<merge>
标签来代替,去掉一层嵌套。- ViewStub
继承自 View,轻量级,宽高都是 0,因此它本身不参与任何的布局和绘制过程。
它的意义在于按需加载所需的布局文件。实际开发中很多布局文件正常情况下不会显示,比如加载失败的布局文件,他没必要在初始化的时候就加载进来,通过 ViewStub 可以做到在使用的时候再加载,提高了程序初始化的性能。
加载方式:通过 setVisibility(View.VISIBLE)
方法或者通过 inflate()
方法。
15.1.2 绘制优化
指 View 的 onDraw 方法要避免执行大量的操作。
- onDraw 方法中不要创建新的局部对象,因为 onDraw 方法可能会被频繁调用,这样会在一瞬间产生大量的临时对象,占用了过多的内存而且会导致系统频繁出发 gc,降低了效率。
- onDraw 方法中不要做耗时任务,也不能执行成千上万次的循环操作。大量的循环操作抢占 cpu 的时间片造成 View 绘制不流畅。
15.1.3 内存泄漏优化
最常见的内存泄漏:非静态的 Handler 对象引起内存泄漏;匿名内部类引起内存泄漏;非静态内部类引起内存泄漏。
- 非静态的 Handler 对象引起内存泄漏
可以自定义 Handler 静态内部类,如果需要用到外部类的变量或方法,就用弱引用去持有外部类,当需要调用外部类的方法的时候,从弱引用获取外部类的实例再操作。 - 匿名内部类引起内存泄漏
比如在设置一个 view 的点击事件的时候,如果直接在setOnClickListener()
方法传 listener 参数的时候,直接创建一个 listener 传进去,这种就叫匿名内部类。解决方法是,自定义 listener 静态内部类,用弱引用持有外部类实例。 - 非静态内部类引起内存泄漏
解决方法是,改为静态内部类
有些无限循环的动画也会引起内存泄漏,当要退出页面时,应当手动关闭无限循环的动画
15.1.4 响应速度优化和 ANR 日志分析
核心思想是避免在主线程中做耗时操作。
Activity 5 秒未响应屏幕触摸事件或者键盘输入事件就会出现 ANR;BroadcastReceiver 如果 10 秒之内未执行完操作也会出现 ANR。
定位 ANR 问题出现的地方。当进程发生 ANR 后,系统会在 /data/anr 目录创建文件 traces.txt(名字好像不固定),通过这个文件就可以定位哪里出现了 ANR。
发生 ANR 后,连接手机后执行 adb 命令:adb pull /data/anr .
,把 anr 目录上传到电脑,就能分析 traces.txt 文件了
15.1.6 线程优化
采用线程池
15.1.7 一些性能优化建议
- 避免创建过多对象
- 不要过多使用枚举,枚举占用的内存空间要比整型大
- 常量使用 static final 来修饰
- 使用一些 Android 特有的数据结构,如 SparseArray 和 Pair 等,它们都具有更好的性能
- 适当使用软引用和弱引用
- 采用内存缓存和磁盘缓存
- 尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏
15.2 内存泄漏分析
集成 LeakCanary,发生内存泄露后通过它可以方便地分析内存泄漏
15.3 提高程序的可维护性
Android 的程序设计思想,主旨是如何提高代码的可维护性和可扩展性。
切入点:代码风格、代码的层次性和单一职责原则、面向扩展编程以及设计模式。
可读性是代码可维护性的前提,而良好的代码风格在一定程度上可以提高程序的可读性。
良好的代码风格:
- 命名要规范,能正确的传达出变量或者方法的含义,少用缩写。变量前缀可以参考 Android 源码,比如私有成员以 m 开头,静态成员以 s 开头,常量全部用大写字母表示等等。
- 代码排版上要留出合理的空白来区分不同的代码块,同类变量的声明放在一组,两类变量之间留出一行空白作为区分。
- 仅为非常关键的代码添加注释,其他地方不写注释,这就对变量和方法的命名风格提出了很高的要求,一个合理的命名风格可以让读者阅读源码就像阅读注释一样清晰。
代码的层次性和单一职责原则
层次性是指对于一段业务逻辑,不要试图在一个方法或者一个类中去全部实现,将它分为几个子逻辑,每个子逻辑做自己的事情,这样既显得层次分明又可以分解任务实现简化逻辑的效果。
单一职责是和层次性相关联的。代码分层以后,每一层仅关注少量逻辑,这样就做到了单一职责。
面向扩展编程
程序的扩展性标志着开发人员是否有足够的经验,很多时候我发保证已经做好的需求不在后面的版本发生变更。所以在写的时候,要时刻考虑扩展,考虑如果这个逻辑后期改变了需要做哪些修改,以及怎样才能降低修改的工作量,面向扩展编程会使程序具有很好的扩展性
设计模式
恰当的设计模式可以提高代码的可维护性和可扩展性,但 Android 程序容易有性能瓶颈,因此要控制设计的度,不能太牵强,否则就过度设计了。
常见的设计模式: 单例模式、工厂模式、观察者模式等。