这多是最好的性能优化教程(三)

这多是最好的性能优化教程系列专栏
这多是最好的性能优化教程(一)
这多是最好的性能优化教程(二)
这多是最好的性能优化教程(三)android

前言

内存泄漏历来都是咱们老生常谈的话题,不管是 Android Studio 自带的内存泄漏分析工具仍是专业的 Eclipse MAT 抑或是备受青睐的第三方插件 LeakCanary,都为咱们的内存泄漏检测提供了便利。若是从根源上解决内存泄漏,内存优化必不可少。因此本章节咱们参考扔物线胡凯的内存优化策略,直接拿出一章节来谈内存优化。git

内存优化基本能够分为下面几个方面github

  • 减小对象的内存占用
  • 对内存对象进行复用
  • 避免对象的内存泄漏
  • 内存使用策略优化

减小对象的内存占用

避免在 Android 里面使用 Enum

Enum 是 Java 中包含固定常量的数据类型,当须要知道预先定制的几个值,这几个值表示一些数据类,咱们均可以使用 Enum。咱们通常用 Enum 作一些编译时检查,以免传入不合法的参数。数据库

但 Enum 的每一个对象都是 Object,在 Android 官网上就早已明确指出应该在 Android 开发中避免使用 Enum,由于与静态常量想必,它对内存的占用是要大不少的。缓存

所以在实际开发中,我更加倾向于接口变量,由于接口会自动把成员变量设置为 static 和 final 的,这一点能够防止某些状况下错误地添加新的常量,这也使得代码看起来更加简单和清晰。性能优化

使用更加轻量的数据结构

前面第一节已经说过,咱们应该更加倾向于考虑使用 ArrayMapSparseArray 而不是 HashMap 等传统数据结果,前面已经用图示演示了 HashMap 的简要工做原理,相比起 Android 系统专门为移动操做系统编写的 ArrayMap 容器,在大多数状况下,都显示效率低下,更占内存。一般的 HashMap 的实现方式更加消耗内存,由于它须要一个额外的实例对象来记录 Mapping 操做。另外,SparseArray 更加高效在于他们避免了对 keyvalueautobox 自动装箱,而且避免了装箱后的解箱。数据结构

使用更小的图片

在设计给到资源图片的时候,咱们须要特别留意这张图片是否存在能够压缩的空间,是否可使用一张更小的图片。尽可能使用更小的图片不只仅能够减小内存的使用,还能够避免出现大量的 InflationException。假设有一张很大的图片被 XML 文件直接引用,颇有可能在初始化视图的时候就会由于内存不足而发生 InflationException,这个问题的根本缘由实际上是发生了 OOM。app

减小 Bitmap 对象的内存占用

Bitmap是一个极容易消耗内存的大胖子,减少建立出来的Bitmap的内存占用是很重要的,一般来讲有下面2个措施:ide

  • inSampleSize:缩放比例,在把图片载入内存以前,咱们须要先计算出一个合适的缩放比例,避免没必要要的大图载入。
  • decode format:解码格式,选择 ARGB_8888 / RBG_565 / ARGB_4444 / ALPHA_8,存在很大差别。

尽可能地采用 int 类型

Android 系统中 float 类型的数据存取速度是 int 类型的一半,尽可能优先采用 int 类型。而一样能做为整数的代名词,采用 int 替换 Integer 会让你的内存开销更小。函数

对内存对象进行复用

复用系统自带的资源

Android 系统自己内置了不少的资源,例如字符串 / 颜色 / 图片 / 动画 / 样式以及简单布局等等,这些资源均可以在应用程序中直接引用。这样作不只仅能够减小应用程序的自身负重,减少 APK 的大小,另外还能够必定程度上减小内存的开销,复用性更好。可是也有必要留意 Android 系统的版本差别性,对那些不一样系统版本上表现存在很大差别,不符合需求的状况,仍是须要应用程序自身内置进去。

注意 ListView / GridView 的 Adapter 对 ConvertView 进行复用

这个貌似没啥好说的,太基础了,并且咱们可能如今更加青睐于 RecyclerView

尽可能的采用 StringBuilder

这个也特别基础,咱们点到为止。大概就是尽可能的采用 StringBuilder / StringBuffer 来替换咱们频繁的字符串拼接。

尽可能使用原字符串的 subString

当从已经存在的数据集中抽取出 String 的时候,尝试返回原数据的 subString 对象,而不要建立一个重复的对象。

避免在 onDraw() 里面执行对象的建立

相似 onDraw() 等频繁调用的方法,必定须要注意避免在这里作建立对象的操做,由于他会迅速增长内存的使用,并且很容易引发频繁的 gc,甚至是内存抖动。

避免对象的内存泄漏

内存对象的泄漏,会致使一些再也不使用的对象没法及时释放,这样一方面占用了宝贵的内存空间,很容易致使后续须要分配内存的时候,空闲空间不足而出现 OOM。显然,这还使得每级 Generation 的内存区域可用空间变小,gc 就会更容易被触发,容易出现内存抖动,从而引发性能问题。

注意 Activity 的泄漏

一般来讲,Activity 的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,咱们须要特别注意如下两种状况致使的 Activity 泄漏:

  • 内部类引用致使 Activity 的泄漏
    最典型的场景是 Handler 致使的 Activity 泄漏,若是 Handler 中有延迟的任务或者是等待执行的任务队列过长,都有可能由于 Handler 继续执行而致使 Activity 发生泄漏。此时的引用关系链是 Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,能够在 UI 退出以前,执行 remove Handler 消息队列中的消息与 runnable 对象。或者是使用 Static + WeakReference 的方式来达到断开 Handler 与 Activity 之间存在引用关系的目的。
  • Activity Context 被传递到其余实例中,这可能致使自身被引用而发生泄漏。
    内部类引发的泄漏不只仅会发生在 Activity 上,其余任何内部类出现的地方,都须要特别留意!咱们能够考虑尽可能使用 static 类型的内部类,同时使用 WeakReference 的机制来避免由于互相引用而出现的泄露。

尽可能地采用 Application Context

对于大部分非必须使用 Activity Context 的状况(Dialog 的 Context 就必须是Activity Context),咱们均可以考虑使用 Application Context 而不是 Activity 的 Context,这样能够避免不经意的 Activity 泄露。

并且若是习惯 Glide 的童鞋可能会发现,Glide 须要传递的 Context 若是是 Activity 的 Context ,那么在 Activity 被销毁后还没加载出来的话还会引起崩溃。因此,请在使用 Glide 或者 Toast 等的时候,直接传递 Application Context 吧。

注意 Cursor 对象是否及时关闭

在程序中咱们常常会进行查询数据库的操做,但时常会存在不当心使用 Cursor 以后没有及时关闭的状况。这些 Cursor 的泄露,反复屡次出现的话会对内存管理产生很大的负面影响,咱们须要谨记对 Cursor 对象的及时关闭。

注意 WebView 的泄漏

Android中 的 WebView 存在很大的兼容性问题,不只仅是 Android 系统版本的不一样对 WebView 产生很大的差别,另外不一样的厂商出货的 ROM 里面 WebView 也存在着很大的差别。更严重的是标准的 WebView 存在内存泄露的问题,看这里。因此一般根治这个问题的办法是为 WebView 开启另一个进程,经过 AIDL 与主进程进行通讯,WebView 所在的进程能够根据业务的须要选择合适的时机进行销毁,从而达到内存的完整释放。

注意临时 Bitmap 对象的及时回收

虽然在大多数状况下,咱们会对 Bitmap 增长缓存机制,可是在某些时候,部分 Bitmap 是须要及时回收的。例如临时建立的某个相对比较大的 Bitmap 对象,在通过变换获得新的 Bitmap 对象以后,应该尽快回收原始的 Bitmap,这样可以更快释放原始 Bitmap 所占用的空间。

须要特别留意的是 Bitmap 类里面提供的 createBitmap() 方法:


这个函数返回的 Bitmap 有可能和 source bitmap 是同一个,在回收的时候,须要特别检查 source bitmap 与 return bitmap 的引用是否相同,只有在不等的状况下,才可以执行 source bitmap 的 recycle() 方法。

注意监听器的注销

在 Android 程序里面存在不少须要 register 与 unregister 的监听器,咱们须要确保在合适的时候及时 unregister 那些监听器。本身手动 add 的 listener,须要记得及时 remove 这个 listener。

内存使用策略优化

谨慎使用 large heap

Android 设备根据硬件与软件的设置差别而存在不一样大小的内存空间,他们为应用程序设置了不一样大小的 Heap 限制阈值。你能够经过调用 getMemoryClass() 来获取应用的可用 Heap 大小。在一些特殊的情景下,你能够经过在 manifest 的 application 标签下添加 largeHeap = true 的属性来为应用声明一个更大的 heap 空间。而后,你能够经过 getLargeMemoryClass() 来获取到这个更大的 heap size 阈值。然而,声明获得更大 Heap 阈值的本意是为了一小部分会消耗大量 RAM 的应用 ( 例如一个大图片的编辑应用 ) 。不要轻易的由于你须要使用更多的内存而去请求一个大的 Heap Size。只有当你清楚的知道哪里会使用大量的内存而且知道为何这些内存必须被保留时才去使用 large heap。所以请谨慎使用 large heap 属性。使用额外的内存空间会影响系统总体的用户体验,而且会使得每次 gc 的运行时间更长。在任务切换时,系统的性能会大打折扣。另外, large heap 并不必定可以获取到更大的 heap。在某些有严格限制的机器上,large heap 的大小和一般的 heap size 是同样的。所以即便你申请了 large heap,你仍是应该经过执行 getMemoryClass() 来检查实际获取到的 heap 大小。

资源文件须要选择合适的文件夹进行存放

咱们知道 hdpi / xhdpi / xxhdpi 等等不一样 dpi 的文件夹下的图片在不一样的设备上会通过 scale 的处理。例如咱们只在 hdpi 的目录下放置了一张 100 x 100 的图片,那么根据换算关系,xxhdpi 的手机去引用那张图片就会被拉伸到 200 x 200。须要注意到在这种状况下,内存占用是会显著提升的。对于不但愿被拉伸的图片,须要放到 assets 或者 nodpi 的目录下。

Try catch某些大内存分配的操做

在某些状况下,咱们须要事先评估那些可能发生 OOM 的代码,对于这些可能发生 OOM 的代码,加入 catch 机制,能够考虑在 catch 里面尝试一次降级的内存分配操做。例如 decode bitmap 的时候,catch 到 OOM,能够尝试把采样比例再增长一倍以后,再次尝试 decode。

谨慎使用 static 对象

由于 static 的生命周期过长,和应用的进程保持一致,使用不当极可能致使对象泄漏,在 Android 中应该谨慎使用 static 对象。

特别留意单例对象中不合理的持有

虽然单例模式简单实用,提供了不少便利性,可是由于单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。特别是持有 Context 的引用,须要谨慎对待

优化布局层次,减小内存消耗

越扁平化的视图布局,占用的内存就越少,效率越高。咱们须要尽可能保证布局足够扁平化,当使用系统提供的 View 没法实现足够扁平的时候考虑使用自定义 View 来达到目的。

谨慎使用多进程

使用多进程能够把应用中的部分组件运行在单独的进程当中,这样能够扩大应用的内存占用范围,可是这个技术必须谨慎使用,绝大多数应用都不该该贸然使用多进程,一方面是由于使用多进程会使得代码逻辑更加复杂,另外若是使用不当,它可能反而会致使显著增长内存。当你的应用须要运行一个常驻后台的任务,并且这个任务并不轻量,能够考虑使用这个技术。

一个典型的例子是建立一个能够长时间后台播放的 Music Player。若是整个应用都运行在一个进程中,当后台播放的时候,前台的那些 UI 资源也没有办法获得释放。相似这样的应用能够切分红 2 个进程:一个用来操做 UI,另一个给后台的 Service。

写在最后

内存优化并不就是说程序占用的内存越少就越好,若是由于想要保持更低的内存占用,而频繁触发执行 gc 操做,在某种程度上反而会致使应用性能总体有所降低,这里须要综合考虑作必定的权衡。

若是想第一时间收到更新信息的能够关注个人简书:简书地址
你也能够选择关注个人公众号:nanchen

相关文章
相关标签/搜索