赞
踩
为什么要进行内存优化
安卓应用如果不进行内存优化,一些不良的代码习惯或编码可能有内存泄漏,甚至导致OOM。这样的应用随着长时间的使用,经常会出现越来越卡的现象,严重的甚至会直接闪退,进而给用户带来劣质感,严重影响用户体验和使用,最终导致用户流失。
对于你的手机,都有自己的app内存配置参数,可以adb查看
adb shell getprop | findstr heap
显示
[dalvik.vm.heapgrowthlimit]: [384m] //一个应用正常时的最大堆(Dalvik的)内存,会触发oom
[dalvik.vm.heapmaxfree]: [8m] //单次最大的free内存
[dalvik.vm.heapminfree]: [2m] //单次最小的free内存
[dalvik.vm.heapsize]: [512m] //application标签下加android:largeHeap="true"时的最大堆内存,会触发oom
[dalvik.vm.heapstartsize]: [8m] //应用起始分配的堆内存
[dalvik.vm.heaptargetutilization]: [0.75] //堆内存利用率参考值
1
2
3
4
5
6
7
8
9
项目功能完后内存如果有泄漏或抖动等,轻则出现卡顿,严重的长期不退出进程最终出现OOM。
**哟哟,似乎上面的解释很合理,以上说的都是不内存优化的危害,所以要内存优化。
但是,但是,但是安卓的java部分的内存不是自动回收的吗?,是不是这部分就不用优化啦?理论上是这样的(现实是残酷的),当创建的对象在不使用的时候会被系统在某个时间自动回收,但是系统认为可以被回收的不再被使用的对象是指那些 不在GC Root的引用链上,但是实际上很多时候我们认为已经不需要使用了的对象因为编码或系统本身的原因,还是在这条引用链上的,导致无法回收。
优化步骤
1,查看当前的应用内存使用情况,发现是否有内存泄漏
可以使用adb查看
可以使用Android studio的Profile查看
也可以使用DDMS查看
这里主要讲的是第一种,后两种也比较简单。
adb shell dumpsys meminfo [applicationId(一般都是包名)]
这里主要看Java Heap、Native Heap(C、C++占用的内存) 、Graphics(GPU占用的内存,主要是纹理、帧缓存等占用的,手机没有独立显存)
进入某个界面进行一些操作后返回,检查(记得gc下下,内存稳定下来看,防止误差):
内存是否增长明显
Activities、Views等是否数量增加
无论出现1或者2都是代表内存并没有被释放
2,内存泄漏点查找,并解决问题
一般可以使用DDMS或者Profile导出上面步骤1操作前和操作后的hprof文件,如下图:
点击上面的红圈后生成了hprof文件,一般需要使用platform-tools里面的hprof-conv.exe转换一下,如:
hprof-conv.exe input.hprof output.hprof
再使用MAT打开output.hprof,如图:
可以直接看Leak Suspects里面的分析,如果有具体的分析结果并且指明了具体的未释放类和对象,可以直接修复(一般只是参考,不依靠这个修改)
然后也可以创建Histogram进行类对象数量和大小等的比对(这个可以精确定位内存增长在哪个类的对象上),或者直接在Histogram里面输入自己的包名或者类名查询,然后可以通过菜单选项选择,看到具体对象的被引用情况。比如下图搜索多出来的Activity,最终确定的引用关系:
这个可以看到:LiveActivity类的context对象被MaterialDialog的静态内部类Builder持有了,而Builder的对象builder被MaterialDialog的实例持有,MaterialDialog的实例又被Message的实例持有,而Message的对象又被HandlerThread持有。
通过这个引用链可以发现根本原因,我们一般是: 1,先看引用链上有木有我们自己包下面的类和对象,有的话就在对应的类上修改(比如使用弱引用等)来断开这个强引用链,如果没有则 2,再看系统引用为什么还保持着这个对象,要么是调用系统api的问题,要么是系统本身API就有问题(比如安卓早期版本系统层面就有很多内存泄漏的地方,还有可能国内各种手机厂商的Rom定制导致的),我们这里是HandlerThread里的Looper没有清掉MessageQueue里面的Message导致的,HandlerThread这个类已经有quit()释放资源的方法,那么这里肯定是我们自己调用问题导致quit()没有被执行,属于2的情况。
最终查看LiveActivity类里的dialog dismiss代码,发现是在 activity finish()之后执行的dialog dismiss()方法导致这个问题。调整先后位置后重新走步骤1 adb查看activity数量不再增加,验证OK。
3,其它
实际开发中都是集成LeakCanary到开发项目中,这样只需要操作应用,自动检查出一部分内存列漏点,并提示引用链。解决还是按照步骤2的解决问题部分来的。
一些日常项目中编码的注意点
编码时,总的原则是:
对象的生命周期不能超过它的使用周期,不使用了及时手动删掉一些内部的持有的生命周期比较长的或者在其它GC Root链上的对象(比如赋值null,弱引用等)
项目实践中注意一下情况:
1. 首先还是需要强调一下,实现模块内具体功能时,尽量避免以应用内常驻后台的Service方式实现,android官方也认为这种方式过于贪婪。对这类后台Service,合理处理Service的生命周期,保证流程结束时,Service能够被正常
停止。通过bindService绑定的Service,用完后要及时解除绑定,否则也会造成被绑定的Service一直无法被释放。
2. 在应用的内部流程上,保证对象的生命周期不会超过相应的逻辑流程。比如Apllication中初始化的对象生命周期一定要是整个应用进程的。
3. 静态对象直接或间接引用到Activity,静态对象的生命周期和进程一致。正常情况下,在AMS调度完Acitity的onDestroy之后,ActivityThread会删除对Activity的引用,之后不存在从GC roots到该Activity的引用路径,该Activity的java对象也会被回收。但是在Activity被静态对象引用后,即使前面的引用被删除后,依然存在到该Activity的引用,进程不退出,静态对象不会释放,被其引用的Activity也不会释放。这种情况,也是Monkey测试中碰到最多的内存泄露情形。比较常见的情形,把Activity作为Context直接传给静态对象,把Activity的非静态内部类对象传给静态对象,把Activity的某个view传给静态对象,这些情形都会导致静态对象持有Activity的引用。第1种情况比较明显,后面2种情况中,非静态内部类会持有其外部类的引用,而view对象会通过其mContext持有Activity的引用。所以,对静态对象的赋值,除非是简单的java类型,否则一定要有相对应的清理操作。
4. 需要使用Context作为参数传递时,尽量使用ApplicationContext,使用Activity作为Context的参数传递时,可能会把Activty的引用传递给一个生命周期不同的对象,从而导致Activity在应该被释放时而没有被释放。在静态对象被构造时,如果有Context的参数,要主动转换成ApplicationContext,防止传入的参数是Activity。以上,对Service也是一样。
郑州市不孕不育专科医院:http://www.zzchbb.com/
5. 如非必要,不要使用SingleInstance。SingleInstane的对象一旦被创建,在应用退出之前,其对象不会被释放。所以,这点要根据具体流程进行评估,仅在确定需要时使用SingleInstance。同样,对于静态对象,也是需要确保其生命周期和相应的逻辑流程一致,在不需要时进行清理。
6. 对于Bitmap类的对象要及时主动释放,不仅对于代码中主动构造的Bitmap对
象,要有主动的释放操作,对于通过view. setBackgroundResource(@DrawableRes int resid)接口设置的Bitmap资源,以及通过xml布局文件配置的Bitmap资源,都要在不需要时进行主动释放。主动释放的这部分主要是Native Heap和Graphics,前者是为图片数据分配native层的内存,后者是描画该图片GPU占用的内存。因此,在Activity的onDestroy中,要主动释放通过各种方式加载的Bitmap对象。首先,通过View的getBackground()方法获取到BitmapDrawable对象,再通过BitmapDrawable得到Bitmap对象,最后调用Bitmap的recycle方法进行回收。另外有些图片用的是.9格式的png图片,这种图片生成的是NinePatchDrawable,也就是说,这种情况下,通过getBackground()方法获取到的是NinePatchDrawable对象。NinePatchDrawable中取得Bitmap的方法与BitmapDrawable不同,首先通过成员函数getConstantState()获取到内部的mNinePatchState对象,再通过NinePatchState的getBitmap()获取到Bitmap对象。
7. 重写OnTrimMemory(int level)方法,在应用的所有界面不可见时,系统会回调该方法供应用释放部分内存,其中level的值TRIM_MEMORY_UI_HIDDEN(如想了解level的其他取值及作用,请自行搜索,其他值可以暂不理会)。这时候可以释放掉Bitmap或其他不再使用的对象,在onRestart或其他合适的时间点,再重新恢复以上资源,保证应用在用户不可见的情况下,占用最少的内存。
8. 注册某个对象后,要及时进行取消。比如Activity实现了某些Listener接口,在被作为Listener对象注册后,在Activity生命周期结束后,没有进行取消注册的话,就会造成Activity的对象无法被释放。类似的,注册广播接收器、注册观察者、EventBus等等,都需要在适当的时间取消注册。
9. 使用的资源对象要及时关闭,比如File、Cursor等。集合中的对象,在不使用时要及时进行清理,特别是Static类型的集合对象,或者是被Static对象持有的集合对象。
10. 使用优化过的数据集合,这也是android官方推荐的。Android API当中提供了一些优化过后的数据集合工具类,如SparseArray,SparseBooleanArray,以及LongSparseArray等,使用这些API可以让我们的程序更加高效。传统Java API中提供的HashMap工具类会相对比较低效,因为它需要为每一个键值对都提供一个对象入口,而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间。
11. 图片分辨率相关,对于xxhdpi的设备,在使用资源图片时,如果xxhdpi文件夹中存在相应的资源图片时,图片加载时,不会被进行缩放,如果xxhdpi不存在,xhdpi中存在,那么不管该图片的分辨率是多少,系统会对其进行放大。也就是说,对于同一张图片,仅仅放在xxhdpi或者xhdpi中时,对于xxhdpi的设备,获取到的Bitmap对象的大小是不一样的。对于一个1280×720的图片,大小可能是3.68M和8.29M,差别超过一倍。
12. ListView复用,getView里尽量复用conertView,同时因为getView会频繁调用,要避免频繁地生成对象。
13. Handler导致的内存泄露,Handler一般以内部类的形式实现,从而Handler会持有外部类的引用。如果通过Handler发送一个延迟处理的消息到线程的消息队列,会存在一个消息队列-〉消息-〉Handler-〉外部类(Activity或Servie)的引用,如果外部类退出,这时候,因为存在从消息队列的引用,相应的Activity或Service就不会被释放。对于这种情况,可以考虑Handler不以内部类形式实现,或以静态内部类实现,或者在外部类退出时,从消息队列中删除相应的延迟消息。
14. 线程导致内存泄露,如果以内部类形式实现了一个子线程,线程开始运行并执行一个耗时的操作,在线程没有结束之前,外部类退出,此时,外部类不会被释放,因为作为内部类的子线程持有外部类的引用,解决方法和前一条类似。
15. 如果在Activity中播放一个无限循环的动画,在退出Activity时没有在 onDestroy中去停止动画,那么动画会一直播放下去,尽管已经无法在界面上看见动画效果了,并且这个时候 Activity的 View会被动画持有,而View又持有了Activty,最终Activity无法释放。解决方法是在Activity的onDestroy中调用animator.cancel()来停止动画。
郑州不孕不育哪个好:http://jbk.39.net/yiyuanfengcai/tsyl_zztjyy/1222/
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。