Android常用内存优化方式整理

Android中,内存是十分宝贵的资源,内存优化有助于增加程序的流畅度提高用户体验,所以学习内存优化技是很有必要的。下面整理一些简单常用的性能优化方式。主要有以下几方面:

  1. 减小对象内存占用
  2. 对象复用
  3. 避免内存泄漏
  4. 内存使用策略优化

1、减小对象内存占用

基本数据类型与包装类型

与基本数据类型相比包装类型占用的内存更大,因此非必要情况不使用包装类型。例如:HashMap的Key或者 value为基础类型的情况下使用 SparseArray( SparseBooleanArray; SparseIntArray;SparseLongArray)替换, 或者在小数据量情况下使用 ArrayMap

拓展:ArrayMap与HashMap类似,都是使用键值对存储数据,考虑到内存优化的问题,Android优化了HashMap,也就是现在的ArrayMap。ArrayMap使用二分法对key从小到大排序,在删除,添加,查找元素的时候,使用二分查找法找到key所在的index,通过index找到对应的元素。所以ArrayMap适合于数据量偏小的场景,否则性能会变得很慢。ArrayMap使用两个数组存储,一个存储key的hash值,一个存储value。而HashMap使用的是数组+链表+红黑树

枚举

enum比起使用常量至少会增加2倍以上内存。
java中enum可以保证类型安全,提高代码的可阅读性;但是enum的每一个值都是对象,每个声明都会占用部分内存以便于引用到该对象,因此枚举会比Integer和String占用更多内存;替代方案,官方推出了@IntRef与@StringRef 两个注解来提供编译时的类型检查

Bitmap

Bitmap 极易消耗内存,减小Bitmap占用内存可以显著的减少内存占用。
优化措施:缩放,在图片载入内存之前,先行计算出一个合适的缩放比例,避免不必要的大图载入;解码格式选择:ARGN_8888/RG_565/ARGB_4444/ALPHA_8

图片

图片占用的内存远超文本,可以使用压缩后的图片,或者部分场景下使用shape绘制替代图片

代码混淆

减少不必要的类、类库;混淆可以去除无用代码,通过语意模糊对类,字段进行重命名,从而缩小,优化代码,差生更少量的映射

序列化

使用Parcelable进行数据传输;Parcelable是Android 特有的序列化类,性能比Serializable好,内存开销小,所以推荐在内存中传输数据时使用,但是由于是Android内特有类,所以在进行网速数据传输的时候不建议使用,建议使用Serializable,

不同版本的Parcelable的实现可能不同,所以也不推荐进行数据持久化时使用

2、对象复用

对象池

利用系统框架中某些既有复用特性,减少对象的重复创建,例如通过Message.obtain() 创建Message对象而不是new Message();Android系统本身也内置了很多的string,color,drawable,anim,style以及简单的layout等资源,这些都是能够在应用中直接引用的,这样不仅能减小APK大小,同时还能在一定程度上减小内存开销

ListView/GridView/RecyclerView

针对这种大量重复的子组件视图对ItemView进行复用

Bitmap复用

利用LRU cache机制对Bitmap 复用

3、内存泄漏

静态变量

静态变量的生存期为整个源程序
例如:将Activity的context对象赋值给全局静态变量,造成activity无法正常销毁

单例

单例的生存期根据初始化方式不同,开始时间不同,但是都从初始化开始一直到程序退出
例如:单例模式中实现观察者了模式,只在activity 中 注册了监听方法。没有在activity 中进行移除注册,就会引起内存泄漏

属性动画

无限循环播放的动画没有在onDestory中停止,动画就会一直播放,动画->View->Activity 这个引用链会造成Activity无法被正常销毁

非静态内部类

非静态内部类都隐式持有外部类的实例,造成外部类无法回收

异步线程/任务

异步线程/任务都是一个匿名内部类,都会持有当前外部类实例,会造成外部实例无法正常回收

Handler

Handler 会显式或者隐式持有当前Activity引用
在Activity 的onDestory()中关闭全部的当前的类的Hander;removeCallback(),removeMessage();removeCallbacksAndMessages

资源未关闭

对于使用BroadCastReceveiver,ContentObserver、File、Cursor、Stream、Bitmap等资源,应该在不使用的时候及时回收,否者这些资源不会被回收,造成内存泄漏;例如:在Activity销毁的时候回收

全局集合对象

集合类型对象如果只有添加方法,没有删除机制,会造成内存一直被占用,如果这个集合同时是全局性的变量,就会造成集合占用内存只增不减

WebView

WebView会造成的内存泄漏。
解决方案:
动态生成WebView,退出Activity 时候移除;
为加载WebView的Activity开启新的进程,并在该页面退出后关闭进程;
(推荐)从根源解决,WebView内存泄漏的主要原因是因为 WebView 注册了 component callbacks;但是反注册使用错误导致的;系统分别在attach和detach时进行了注册和反注册,但是 Activiy 比WebView 先执行了destory,这就会造成WebView不执行反注册,正确的写法是先在Activity 的onDestory()执行前先移除WebView,同时移除WebView绑定的服务,再执行Activity的super.onDestory()

EditText

EditText导致的内存泄漏 基本上都是因为键盘 inputMethManager 持有了EditTExt的引用,而EditTEext持有Activity引用造成的,但是由于Android系统碎片化并没有普适的方法解决,可以在Activity退出的时候,移除EditText断开引用链

常用的内存泄漏检查工具:
Android Profiler
LeakCanary

4、内存使用策略优化

优化布局,减少布局层次

越是扁平化的布局占用的资源就越少,同时也能优化View渲染的递归时间

StringBuffer/StringBuilder

在用到大量的字符拼接操作时候,可以考虑使用使用StringBuffer/StringBuilder而不是使用“+”拼接,减少内存抖动

避免在onDraw(),创建对象

在onDraw 等频繁调用的方法中,避免创建对象的操作,这会频繁GC造成内存抖动,(创建对象会增大内存开销,GC不是实时的)

选择合适的文件夹放置文件

hdpi/xhdp/xxhdpi等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100 * 100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200 * 200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下

设计合适的缓存大小

在设计ListView/GridView/RecyclerView的Bitmap的 lru cache的时候要充分的考虑,应用程序剩余可用内存,屏幕上同时会渲染的图片数量,有多少的图片需要缓存,设备的 dpi是多少(hpdi的设备比mdpi的设备需要更大内存来缓存同样的数量)等等;

使用IntentService

当Service完成任务之后停止失败会引起内存泄漏,建议使用IntentService,它会在任务完成时候主动停止

合理使用数据引用类型

强引用:
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。强引用其实也就是我们平时使用的Object strongReference = new Object();
软引用:
如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。

软引用可用来实现内存敏感的高速缓存

弱引用:
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象
虚引用:
虚引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收

虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

SharePreference

SharePreference的操作会讲整个xml都读取到内存中,极易出现为读取其中的某一项而大量的无用数据读进内存

抽象

大多数情况下抽象类都是一种良好的编程习惯,因为抽象可以调好代码的灵活性与可维护性。但是没有个抽象类都会造成额外的内存开销,因此如果抽象类没有显著的提高效率,应该尽量避免使用

反射

Android 有相当一部分框架都通过反射来实现(如:Retrofit、ORMLite),这些框架往往可以简化代码。但是这个框架一般为了提升效率,往往会在内存中缓存大量的mapping,而且会长时间的保留在内存中。所以如何取舍就要看项目的实际需求了’

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值