安卓(Android)性能优化内存优化实战秘籍

EvanAmy 发布于1月前
0 条问题

内存是计算机中重要的部件之一,它是外存与CPU进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的,因此内存对计算机的性能影响非常大。既然内存对性能影响非常大,那我们要清楚内存会对性能产生什么影响以及优化的目标。

内存的影响

内存的影响要从两方面分析: 系统层面和进程层面。

1

系统层面

从系统层面分析内存的影响,应该从设备的物理内存分析,设备的物理内存被分为很多页(Page),每页 4KB。不同的页用来做不同的事情, 。

安卓(Android)性能优化 内存优化实战秘籍

图1

橘黄色的是已使用页,黄色的是缓存页(数据在磁盘上有备份,所以 Cache Pages 是可以被回收的),绿色的是空闲页。

随着开启的应用越来越多,最终Free Pages不足以满足应用的内存需求,会触发 用于回收 Cached pages 的 kswapd 进程,在 Android 上我们有个机制叫 Low Memory Killer,就是用来进行回收内存, 回收的机制简单说就是按照进程优先级的顺序(感兴趣的同学可以自行了解),如果频繁触发 Low Memory Killer 将导致应用卡顿、桌面黑屏重启,手机死机等等。

结论:为了减少触发Low Memory Killer机制,应该尽量减少内存开销(当然我们自己的应用只是系统众多应用的一部分,因此我们从系统层面优化的空间和效果就会小很多,这需要大家共同提供一个良好的环境)。

2

进程层面

每个进程都是有一个单独的虚拟机,使用的内存空间都是独立的,现在我们要分析当达到一定阈值会对性能产生什么影响。

不管是Dalvik虚拟机还是Art虚拟机,当分配对象所占用的内存空间不足时会触发GC,现在我们来分析一下触发GC的原理(Dalvik和Art原理大体相同,只是在具体的操作上有区别,所以下面分析的原理都适用):

GC的类型:

GC_FOR_MALLOC: 表示是在堆上分配对象时内存不足触发的GC。

GC_CONCURRENT: 当我们应用程序的堆内存达到一定量,或者可以理解为快要满的时候,系统会自动触发GC操作来释放内存。

GC_EXPLICIT: 表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。

GC_BEFORE_OOM: 表示是在准备抛OOM异常之前进行的最后努力而触发的GC。

GC触发时机:

1. 调用函数dvmHeapSourceAlloc在Java堆上分配指定大小的内存。如果分配成功,那么就将分配得到的地址直接返回给调用者了。函数dvmHeapSourceAlloc在不改变Java堆当前大小的前提下进行内存分配,这是属于轻量级的内存分配动作。

2. 如果上一步内存分配失败,这时候就需要执行一次GC了。不过如果GC线程已经在运行中,即gDvm.gcHeap->gcRunning的值等于true,那么就直接调用函数dvmWaitForConcurrentGcToComplete等到GC执行完成就是了。否则的话,就需要调用函数gcForMalloc来执行一次GC了,参数false表示不要回收软引用对象引用的对象。

3. GC执行完毕后,再次调用函数dvmHeapSourceAlloc尝试轻量级的内存分配操作。如果分配成功,那么就将分配得到的地址直接返回给调用者了。

4. 如果上一步内存分配失败,这时候就得考虑先将Java堆的当前大小设置为Dalvik虚拟机启动时指定的Java堆最大值,再进行内存分配了。这是通过调用函数dvmHeapSourceAllocAndGrow来实现的。

5. 如果调用函数dvmHeapSourceAllocAndGrow分配内存成功,则直接将分配得到的地址直接返回给调用者了。

6. 如果上一步内存分配还是失败,这时候就得出狠招了。再次调用函数gcForMalloc来执行GC。参数true表示要回收软引用对象引用的对象。

7. GC执行完毕,再次调用函数dvmHeapSourceAllocAndGrow进行内存分配。这是最后一次努力了,成功与事都到此为止。

总结:我们看到内存不足会引起GC触发,当GC触发后会开启线程进行垃圾回收操作,GC线程分为并发GC和非并发GC,有些垃圾回收算法会执行stop world,这将影响主线程,也就会影响绘制产生卡顿,即使非stop world也会占用CPU的资源,导致我们真正执行任务的线程使用CPU时间片减少,从而降低我们的进程的执行效率。当分配内存不足,调用GC回收仍不足时,就会产生OOM问题,导致进程直接Crash,而且我们应该尽量避免小空间的产生,因为当GC回收这些小空间后,会产生大量碎片,虽然内存空间从大小来看够,但是由于这些内存空间不连续,因此在分配大内存时应然会OOM。

优化目标

通过以上分析可知,因为内存占用过多,导致内存不足,内存不足导致OOM、卡顿、执行效率降低,所以我们内存优化的目标就是 减少内存的占用。

为了减少内存占用,我们首先分析一下哪些问题会导致多占用内存,多占用内存可以分为两类:

第一类、无用的数据还在占用内存,这类问题的代表就是 内存泄漏;

第二类、有用的数据占用的内存过多,这类问题的代表就是 图片加载 (减少加载图片的内存)、 内存抖动 (比如在循环体内或者onDraw等方法体里创建很多临时的内存)。

1

内存泄漏

Android系统虚拟机的垃圾回收是通过虚拟机GC机制来实现的。GC会选择一些还存活的对象作为内存遍历的根节点GC Roots,通过对GC Roots的可达性来判断是否需要回收。内存泄漏就是在当前应用周期内不再使用的对象被GC Roots引用,导致不能回收,使实际可使用内存变小。

2

图片加载

大部分 App 都免不了使用大量的图片,比如电商应用和外卖应用等。图片在 Android 中对应的是 Bitmap 和 Drawable 类,我们从网络上加载下来的图片最终会转化为 Bitmap。图片会消耗大量内存,如果使用图片不当,很容易就会造成 OOM。

3

内存抖动

内存抖动是指在短时间内有大量的对象被创建或者被回收的现象,有短时间内快速的上升和下落的趋势,内存呈锯齿状。此时会频繁的GC,造成卡顿,甚至有OOM的可能。

4

其它问题

数据容器:比如 HashMap,HashMap 需要中存储每一个键值对都需要一个额外的 Entry 对象。

AutoBoxing:自动装箱的核心就是把基础数据类型转换成对应的复杂类型。在自动装箱转化时,都会产生一个新的对象,这样就会产生更多的内存和性能开销,如int只占4字节,而Integer对象有16字节。

枚举类型:使用枚举类型的dex size是普通常量定义的dex size的13倍以上,同时,运行时的内存分配,一个enum值的声明会消耗至少20bytes。

内存复用:比如ViewHolder实现ConvertView复用,如果不实现,每次进入getView都会重新创建一份ConvertView对象。

强引用:如果某些对象(特别是较大或者使用频率较低的对象)使用强引用,那么当GC触发时不能回收该对象。

缓存:对于会产生大量内存对象,但是只使用部分创建对象的业务,如果不把那些不常用的对象从内存中剔除,那么内存中就会存留一些使用概率很低的对象,造成不必要的浪费。

(欢迎交流,补充更多导致内存变多问题)

现在我已经知道要解决的问题,下面我们针对每一种问题给出相应的解决方式以及可用的工具。

内存泄漏

分析内存泄漏问题可以借助第三方的工具,下面依次讲解各种工具:

1.Lint扫描:该工具主要是扫描静态代码,从代码的写法上分析可能存在的内存泄漏,因此该方法不太准确、覆盖率不高, 故不推荐使用。

2.LeakCanary:该工具的检测原理是LeakSentry 会 hook Android 生命周期,自动检测当 Activity被销毁时,它们的实例是否被回收了,销毁的实例会传给 RefWatcher,RefWatcher 会持有它们的弱引用,当调用GC后,弱引用没有被回收则说明存在内存泄漏,该工具初始只检测activity的泄漏,如果想检测其它类(Fragment、View等)必须自己手动添加监听,如果想全面检测内存泄漏使用起来比较麻烦, 故不推荐使用。

3.Memory Profiler:Profiler 是 Android Studio 为我们提供的性能分析工具,它包含了 CPU、内存、网络以及电量的分析信息,而 Memory Profiler 则是 Profiler 中的其中一个版块。该工具可以实时观测应用的内存使用情况,可以用来观测是否有内存抖动等情况,但是不建议该工具的堆转储分析内存泄漏,最好结合下面的MAT工具一起使用。

4.MAT:对于内存泄漏问题,Memory Profiler 只能给我们提供一个简单的分析,不能够帮我们确认具体发生问题的地方。而 MAT 就可以帮我们做到这一点,MAT 的全称是 Memory Analyzer Tool,它是一款功能强大的 Java 堆内存分析工具,可以用于查找内存泄漏以及查看内存消耗情况,接下来我就借助自己工程的实际操作来分析内存泄漏问题。

1

实例

现在我们分析京东App结算页面的内存泄漏情况。

分析思路:

操作待分析的页面(打开该页面,在该页面尽可能操作所有功能,然后退出页面,执行GC操作,现在堆里没有被回收的有关该页面的数据,就和可能是内存泄漏),然后用Memory Profiler堆转储,然后用MAT工具打开hprof文件进行分析。

1.打开Memory Profiler工具,在进入结算页面前执行GC操作,除去尽可能无关的内存数据。

安卓(Android)性能优化 内存优化实战秘籍

图2

2.进入结算页面操作,然后退出页面,执行GC操作,然后使用堆转储的工具生成hprof文件。

安卓(Android)性能优化 内存优化实战秘籍

图3

3.使用MAT分析该文件(注:因为Memory Profiler文件导出的hprof非标准的文件,得使用命令进行转换hprof-conv /Users/XXXX/XXX/XXXXX.hprof  /Users/XXXX/XXX/XXXXX.hprof )

安卓(Android)性能优化 内存优化实战秘籍

图4

至此我们已经打开堆转储的文件,但是现在有一个难题,那就是怎么从这么多的内存对象中找到我们模块的内存泄漏,因为我们结算页面不仅有我们自己生成的类,也有好多第三方的类,如何找全内存泄漏这是一个关键,现在提供一个思路:第一步、进入结算页面之前生成一份hprof文件,退出结算页面后再生成一份hprof文件,比较这两个文件里增加的对象,这样可以排除很多和结算页面无关的对象;第二步、使用MAT的搜索功能,用包名搜索比较的结果,先用自己模块的包名查找自己生成类的内存泄漏,然后再用涉及到的第三方模块的包名,搜索第三方内存泄漏,结合自己的业务分析是否为结算页面的内存泄漏。

1.生成比较结果(进入页面之前生成hprof和退出页面之后生成hprof文件,然后用MAT的比较功能进行比较)

安卓(Android)性能优化 内存优化实战秘籍

图5

从比较结果来看,已经比之前的一万多项少了很多,变成几百项,我们再从这几百项里找到我们模块的内存泄漏即可。

2.用包名进行搜索,筛选出和业务有关的内存泄漏。

安卓(Android)性能优化 内存优化实战秘籍

图6

3.然后执行找出GC root引用链,即可分析出是否为内存泄漏。

安卓(Android)性能优化 内存优化实战秘籍

图7

4.查看结果是否为内存泄漏,如果依然被持有则为内存泄漏。

安卓(Android)性能优化 内存优化实战秘籍

图8

至此,我们已经找出模块内存泄漏的数据,大家可以自行试试自己模块。

图片优化

图片加载问题是我们内存优化的重中之重,接下来我们来分析图片加载的情况,首先我们从图片的源头-图片格式开始分析。

目前移动端Android平台原生支持的图片格式主要有:JPEG、PNG、GIF、BMP和WebP(自Android4.0开始支持),但是在Android应用开发中能够使用的编解码格式只有其中三种:JPEG、PNG、WebP。

1.JPEG

JPEG是一种广泛使用的有损压缩图像标准格式,它不支持透明和多帧动画,通过控制压缩比,可以调整图片的大小。

2.PNG

PNG是一种无损压缩图片格式,它支持完整的透明通道,从图像处理领域讲,JPEG只有RGB三个通道,而PNG有ARGB四个通道。

3.WebP

相对于前面几种图片格式,WebP算是一个初生儿,由Google在2010年发布,它支持有损和无损压缩、支持完整的透明通道、也支持多帧动画,是一种比较理想的图片格式。目前国内很多主流APP都已经应用了WebP,例如微信、微博、淘宝等。在即保证图片质量又要限制图片大小的需求下,WebP应该是首选。在Android 4.0(API level 14)中支持有损的WebP图像,在Android 4.3(API level 18)和更高版本中支持无损和透明的WebP图像。

1

色深,位深以及内存占用和图片文件大小的计算

1.颜色深度

颜色深度是指每个像素可以显示的颜色数,在计算机中,通常采用颜色深度这一概念来说明其处理色彩的能力。颜色深度基本上用量化数bit表示。bit数越高,每个像素可显示出的颜色数目就越多,色彩就越丰富,图像就越真实,文件也越大。

如果一个图片支持256种颜色(如GIF格式),那么就需要256个不同的值来表示不同的颜色,也就是从0到255。用二进制表示就是从00000000到11111111,总共需要8位二进制数,所以颜色深度是8。

如果是BMP格式,则最多可以支持红、绿、蓝各256种,总共24位,所以颜色深度是24。

还有PNG格式,这种格式除了支持24位的颜色外,还支持alpha通道(就是控制透明度用的),总共是32位。

2.位深

计算机之所以能够显示颜色,是采用了一种称作“位”( bit ) 的记数单位来记录所表示颜色的数据。当这些数据按照一定的编排方式被记录在计算机中,就构成了一个数字图像的计算机文件。

3.区别

位深度指的是存储每个像素所用的位数,主要用于实际文件的存储。

色深指的是每一个像素点用多少bit存储颜色,属于图片自身的一种属性。

举个例子:

100像素*100像素的图片,使用ARGB_8888,所以色深32位,保存时选择位深为24位。

在内存中所占大小为:100 * 100 * (32 / 8)Byte。

文件所占大小为 100 * 100 * ( 24/ 8 ) * 压缩效率 Byte。

2

有损压缩和无损压缩

1.有损压缩

有损压缩可以减少图像在内存和磁盘中占用的空间,在屏幕上观看图像时,不会发现它对图像的外观产生太大的不利影响。因为人的眼睛对光线比较敏感,光线对景物的作用比颜色的作用更为重要,这就是有损压缩技术的基本依据。有损压缩的特点是保持颜色的逐渐变化,删除图像中颜色的突然变化。利用有损压缩技术,某些数据被有意地删除了,而被取消的数据也不再恢复。

无可否认,利用有损压缩技术可以大大地压缩文件的数据,但是会影响图像质量。如果使用了有损压缩的图像仅在屏幕上显示,可能对图像质量影响不太大,至少对于人类眼睛的识别程度来说区别不大。可是,如果要把一幅经过有损压缩技术处理的图像用高分辨率打印机打印出来,那么图像质量就会有明显的受损痕迹。

2.无损压缩

无损压缩的基本原理是相同的颜色信息只需保存一次。压缩图像的软件首先会确定图像中哪些区域是相同的,哪些是不同的。包括了重复数据的图像(如蓝天)就可以被压缩,只有蓝天的起始点和终结点需要被记录下来。但是蓝色可能还会有不同的深浅,天空有时也可能被树木、山峰或其他的对象掩盖,这些就需要另外记录。

从本质上看,无损压缩的方法可以删除一些重复数据,大大减少要在磁盘上保存的图像尺寸。但是,无损压缩的方法并不能减少图像的内存占用量,这是因为,当从磁盘上读取图像时,软件又会把丢失的像素用适当的颜色信息填充进来。如果要减少图像占用内存的容量,就必须使用有损压缩方法。

无损压缩方法的优点是能够比较好地保存图像的质量,但是相对来说这种方法的压缩率比较低。

3

Android解析图片方式

1.非drawable下的本地图片或者网络图片

手机无论是加载sd卡图片,assets路径下还是网络图片,都需要先把图片读成数据流格式,再调用相应的decodeStream方法,将数据流转成bitmap形式,加载到内存的图片大小一样,也就是跟手机的屏幕密度没有关系。

2.加载drawable下的资源图片

图片占用内存大小,与手机的屏幕密度、图片所放文件夹密度、图片的色彩格式有关。

这里总结一下获取Bitmap图片大小的代码:

手机在加载图片时,会先查找自己本密度的文夹下是否存在资源,不存在则会向上查找,再向下查找,并对图片进行相应倍数的缩放;

如果在与自己屏幕密度相同的文件夹下存在此资源,会原样显示出来,占用内存正好是: 图片的分辨率*色彩格式占用字节数;

若自己屏幕密度相同的文件夹下不存在此文件,而在大于自己屏幕密度的文件夹下存在此资源,会进行缩小相应的倍数的平方;

若在大于自己屏幕密度的文件夹下没找到此资源,则会向小于自己屏幕密度的文件夹下查找,如果存在,则会进行放大相应的倍数的平方,这两种情况图片占用内存为:

占用内存=图片宽度 X 图片高度/((资源文件夹密度/手机屏幕密度)^2) * 色彩格式每一个像素占用字节数。

4

图片内存存储位置

2.3之前的像素存储需要的内存是在native上分配的,并且生命周期不太可控,可能需要用户自己回收。2.3-7.1之间,Bitmap的像素存储在Dalvik的Java堆上,当然,4.4之前的甚至能在匿名共享内存上分配(Fresco采用),而8.0之后的像素内存又重新回到native上去分配,不需要用户主动回收,8.0之后图像资源的管理更加优秀,极大降低了OOM。

优化图片内存就是为了减少图片占用的内存,通过上面相关知识得知和内存相关的因素:

1.图片颜色深度

2.图片的像素数

占用内存=图片的像素数*图片颜色深度

5

图片颜色深度

安卓内部支持的颜色深度如下图:

安卓(Android)性能优化 内存优化实战秘籍

图9

ARGB_8888:ARGB 四个通道的值都是 8 位,加起来 32 位,也就是 4 个字节,每个像素点占用 4 个字节的大小。

ARGB_4444:ARGB 四个通道的值都是 4 位,加起来 16 位,也就是 2 个字节,每个像素点占用 2 个字节的大小。

RGB_565:RGB 三个通道分别是 5 位、6 位、5 位,没有 A 通道,加起来 16 位,也就是 2 个字节,每个像素点占用 2 个字节的大小。

ALPHA_8:只有 A 通道,占 8 位,1 个字节,每个像素点占用 1 个字节的大小。

如果想要节省内存,可以使用 ARGB_4444 或者 RGB_565。它们相比 ARGB_8888 内存占用都小了一半。

ARGB_4444 图片的失真是比较严重的。

RGB_565 图片的失真虽然很小,但是没有透明度。

ALPHA_8 只有透明度,没有颜色值。对于在图片上设置遮盖的效果的是有很有用。

总结一下!

ARGB_4444 图片失真严重,基本不用!

ALPHA_8 使用场景比较特殊!

如果不设置透明度,RGB_565是个不错选择!

既要设置透明度,对图片质量要求又高的话,那就用 ARGB_8888 !

6

图片的像素数

减少像素数也就是对图片进行有损压缩,这时候就用到BitmapFactory.Options的inSampleSize参数(获取采样率)。

*inSampleSize大于1时,图像高、宽分别以2的inSampleSize次方分之一缩小;

*inSampleSize小于等于1时,图像高、宽不变。

7

实例

接下来我们实际运行一个图片例子,来分析优化图片的问题。

设备:分辨率1920*1080,密度为 3。

图片:尺寸252*250,所放位置为xh,密度为2。

安卓(Android)性能优化 内存优化实战秘籍

图10

分析:

1.用src系统的方式加载图片的情况下,不管图片为什么格式以及有没有进行过压缩,加载到内存的大小都为553KB,这个因为系统默认加载的颜色深度为ARGB_8888,所以最后加载到内存的大小都一样。

2.用src系统的方式加载图片的情况下,内存大小都为553KB,而我们理论计算内存大小=252*250*4/1024=246KB,这是因为加载drawable下的图片时,如果用系统默认的加载方式会进行缩放,占用内存=图片宽度 X 图片高度/((资源文件夹密度/手机屏幕密度)^2) * 色彩格式每一个像素占用字节数=252*250*(3/2)^2*4/1024=553KB。

3.用Fresco方式加载图片的情况下,如果是PNG(不管是否压缩过)或者PNG转成的WEBP(不管是有损压缩转换还是无损压缩转换),加载到内存的大小都为246KB,这是因为每个像素的大小都是4字节,所以内存大小=252*250*4/1024=246KB。

4.用Fresco方式加载图片的情况下,如果是JPG或者JPG转换的WEBP,加载到内存的大小都为123KB,因为这两种情况下都没有透明通道,Fresco在判断没有通道的情况下会用RGB_565方式加载图片,而RGB_565每个像素是2字节,所以内存大小=252*250*4/1024=246KB。

8

优化总结

加载drawable本地资源图片:

1.如果能用Fresco方式加载图片最好用该方式,这样不会进行缩放,当然这样就带来了图片不能自适应的问题,我们可以通过在布局里设置图片定死大小,或者在java代码里动态设置图片大小,因为图片已经加载到内存,在控件上缩放图片不会影响内存的大小。

2.加载图片和控件大小进行比较(前提是解码图片的时候已经知道控件大小),如果图片大于控件,则采用inSampleSize方式进行缩放加载图片。

3.如果图片可以不使用透明度通道(需跟设计师沟通后确认),最好转换成JPG或者JPG转换的WEBP,这样可以使用RGB_565方式加载图片。

加载网络资源图片或者非drawable本地资源图片:

1.网络传输尽量使用WEBP方式进行网络传输,因为该格式占用空间更少。

2.加载图片和控件大小进行比较(前提是解码图片的时候已经知道控件大小),如果图片大于控件,则采用inSampleSize方式进行缩放加载图片。

3.如果图片可以不使用透明度通道(需跟设计师沟通后确认),最好转换成JPG或者JPG转换的WEBP,这样可以使用RGB_565方式加载图片。

当然上面分析的是我们使用图片的情况,其实在我们业务当中有好多可以不使用图片,而是自己绘制。

内存抖动

由于我们自己的工程里没有观察到比较严重的内存抖动,所以没有实际的操作经验,对此项优化只是给一些建议:

尽量避免在循环体内创建对象,应该把对象创建移到循环体外。

注意自定义View的onDraw()方法会被频繁调用,所以在这里面不应该频繁的创建对象。

当需要大量使用Bitmap的时候,试着把它们缓存在数组中实现复用。

对于能够复用的对象,同理可以使用对象池将它们缓存起来。

其它优化

1、HashMap优化

HashMap的存储结构和原理这里就不详细描述了,感兴趣的同学可以自己了解,我们来分析用不同类型的集合对内存的影响,这里分别实验了集合元素数分别为100(如图11)和1000(如图12)的情况:

安卓(Android)性能优化 内存优化实战秘籍

图11

安卓(Android)性能优化 内存优化实战秘籍

图12

Retained Heap代表回收该对象后能够释放多少内存,也就是代表该对象占有多少的内存空间,为了大家方便观看,统计如下表:

安卓(Android)性能优化 内存优化实战秘籍

由此可以得出,占用空间最少为SparseArray,其次是ArrayMap,最大是HashMap。

由于SparseArray和ArrayMap存储和获取数据采用的是二分查找法,所以操作数据的效率要比HashMap低,也就是HashMap通过牺牲内存来换取效率,当数量不够大(千级别)时性能差别不太大。

结论:在存储数量不够大的情况下,如果key的类型为int,则采用SparseArray,如果key是任意类型,则采用ArrayMap;在存储数量特别大的情况下,则使用HashMap。

2、AutoBoxing

自动装箱的核心就是把基础数据类型转换成对应的复杂类型。在自动装箱转化时,都会产生一个新的对象,这样就会产生更多的内存和性能开销,如int只占4字节,而Integer对象有16字节。

3、枚举类型

使用枚举类型的dex size是普通常量定义的dex size的13倍以上,同时,运行时的内存分配,一个enum值的声明会消耗至少20bytes。

4、内存复用

比如ViewHolder实现ConvertView复用,如果不实现,每次进入getView都会重新创建一份ConvertView对象,这样就会产生没有必要的内存。

5、强引用

如果某些对象(特别是较大或者使用频率较低的对象)使用强引用,那么当GC触发时不能回收该对象,这种情况下我们可以使用软引用或者弱引用,当GC触发时可以回收掉这些内存,当然带来的牺牲就是再次需要改对象时需要重新构建。

6、缓存

对于会产生大量内存对象,但是只使用部分创建对象的业务,如果不把那些不常用的对象从内存中剔除,那么内存中就会存留一些使用概率很低的对象,造成不必要的浪费。

7、数据相关

序列化数据使用protobuf可以比xml省30%内存,慎用shareprefercnce,因为对于同一个sp,会将整个xml文件载入内存,有时候为了读一个配置,就会将几百k的数据读进内存,数据库字段尽量精简,只读取所需字段。

8、使用Parcelable进行内存间数据传输

Parcelable是Android特有的类,Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据。而Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable(因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化)。

9、使用handler.obtainMessage()创建Message对象

不要再用new Message()的方式创建Message对象啦!handler.obtainMessage()内部使用了对象池技术,可以有效帮助我们减少创建Message对象的内存消耗。

10、谨慎使用依赖注入框架

依赖注入框架在Android端越来越流行了(如ButterKnife),因为这些框架往往可以简化代码,方便开发。然而,这些依赖注入框架会通过扫描代码执行许多初始化的操作,这会导致你的代码需要大量的内存空间来mapping代码,而且mapped pages会长时间的被保留在内存中。所以如何取舍,就根据实际项目需要来决定了。

11、谨慎使用帧动画

帧动画是非常消耗内存的,在使用帧动画之前,我们尽量考虑一下这个动画效果能不能通过补间动画或属性动画来实现。如果是大图做帧动画或者上百帧的动画,那么在低端设备上就极有可能出现OOM。

12、谨慎使用static对象

因为static的生命周期过长,和应用的进程保持一致,使用不当很可能导致对象泄漏,在Android中应该谨慎声明static对象。

Android内存优化是一个比较重要的话题,合理使用内存可以有效减少OOM、卡顿等疑难问题,本篇文章从内存影响分析出优化目标,根据优化目标我们找到目前存在的问题,对这些问题我们一一解决,最终让内存达到一个比较合理使用范围。 不过分析了这么多,最重要的是实际操作,小伙伴们马上实践起来!

欢迎讨论: 刘跃明 liuyueming@jd.com

查看原文: 安卓(Android)性能优化 内存优化实战秘籍

  • purplefish
  • organicgorilla
  • goldenrabbit
  • bluebutterfly
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。