Google Developing for Android 二 - Memory 最佳实践 // lightSky‘Blog
Google Developing for Android 二 - Memory 最佳实践
原文:Developing for Android, II
The Rules: Memory
在决定应用的行为,是否有好的用户体验以及整体的设备体验来说,内存的使用可能是独立因素中最重要的。内存因素包括应用的内存占用,以及内存搅动(导致的垃圾回收会对运行期间的性能有影响)。
避免在循环中分配内存
内存分配虽然不可避免,但是应尽可能的避免,特别是在平凡的调用的代码块中。比如在绘制代码中,因为每一帧的渲染都会执行该方法。
避免在自定义View的onDraw方法中分配内存,因为动画也许会调用它。使用缓存对象替换临时对象可以避免新的内存开销。典型的例子就是在onDraw方法中分配了一个新的Paint对象,因为Canvas需要一个Paint对象。对于这种自定义View的实例只分配一个独立的Paint对象而后在onDraw方法中临时使用更好。
尽可能的避免内存分配
避免常量,临时变量的内存分配。下面有一些可参考的策略,也许并不适用于传统的java编码,但是对于Android开发是推荐的。通常可以使用工具帮助我们去决定是否某一块代码需要优化。如果代码的某一部分很少执行(比如用户改变一些设置的操作),更简洁和传统的抽象层是不错的选择。但是如果分析表明某些代码频繁执行并导致了大量的内存搅动,考虑以下策略:
对象缓存
重用对象在一些常量内存的再分配中很有用,比如在内部循环中避免内存分配。比如,有些频繁调用的方法中可能需要一个Rect对象存储一些中间值,最好在把Rect作为类级别的常量,只分配一次内存,甚至是静态的,避免每次方法调用的时候都分配。关于单例的一些警示对于这种方式也是适用的,在Android上,静态的常见缺陷就是它们对于某一个进程是静态的,但是可能有多个活动在同一进程中。小心应用,这种技术在避免内存的再分配中是通用的对象池
如果代码临时需要同一种类型的多个对象,考虑适用对象池而不是频繁的分配内存。但对象池可能不容易管理。如果对象有状态并且又是被任意线程访问的情况,要注意一些并发性的问题。在内存压力方面也有问题(可以使用LruCache策略),对象的增加存在着内存泄漏的风险,因此当你使用对象池的时候注意这些问题,考虑只在特定的情况下使用它。如果这种策略在那些内存配置低低老版本或者设备上非常有用,你应该通过API版本或者isLowRamDevice()方法来检测对象池的使用限制Arrays
ArrayList是一种很便利的集合也不会造成太多的分配。它会再分配,并且会复制当前的数组添加到列表后面,但是设置一个合理的初始化容量可以避免频繁的分配内存。如果你的集合不需要动态的变化大小,考虑使用ArrayAndorid集合类
除非你需要一个map去存储大量的数据,否则考虑使用ArrayMap或者是SimpleArayMap作为数据结构而不是HashMap。这些类是经过优化的,相对于HashMap会有更高的内存效率以及更少的GC压力,这样的数据结构在移动设备上能够更好的满足通用的使用情景(另外它们也支持实体的遍历而不使用Iterator)。当然,考虑设置一个恰当的初始容量去避免自动扩容需要修改对象的方法
这种情况你不应该不要返回一个新的对象,考虑将该对象作为参数传入进来,去修改该对象:
Rect getRect(int w, int h) |
getRect(Rect , w, h) |
- 当原始类型可以满足时避免使用对象类型 使用Integer,Float而不是int,float时,会导致内存分配,自动装箱以及更多对象自身内存的分配。比如,如果你的方法中一个Integer,然后代码调用附带了一个int,由于自动装箱,将会导致一个内存分配。带有传统类型的集合类和泛型可能不可避免的,但是当原始类型可以搞定的时候应该避免包装类型。Android中提供了一些像SparseIntArray和SparseLongArray的集合类,它们内部使用的就是原始类型而不是对象类型。
- 避免对象数组 如果你有一组简单的数据对象,可以考虑将每一个字段存储到数组中。比如,假设你绘制的时候需要追踪一些之前的touch X/Y的point数据。你应该使用两个int[]来保存它们,一个用于存储X坐标值,另一个用于存储Y坐标值,而不是使用一个Point[]。这样不仅仅减少了原始对象的个数(节省了内存),也增加了数据的局部性,可以更好的利用宝贵的内存和CPU的缓存。
避免Interators
明确的(List.iterator()
)或者不明确的(for(Object o : myObjects)
)使用Interator会导致一个Interator对象的内存分配。单独一个内存分配不是什么大事,但是应该尽量避免在内部循环中分配内存。当然,直接的使用角标进行集合遍历可以不用分配任何的内存。
final count = myList.(); |
值得注意的是,Interator总会导致一个内存的分配,即使是空集合。因此,如果当你非要使用foreach的时候,应该在遍历集合的之前可以进行一次isEmpty的检验。
枚举通常可以用来代表常量,但是会比原始类型耗费更多,它涉及到代码量的大小和枚举对象内存的分配。
一个临时的枚举不会造成较大的内存消耗。但是Proguard会,在一些情况下他会进行一些静态的分析所有的代码,将枚举优化为int值。当枚举在整个应用中被被广泛的使用或者当一个library或某个API中的枚举被其它很多应用使用的时候时就是问题了,甚至会很糟糕。
使用AndioStudio1.3版本中的@IntDef
注解能够保证你的代码在build时期是类型安全的(当lint error开启的时候)。因此使用int变量对于性能和代码量都会更好。
避免非移动应用的Frameworks和Libraries
有时会使用一些熟悉的java平台的一些框架,比如注解依赖的Guice。但是它并没有为移动应用进行优化,使用它们将会导致一些问题。
如果你只是使用了某个Library中的一小部分,你可以试着将那一部分抽取出来。即使Proguard在很多情况下可以跳过那些不用的代码,但是在大的library的依赖图可能会导致优化失败(也会大大增加Proguard的build时间)。
有一些libraries虽然被引入到Android应用中,但是你不应该随意使用,除非很熟悉它,知道它可能为应用带来的问题。
还有些问题就是使用那些非移动的框架和库可能会增加内存的开销。你可以通过监视内存的使用和垃圾回收器的行为来检测它们导致的问题程度。
避免静态的内存泄漏
对于避免临时的内存分配使用static对象很有用。但是应该注意使用静态变量去缓存对象时,它们实际上不应该一直存在整个进程的生命周期中。特别的,这些static的变量不应该和Activity的生命周期一致。比如,当屏幕方向改变的时候Activity会destroy并recreate,但是static变量持有Activity的引用,这样会导致内存泄漏。Activities是非常耗资源的,这种内存泄漏很快会导致你的应用和系统OOM。
避免Finalizers
因为和java语言的席位差别,finalizers需要的不是一个垃圾回收器,而是两个。这就意味着不仅资源会被finalizer会被冻住直到两个垃圾回收器都触发的时候,而且系统中同时运行两个垃圾回收器也会导致资源消耗和卡顿。有一种特殊情况需要finalization,当你的对象持有一个本地的指针时。如果没有这样的情况,就可以完全避免finalizers。
如果你确实需要finalizers,考虑实现AutoCloseable接口并且在你的代码域内通过close方法释放所有的资源。
避免过度的静态初始化
在你的应用中一些重要的时间内(比如启动的时候),过多的初始化可能导致性能的问题和较差的用户体验。可以在你需要它的时候再去加载代码。
通过命令释放缓存
从API 14,ComponentCallbacks2提供了onTrimMemory()回调方法允许你的app在较低内存压力的情况下释放内存。更多详情可以参考Google I/O 2012的Video Doing More with Less,展示了一个如何LruCache处理bitmap的例子。
使用 isLowRamDevice()
KitKat版本中出现的ActivityManager.isLowRamDevice()方法可以帮助你检测应用运行时的内存限制.当你的应用中有一些特性比较好内存的时候u,可以通过这种方式去检测内存是否可以满足你的特性,然后决定是否开启。
避免请求较大的Heap内存
应用可以通过在mainfest文件中设置application tag来开启请求较大heap内存的功能,但是你不应该这么做。请求一个大的Heap的行为可能着该应用只考虑到自己的需求,但是对于整个设备度体验来说是一个错误度决定。
请求大的Heap在很少的情况下可能是必要的,比如media内容的处理。但是应用使用该功能只是为了更好的管理内存和资源而不是导致整个设备的用户体验变得更差。应用请求较大的heap将会导致设备上其它的进程拥有更少的内存,用户在切换activity的时候就有可能导致其它应用被kill掉然后重启。
避免在必要情况外过长的运行service
每一个进程在系统中都有一个资源的限制。如果你不需要service在后台一致运行,就及时将它关闭。
Android提供了很多机制来确保组件只在特定的范围内运行:
- 使用BroadcastReceiver去接收那些重要但是不频繁的事件,而不是使用一个大多数时间都无用的Service,比如network状态变化或者alarm。App可以在不需要但时候 关闭BroadcastReceivers,以至于系统只在需要的时候才被唤醒。
- 使用IntentService实现一个service,这样的service会在任务栈为空的时候自动关闭
优化代码大小
瘦身的应用会运行更快。加载的代码量越少,用户下载你的app的时间就会越少,你的应用也会更快的启动和初始化。下面是一些建议:
- 使用Proguard剔除无用的代码。使用Gradle也可以,而且它还会从你依赖的libraries中剔除那些无用的代码
- 谨慎依赖 当你只是需要某一种指定的数据类型的时候不要使用那些拥有各种集合较大的library。
- 确保你自己理解那些自动化生成代码的消耗
- 越简单越好,直接解决问题,而不是创造出大量的结构和抽象去解决问题
Google Developing for Android 二 - Memory 最佳实践 // lightSky‘Blog的更多相关文章
- Google Developing for Android 三 - Performance最佳实践
Google Developing for Android 三 - Performance最佳实践 发表于 2015-06-07 | 分类于 Android最佳实践 原文 Developing ...
- Google Developing for Android 一 - 相关上下文介绍
前几天在G+上看到Google Developers站点,有一个Android系列的文章,分享到个人微博,周末闲来没事就学写了下,把它们简单的翻译了下,没想到一发不可收拾,六篇文章全部都翻译完了,有些 ...
- 【机器学习】Google机器学习工程的43条最佳实践
https://blog.csdn.net/ChenVast/article/details/81449509 本文档旨在帮助那些掌握机器学习基础知识的人从Google机器学习的最佳实践中获益.它提供 ...
- 来自Google资深工程师的API设计最佳实践
来自Google资深工程师Joshua Bloch的分享:API设计最佳实践 为什么API设计如此重要?API是一个公司最重要的资产. 为什么API的设计对程序员如此重要? API一旦发布,出于兼容性 ...
- OPEN(SAP) UI5 学习入门系列之二: 最佳实践练习(上)
这篇博文难产了很久,原来是打算一周更新一篇的,上周原计划写MVC,但是写了一半,发现带入了太多的细节,不太符合这个入门系列的主题. 当我们学习一个新的技能的时候,如果一开始就面对大量的细节,很容易陷入 ...
- OPEN(SAP) UI5 学习入门系列之二: 最佳实践练习(下)
上期我们完成了一个简单的主从页面,但是页面是静态的,不能交互,功能也很简单,只有一个销售订单的列表. 我们今天就一鼓作气把代码全都写完,由于本次的代码量较大,所以只对重点代码部分进行讲解. 具体每个文 ...
- (转)Android开发:性能最佳实践-管理应用内存
翻自:http://developer.android.com/training/articles/memory.html 在任何软件开发环境中,RAM都是宝贵的资源,但在移动操作系统中更加珍贵.尽管 ...
- Android+PHP开发最佳实践
本书以一个完整的微博应用项目实例为主线,由浅入深地讲解了Android客户端开发和PHP服务端开发的思路和技巧.从前期的产品设计.架构设计,到客户端和服务器的编码实现,再到性能测试和系统优化,以及最后 ...
- Android 组件化最佳实践 ARetrofit 原理
本文首发于 vivo互联网技术 微信公众号 https://mp.weixin.qq.com/s/TXFt7ymgQXLJyBOJL8F6xg作者:朱壹飞 ARetrofit 是一款针对Android ...
随机推荐
- 使用sublime编写c/c++ 总结
大块头IDE Visual studio太大了,记事本也能写代码但无疑是装逼过分了.写一些轻量级的c/c++代码使用sublime来写是个很好的选择. 三步走: 编译器(win下安装了vs就使用cl, ...
- A Look At Android Support Annotations
转自:https://asce1885.gitbooks.io/android-rd-senior-advanced/content/shen_ru_qian_chu_android_support_ ...
- Winform绑定数据源的几种方式?
第一种:DataSet ds=new DataSet (); this.dataGridView1.DataSource=ds.Table[0]; 第二种:DataTable dt=new DataT ...
- Oracle数据类型隐式转换小析
测试使用环境:oracle 11g r1 平常写sql语句时,大大咧咧,不太注意和数字有关的数据类型,有时例如 where c1=111 和 where c1='111'这样混用,却不曾想这里面另有蹊 ...
- ImageLoader框架的使用、调用系统相册显示图片并裁剪显示、保存图片的两种方式
ImageLoader虽然说是一个相对于比较老的一个框架了 ,但是总的来说,还是比较好用的,今天我就总结了一下它的用法.还有调用系统相册并裁剪,以及,通过sharedpreference和文件存储来保 ...
- javascript方法链式调用和构造函数链式调用对比
先说一下方法链:B的实例从A继承了A中的同名方法,如果B的方法重载了A中的方法,B中的重载方法可能会调用A中的重载方法,这种方法称为方法链. 构造函数链:子类的构造函数B()有时需要调用父类的构造函数 ...
- python杂七杂八小问题
1.win7系统下,安装完GTK+后,从命令行界面无法启动ipython,提示“failed to create process”.运行easy_install也遇到了这个问题. 原因是安装GTK+时 ...
- linux文件目录权限详解(20170101)
linux目录权限与文件权限是不同的,二者要相互配合,这是基础. 比如要读文件:目录至少要有x,文件至少要有r. 要写文件:目录至少要有x,文件至少要有rw. 要执行文件:目录至少要有x,文件至少要有 ...
- leetcode 186. Reverse Words in a String II 旋转字符数组 ---------- java
Given an input string, reverse the string word by word. A word is defined as a sequence of non-space ...
- php lock_sh共享锁 与 lock_ex排他锁
参考网站:http://hi.baidu.com/honly1215/item/8d27a66d11689c3aac3e83fe 文件锁有两种:共享锁和排他锁,也就是读锁(LOCK_SH)和写锁(LO ...