Android性能优化
布局优化
布局优化可以通过HierarcheyViewer可以直接看到冗余的层级,然后尽量减少视图树层级并使用下面的常用布局优化方式进行优化
布局优化遵循以下原则:
- 尽量多使用相对布局(RelativeLayout)或约束布局(ConstraintLayout)
- 在List组件中尽量避免使用LinearLayout的layout_weight属性
- 将可复用组件抽取出来并通过
<
include>
标签使用 - 使用
<
ViewStub>
标签来加载一些不常用布局 - 使用
<
merge>
标签来减少布局的嵌套层次
include布局
使用include将公用布局独立为一个布局xml,其他页面可以直接引用这个布局xml
实现原理很简单,在解析xml时检测到include标签,就直接把该布局下的根视图添加到include所在的父布局中
代码实现流程:
merge标签
布局中经常会出现子布局根视图与父视图是同一类型,使用merge标签作为子视图的顶级视图可以合并ui布局,降低嵌套层次
代码实现流程:
ViewStub视图
ViewStub是一个不可见的和能在运行期间延迟加载目标视图、宽高都为0的View,在调用ViewStub的inflate()方法或设置visible之前不占用布局空间和系统资源
代码实现流程:
内存优化
珍惜Services资源
- Service如果没有正在执行任务,任何时候都应该是非运行状态,同时要注意停止Service失败而引起的内存泄漏
- 当启动一个Service,系统会倾向为了保留这个Service而一直保留Service所在的进程。这减少了系统能够存放到LRU缓存中的进程数量,他会影响App之间的切换效率,这甚至会导致系统内存使用不稳定,从而无法继续保持住所有目前正在运行的Service
- 最好使用IntentService,他会在处理完自己的intent任务之后尽快结束自己
当UI隐藏时释放内存
实现Activity里的onTrimMemory()并监听TRIM_MEMORY_UI_HIDDEN,可以接收到用户离开UI时的通知,在该回调里释放UI所占用的资源
onStop()可以用来释放网络连接、广播等资源,但不应该用来释放UI资源
当内存紧张时释放部分内存
app生命周期里的任何阶段onTrimMemory都可以告诉你内存资源是否紧张,根据onTrimMemory中内存级别决定释放哪些资源
- TRIM_MEMORY_RUNNING_MODERATE 表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经有点低了,系统可能会开始根据LRU缓存规则来去杀死进程了。
- TRIM_MEMORY_RUNNING_LOW 表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经非常低了,我们应该去释放掉一些不必要的资源以提升系统的性能,同时这也会直接影响到我们应用程序的性能。
- TRIM_MEMORY_RUNNING_CRITICAL 表示应用程序仍然正常运行,但是系统已经根据LRU缓存规则杀掉了大部分缓存的进程了。这个时候我们应当尽可能地去释放任何不必要的资源,不然的话系统可能会继续杀掉所有缓存中的进程,并且开始杀掉一些本来应当保持运行的进程,比如说后台运行的服务。
- TRIM_MEMORY_BACKGROUND 表示手机目前内存已经很低了,系统准备开始根据LRU缓存来清理进程。这个时候我们的程序在LRU缓存列表的最近位置,是不太可能被清理掉的,但这时去释放掉一些比较容易恢复的资源能够让手机的内存变得比较充足,从而让我们的程序更长时间地保留在缓存当中,这样当用户返回我们的程序时会感觉非常顺畅,而不是经历了一次重新启动的过程。
- TRIM_MEMORY_MODERATE 表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的中间位置,如果手机内存还得不到进一步释放的话,那么我们的程序就有被系统杀掉的风险了。
- TRIM_MEMORY_COMPLETE 表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的最边缘位置,系统会最优先考虑杀掉我们的应用程序,在这个时候应当尽可能地把一切可以释放的东西都进行释放。
检查你应该使用多少内存
通过getMemoryClass()来获取可用heap大小,超过这个大小会出现OutOfMemory错误
特殊场景下可以通过在manifest的application标签下增加largeHeap=true属性来声明一个更大的heap空间,如果这样做的话可以通过getLargeMemoryClass()来查看获取到的heap大小
避免bitmaps的浪费
加载bitmap时仅仅需要保留适配当前屏幕设备分辨率的数据,如果原图高于设备分辨率需要做缩小操作,因为bitmap的尺寸会对内存呈现出2次方的增加,因为X与Y都在增加
建议使用成熟的ImageLoader框架,如Glide、Picasso、Fresco等,可以避免很多问题
使用优化的数据容器
利用Android FrameWork里面优化过的容器类,如SparseArray,SparseBooleanArray与LongSparseArray,相对于使用java的HashMap,我们使用SpareArray会比较节省内存消耗,因为HashMap中需要额外创建一个实例对象来记录Mapping操作
请注意内存开销
- Enums内存消耗通常是常量的2倍,尽量避免在Android上使用enums
- Java中每一个类(包括匿名内部类)都会使用大概500bytes
- 每一个类的实例产生的花销是12-16bytes
- 往HashMap添加一个entry需要一个额外占用的32bytes的entry对象
请注意代码"抽象"
虽然抽象能够替身代码的灵活性和可维护性,但是也导致一个显著的开销:用于执行的等量代码会被map到内存中。所以,如果抽象没有有效替身效率,应该尽量避免
为序列化的数据使用nano protobufs
Protocol buffers类似xml却比xml更加轻量,快速,简单,通常的协议化操作会产生大量繁琐的代码并增加ram使用量,显著增加apk大小,更慢的执行速度,更容易达到dex的字符限制
避免使用依赖注入框架
通常一些注入框架会扫描代码并执行许多初始化操作,而这些代码需要大量的RAM来map代码,而mapped pages会长时间保留在RAM中
谨慎使用外部库
不要为了1个或2个功能而导入一个大而全的library,这会导致应用中有多个相同相冲突的library
优化整体性能
- 谷歌官方提供的优化整个app性能的文章: Best Practices for Performance
- 关注lint工作提出的建议
使用ProGuard来剔除不需要的代码
ProGuard能够通过移除不需要的代码,重命名、域与方法等方面或对代码进行压缩,优化与混淆。使用ProGuard能够更少mapped代码所需的RAM
对最终的apk使用zipalign
使用zipalign最apk进行重新校准后可以减少ram占用,zipalign优化的最根本目的是帮助操作系统更高效率的根据请求索引资源
使用多进程
通过将App组件切分为多个组件,运行在不同进程中。例如音乐播放,将播放和前台的UI操作分开
Android性能检测优化工具
- Memory Monitor 实时获取应用内存使用情况
- LeakCanary 自动追踪内存泄漏
- Android手机开发者选项中的调试GPU过度渲染 显示过度绘制的区域
- Hierarchy Viewer 查看布局层级
- Debug类/DDMS 数据采集工具
- TraceView 分析函数时间消耗找出性能瓶颈