本文大体分为四部分
- 内存优化
- 布局优化
- 编码优化
- 网络优化
内存优化
首先说一下内存泄漏和OOM:
- 内存泄漏,因为不恰当的引用导致本该被释放的资源无法得到释放。
- OOM,新分配的内存大小加上已经占用的内存大小,超出了限制的内存大小。
内存泄漏更多是因为我们的代码写的有问题,OOM更多是因为我们对我们应用内存的占用没有很好的把控。内存泄漏是导致OOM的一大元凶。
内存优化分为5点来说:
- 减少对象内存占用
- 内存对象的复用
- 避免内存泄漏
- 合理的内存使用策略
- 内存优化辅助工具
一、减少对象内存占用
- 使用更加轻量的数据结构。比如:ArrayMap/SparseArray是替代HashMap的好帮手。关于ArrayMap和SparseArray的使用:
- 适合对象个数的数量级最好在千以内,因为他们的插入和删除的效率不够高。
- 查找和插入使用的是二分查找。
- key类型是int时,请使用SparseArray因为它避免了自动装箱。
- 避免使用Enum。官方说法是,相对于静态常量,枚举会消耗两倍以上的内存。并且在运行时还会产生额外的内存占用。在一个官方实例里,枚举占用的内存是静态常量的13倍,运行时内存占用是6倍。
- 减少Bitmap对象的内存占用。对于创建出来的Bitmap对象通常有两个操作可以优化其内存占用。
- inSampleSize:在载入内存之前,计算一个合适的缩放比例。
- decode format:解码格式,ARGB_8888(每个像素4个字节,最高精度,有透明通道)、RGB_565(每个像素两个字节,无透明度)、ARGB_4444(deprecated in Api13)、Alpah_8,不同的解码格式差别很大。根据情况选择合适的会比较好。
- 使用更小的图片。拿到美工给的图,要留意大图直接被XML引用有时会出现InflationException,该异常的根本原因就是OOM。
二、内存对象的复用
- 复用系统自带的资源。系统自带了很多颜色、动画、样式、布局、图片。使用这些可以减少内存开销,但需注意版本差异性。
- ListView/GridView/RecyclerView内重复子View的复用。
- Bitmap对象复用。使用inBitmap来复用已经存在的内存区域。3.0之后出现,重用的bitmap大小和解码格式需要一致。4.4以后优化大小限制,只要小于或等于原bitmap的大小即可。可以维护一个有多种典型bitmap的对象池,使得后续bitmap创建都可以找到合适的复用模板。
- 在频繁调用的方法外创建对象。如onDraw()方法会频繁调用,在里面做创建对象的操作会迅速增加内存使用,很容易引起频繁GC甚至是内存抖动。
- StringBuilder/StringBuffer。使用StringBuilder/StringBuffer来替代频繁的字符串拼接操作。
三、避免内存泄漏
- Activity的泄漏。
- 内部类引用导致。典型的如Handler。考虑尽量使用静态内部类,同时使用弱引用机制避免互相引用出现的泄漏。
- Activity Context被传递到其他实例中,可能导致自身被引用发生泄漏。尽量使用Application Context。除了和UI相关的,如显示弹窗、启动Activity、填充布局。参考Android Context。
- 临时Bitmap对象的回收。临时创建一个相对比较大的bitmap对象,在经过变换获得新的bitmap对象之后,应尽快回收之前的bitmap。注意createBitmap()方法可能返回source bitmap,所以需要检查返回值是否和source bitmap相等。不等才可以对source bitmap执行recycle方法。
- 监听器的注销。
- Cursor对象的及时关闭。
- 缓存容器的对象泄漏。如4.0之前,把drawable添加到缓存容器,因为drawable和view的强引用很容易导致activity发生泄漏。
- WebView的泄露。Android的WebView存在很大的兼容性问题,WebView因为不同系统版本不同厂商都粗乃很大的差异,甚至标准的WebView存在内存泄漏的问题(09年发现,13年修复)。根治方法:为WebView使用新进程,通过AIDL进行通信,WebView所在进程根据业务需要在合适时机进行销毁。
- 慎用static对象,static的生命周期和应用的进程保持一致,使用不当很可能导致内存泄漏。
- 留意单例对象中不合理的引用。单例对象的生命周期和应用保持一致。
四、合理的内存使用策略
- 使用IntentService代替Service。
- 谨慎使用large heap。在清单文件的<application>节点设置largeHeap=true可以为应用生命一个更大的heap控件。会影响用户体验,并使GC运行时间更长,任务切换耗能增加。并且,在一些严格限制的机器上,largeHeap和通常的heap size大小一样。你始终应该通过getMemoryClass()来检查实际获取到的heap大小。
- 合适的缓存大小。结合可用内存大小等因素设置。
- onLowMemory()和onTrimMemory()。后者从4.0开始提供,提供了更为详细的系统内存占用级别。可以通过监测系统内存占用适当的释放自身的一些内存占用。
- 选择合适的文件夹存放资源文件。图片会被拉伸以适应不同的设备。对于不希望被拉伸的图片,放在assets或nodpi目录下。
- 对大内存分配做Try...Catch...操作。比如给解析大图时,使用try catch,catch到OOM后将采样比例增加一倍再次尝试解析。
- 慎用抽象编程。抽象需要同等量的代码用于可执行,这些代码会被mapping到内存中。
- 使用nano protobufs序列化数据。Google设计,类似XML,比XML更加轻量快速简单。
- 慎用依赖注入。通过扫描你的代码执行许多初始化操作,会导致你的代码需要大量的内存空间来mapping代码,而且mapped pages会长时间保留在内存中。
- 慎用多进程。可以扩大应用的内存占用范围,但使用不当会导致显著增加内存。
- 使用ProGuard剔除不需要的代码。
- 慎用第三方库。很多功能会用不上。
五、内存优化辅助工具
- facebook开源的LeakCanary,可用来监测内存泄漏
- Android Monitor,可以查看内存占用,可以查看指向,可以手动触发GC。分析内存泄漏的流程是
- 手动触发GC
- 查看JavaHeap
- 点击Analyzer Task即可进行内存泄漏的分析。
布局优化
分四个方面
- 选择合适的根节点
- 重用布局文件
- 仅在需要时加载布局
- 避免过度绘制
一、选择合适的根节点
Android在创建Activity时默认生成的布局为RelativeLayout,而新建布局时默认的根节点为LinearLayout。这是因为
- 在复杂的布局中使用RelativeLayout可以降低布局嵌套,使布局比较扁平。也更加灵活。
- 对于简单的布局,LinearLayout在不用处理weight属性的情况下,性能上是优于至少需要计算两次的RelativeLayout的。
二、重用布局文件
- <include>标签的使用,要注意如果需要使用layout属性,必需先设置layout:width和layout:height
- <merge>标签的使用可减少不必要的视图嵌套 :
- 添加的子视图不需要针对父视图的属性,只是要添加到父视图上显示。根节点可为<merge>。
- 比如在LinearLayout里include另外一个方向相同的LinearLayout,这个被include的视图的根节点就可以改为merge.
三、仅在需要时加载布局
<ViewStub>
使用时调用inflate即可,也可以调用setVisibility(View.VISIBILITY)。
注意:不支持<merge>标签的布局。
四、避免过度绘制
比如给根布局设置了图片背景,但是用户只能看到子View,根本就没有看到最下面的背景。但是背景仍要被绘制。这就是过度绘制。
可以通过设置-开发者选项-显示GPU过度绘制来观察过度绘制。颜色越深的区域过度绘制越严重,蓝色最好,红色最差。
- 去除不必要的背景设置。
- 自定义view时,通过Canvas的clipRect()来绘制部分需要重绘的区域。
五、小知识点
- android:drawableXXX属性。TextView控件可直接显示图片和文字。
- setCompoundDrawable(),代码中通过该方法实现第一个效果。
- android:divider,使用自带的分割线。
- space控件,可用于添加空白间隔,该控件不进行绘制。
- android:lineSpacingExtra="",android:text="aaa\nbbb\nccc"多行文字可使用TextView的行间距实现。
- Spannable,使用Spannable来为TextView设置强大的样式。
编码优化
几个点
- 静态方法。将一项通用的功能写成静态方法,调用速度会提升15%-20%,同时不需要创建对象来调用该方法,也不用担心改变对象的状态(静态方法无法访问非静态字段)。
- 避免创建不必要的对象
- 静态最终常量。对基本数据类型以及String常量使用static final修饰,会在dex文件的初始化器中进行初始化,会更快。
- 多使用增强型for循环,但ArrayList使用传统循环方式。
- 使用系统封装好的API。系统的API很多功能是通过底层汇编模式执行的,效率会比较高。如数组拷贝的功能,使用System.arrayCopy()会比使用循环一一赋值效率高9倍以上。
- 避免在内部使用getter/setter。内部使用时,字段查找比方法调用效率高。
网络优化
工具:Android Studio有Network Monitor
主要有:
- 接口设置多样化,便于App可以以较少的请求来完成业务需求。
- 使用Gzip压缩request和response,减少数据传输大小
- 可以使用Protocol Buffer来代替json、XML等
- 合适的图片。获取图片时告知服务器宽高质量等来获取合适的图片资源。
- 设置网络缓存,来取消不必要的网络请求。
- 在网络良好的时候,对一些很有可能会进行操作的数据进行提前获取。
- 弱网优化,Android Emulator可以设置网络速度和延迟来做弱网测试。可采取的措施如:不自动加载图片、先反馈后提交(如用户点赞,先给提交成功的反馈,记录下来之后提交)