一,内存分析
1,Java堆中各代的分布:
![PP助手 > 性能分析之内存 > image2016-5-22 14:11:43.png image2016-5-22%2014%3A11%3A43.png?versio](http://doc.ucweb.local/download/attachments/236159046/image2016-5-22%2014%3A11%3A43.png?version=1&modificationDate=1463898007000&api=v2)
Young:主要是用来存放新生的对象
Old:主要存放应用程序中生命周期长的内存对象
Permanent:是指内存的永久保存区域,主要存放Class和Meta的信息
在Young中存放的对象都是会快速被创建并且很快被销毁回收的,同时这个区域的GC操作速度也是比Old Generation区域的GC操作速度更快的。
除了速度差异之外,执行GC操作的时候,所有线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行。
通常来说,单个的GC并不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(16ms)。如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了。
2,分析工具
![PP助手 > 性能分析之内存 > image2016-5-22 14:12:0.png image2016-5-22%2014%3A12%3A0.png?version](http://doc.ucweb.local/download/attachments/236159046/image2016-5-22%2014%3A12%3A0.png?version=1&modificationDate=1463898007000&api=v2)
The Heap and CPU Profiling Agent (HPROF)是JAVA2 SDK自带的一个简单的profiler代理,它通过与Java Virtual Machine Profiler Interface (JVMPI) 交互,将profiling信息通过本地文件或socket输出ASCII或二进制格式的流。
HPROF可以监控CPU使用率,堆分配统计。除此之外,还可以报告JVM所有监视器和线程的完整的堆的dump状态。
1),分析hprof可以用AS自带的和MAT.
下面是AS自带的分析工具:
![PP助手 > 性能分析之内存 > image2016-5-22 14:12:58.png image2016-5-22%2014%3A12%3A58.png?versio](http://doc.ucweb.local/download/attachments/236159046/image2016-5-22%2014%3A12%3A58.png?version=1&modificationDate=1463898007000&api=v2)
2),用Android Tools中的Heap Viewer进行分析当前进程中的Heap Size情况
![PP助手 > 性能分析之内存 > image2016-5-22 14:13:14.png image2016-5-22%2014%3A13%3A14.png?versio](http://doc.ucweb.local/download/attachments/236159046/image2016-5-22%2014%3A13%3A14.png?version=1&modificationDate=1463898007000&api=v2)
3),Memory Churn
Memory Churn因为在短时间内大量的对象被创建又马上被释放。瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,会触发GC从而导致刚产生的对象又很快被回收。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。
要定位Memory Churn,可以利用AS中的Memory Monitor很方便的分析,具体方法更上面分析Memory Leak一样
上面提到了4种内存分析工具:
A,Memory Monitor:跟踪整个app的内存变化情况
B,Heap Viewer:查看当前内存快照,分析哪些对象可能发生泄漏
C,Allocation Tracker:最终内存对象的来源
D,MAT:Eclipse的内存分析器,能快速对Java堆进行分析。
4),命令查看当前应用使用内存的情况
![PP助手 > 性能分析之内存 > image2016-5-22 14:13:33.png image2016-5-22%2014%3A13%3A33.png?versio](http://doc.ucweb.local/download/attachments/236159046/image2016-5-22%2014%3A13%3A33.png?version=1&modificationDate=1463898007000&api=v2)
5),静态检查工具也能帮我们找到一些泄漏点,如:Lint
二,内存泄漏经验点
1,Activity生命周期超越activity对象的生命周期
(1)避免在Activity里面实例化其非静态内部类的静态实例 (尤其对launchMode不是singleInstance)
(2)用全局的Context代替组件的Context
(3)避免使用非静态内部类在activity中
推荐方式:使用一个静态的内部类,并且对它的外部类用弱应用(WeakReference)
典型案例:ViewRootImpl 中的W内部类
![PP助手 > 性能分析之内存 > image2016-5-22 14:13:53.png image2016-5-22%2014%3A13%3A53.png?versio](http://doc.ucweb.local/download/attachments/236159046/image2016-5-22%2014%3A13%3A53.png?version=1&modificationDate=1463898007000&api=v2)
2,Handler导致的Activity泄漏
如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity
解决方案:1)在UI退出之前,执行remove Handler消息队列中的消息与runnable对象
2)将Handler定义为静态内部类,并且在类中增加一个成员变量,用WeakReference来应用外部实例
3,HandlerThread使用时需要在结束时调用mThread.getLooper().quit()
4,注册某一对象后未反注册
5,集合对象也要及时清理
6,资源对象用完需要及时关闭
7,构建Adapter时,使用缓存的convertView
8,不要在经常调用的方法中创建对象(如:循环,onDraw等),要巧用容器,池
9,使用更加轻量的数据结构(如:用ArrayMap / SparseArray来替换HashMap)
10,尽量避免使用Enum
11,大量字符串的拼接使用StringBuilder
12,谨慎使用large heap
13,onLowMemory()与onTrimMemory()
onLowMemory : Android系统提供了一些回调来通知当前应用的内存使用情况,通常来说,当所有的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调。在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行。
onTrimMemory : Android系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递以下的参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用。
14,资源文件使用Android提供合适的文件夹存放
hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下
15,使用IntentService,它会在处理完交代给它的任务之后尽快结束自己
16,优化布局层次,减少内存消耗
越扁平化的视图布局,占用的内存就越少,效率越高。我们需要尽量保证布局足够扁平化,当使用系统提供的View无法实现足够扁平的时候考虑使用自定义View来达到目的
17,谨慎使用多进程
使用多进程可以把应用中的部分组件运行在单独的进程当中,这样可以扩大应用的内存占用范围,但是这个技术必须谨慎使用,绝大多数应用都不应该贸然使用多进程,一方面是因为使用多进程会使得代码逻辑更加复杂,另外如果使用不当,它可能反而会导致显著增加内存。当你的应用需要运行一个常驻后台的任务,而且这个任务并不轻量,可以考虑使用这个技术
18,谨慎使用第三方libraries
19,考虑不同的实现方式来优化内存占用
有时我们需要结合整体项目评估内存使用方案,若一味的降低内存而不综合考虑不一定是最好。
20,图片的处理
图片一般是APP处理的大头,如处理不好会极其造成大内存占用,影响性能,轻则越用越慢,重则崩溃闪退,严重影响用户体验。
对于图片的处理一般可以采取如下方式处理:
(1)decode format : 不同的解码格式最内存的占用也存在很大差异
ALPHA_8 : 8位的alpha位图
ARGB_4444 : 16位ARGB位图
ARGB_8888 : 32位ARGB位图
RGB_565:代表8位RGB位图
位图位数越高代表其可以存储的颜色信息越多,当然位图也就越逼真但同时占用的内存也会越多。我们需要结合项目实际灵活选择
(2)inSampleSize : 缩放比,在载入内存前,我们可以计算出一个合适的缩放比例,避免不必要的大图载入
(3)Decode图片时采用Native memory内存占用空间
这里面有2种实现方式:第一种可以用JNI在底层结合SKIA图片库进行处理;第二种利用Android提供的API处理
上面的第一种方式难度会比较大,且有各种兼容性问题处理,这里重点介绍第二种,其实Android很早就已经考虑到这个问题了,在5.0以前可以如下API配合使用:
options.inPurgeable = true;
options.inInputShareable = true;
(4)图片使用完要及时进行回收操作
在释放资源时,需要注意释放的Bitmap或者相关的Drawable是否有被其它类引用。如果正常的调用,可以通过Bitmap.isRecycled()方法来判断是否有被标记回收;而如果是被UI线程的界面相关代码使用,就需要特别小心避免回收有可能被使用的资源,不然有可能抛出系统异常 cannot draw recycled bitmaps
(5)不必要的时候避免图片的完整加载
只需要知道图片大小的情形下,可以不完整加载图片到内存。 在使用BitmapFactory压缩图片的时候,BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,可以在不分配空间状态下计算出图片的大小
如:我们的计算图片大小的时候就需要设置此属性为true,不然就会完整加载图片而浪费内存
(6)尽可能使用更小的图片(可以根据不同的分辨率使用不同的图片)
尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图的时候就会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM
(7)图片缓存(对象池)
在Android上面最常用的一个缓存算法是LRU(Least Recently Use)。
目前比较常用成熟的应用框架有:ImageLoader, Fresco等,当然也可以自实现,一般采用3级缓存机制,然后结合设备内存阀值机其他因素灵活结合LRU算法即可
(8)复用系统自带的资源
Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去
(9)利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率上的提升
使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小
使用这个API有几个点需要注意:
A,在SDK 11 -> 18之间,重用的bitmap大小必须是一致的,例如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小
B,新申请的bitmap与旧的bitmap必须有相同的解码格式,例如大家都是8888的,如果前面的bitmap是8888,那么就不能支持4444与565格式的bitmap了。
我们可以创建一个包含多种典型可重用bitmap的对象池,这样后续的bitmap创建都能够找到合适的“模板”去进行重用
(10)使用inNativeAlloc来达到扩大使用内存的目的 (限4.0以下)