Google Developing for Android 二 - Memory 最佳实践 // lightSky‘Blog

Google Developing for Android 二 - Memory 最佳实践

|   分类于 Android最佳实践

原文: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是一种很便利的集合也不会造成太多的分配。它会再分配,并且会复制当前的数组添加到列表后面,但是设置一个合理的初始化容量可以避免频繁的分配内存。如果你的集合不需要动态的变化大小,考虑使用Array
  • Andorid集合类
    除非你需要一个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.();    ( i = ; i < count; ++i) {       Object o = myList.get(i);

值得注意的是,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。
  • 确保你自己理解那些自动化生成代码的消耗
  • 越简单越好,直接解决问题,而不是创造出大量的结构和抽象去解决问题
时间: 2024-10-22 22:22:26

Google Developing for Android 二 - Memory 最佳实践 // lightSky‘Blog的相关文章

Android企业级应用程序开发完整训练:精通Android商业级开发最佳实践的24堂课

从企业级商业实战的角度入手,24小时内通过23个动手实战案例,循序渐进的对Android商业级别的应用程序开发要点各个击破,依托于在多年的Android(6款完整的硬件产品和超过20款应用软件)开发和企业级培训经验(超过150期的次Android的企业内训和公开课),旨在在实务的基础之上帮助你完成任何复杂程序的高质量Android应用程序开发,让Android开发跟上想象的速度.最后,通过ActivityManagerService揭秘Android应用程序一切行为背后的核心根源,让你从此开发应

Google Developing for Android 一 - 相关上下文介绍

前几天在G+上看到Google Developers站点,有一个Android系列的文章,分享到个人微博,周末闲来没事就学写了下,把它们简单的翻译了下,没想到一发不可收拾,六篇文章全部都翻译完了,有些地方省略了部分示例的描述或者换了另一种表述,如果有理解的不准确的地方,还望指正 原文:Developing for Android, I:Understanding the Mobile Context context或者这些建议为何如此重要 对于理解这些最佳实践的相关上下文是非常重要的.特别是明白

Android 应用兼容性最佳实践 | 中文教学视频

本期中文视频向各位开发者介绍如何现代化您的应用,其中包括 Android?O.Android P?(预览版)?的新特性.行为变更.应用开发中常见的兼容性最佳实践,以及测试.兼容系统时需要注意的一些事项.希望大家尽快将开发的应用兼容新的 Android 系统. ?我们一起来看下视频 讲解? 视频中提到的文档链接: >> targetSdkVersion 要求:https://goo.gl/XM9B5Z >> targetSdkVersion 升级指南:https://goo.gl/Y

OPEN(SAP) UI5 学习入门系列之二: 最佳实践练习之一

这篇博文难产了很久,原来是打算一周更新一篇的,上周原计划写MVC,但是写了一半,发现带入了太多的细节,不太符合这个入门系列的主题. 当我们学习一个新的技能的时候,如果一开始就面对大量的细节,很容易陷入其中而只见树木不见森林,所以最后我想我们还是先按照开发文档的节奏,一起来做UI5的最佳实践练习.在练习中对常用的一些控件以及API有一个直观的感受,如果需要细节的信息再去查文档. 这个最佳实践练习的子系列又会分为若干篇,但是不会完全按照Tutorial里面的章节来分,因为我希望每一篇都是都是一个完整

Android组件化最佳实践 ARetrofit原理

简介 ARetrofit是一款针对Android组件之间通信的框架,实现组件之间解耦的同时还可以通信. 源码链接:https://github.com/yifei8/ARetrofit欢迎star.issues.fork 组件化 Android组件化已经不是一个新鲜的概念了,出来了已经有很长一段时间了,大家可以自行Google,可以看到一堆相关的文章. 简单的来说,所谓的组件就是Android Studio中的Module,每一个Module都遵循高内聚的原则,通过ARetrofit来实现无耦合

观察者模式在android 上的最佳实践

在上一篇文章中介绍了介绍了观察者模式的定义和一些基本概念,观察者模式在 android开发中应用还是非常广泛的,例如android按钮事件的监听.广播等等,在任何类似于新闻-订阅的模式下面都可以使用.从某种意义上面来说android有点像JAVA EE的WEB页面,在都需要提供View层用于进行操作,在多个页面之间传递数据发送通知都是一件很麻烦的事情. 在android中从A页面跳转到B页面,然后B页面进行某些操作后需要通知A页面去刷新数据,我们可以通过startActivityForResul

HTTPS理论基础及其在Android中的最佳实践

我们知道,HTTP请求都是明文传输的,所谓的明文指的是没有经过加密的信息,如果HTTP请求被黑客拦截,并且里面含有银行卡密码等敏感数据的话,会非常危险.为了解决这个问题,Netscape 公司制定了HTTPS协议,HTTPS可以将数据加密传输,也就是传输的是密文,即便黑客在传输过程中拦截到数据也无法破译,这就保证了网络通信的安全. 密码学基础 在正式讲解HTTPS协议之前,我们首先要知道一些密码学的知识. 明文: 明文指的是未被加密过的原始数据. 密文:明文被某种加密算法加密之后,会变成密文,从

Android 轮询最佳实践 Service + AlarmManager+Thread

android中涉及到将服务器中数据变化信息通知用户一般有两种办法,推送和轮询. 消息推送是服务端主动发消息给客户端,因为第一时间知道数据发生变化的是服务器自己,所以推送的优势是实时性高.但服务器主动推送需要单独开发一套能让客户端持久连接的服务端程序,不过现在已经有很多开源的代码实现了基于xmmp协议的推送方案,而且还可以使用谷歌的推送方案.但有些情况下并不需要服务端主动推送,而是在一定的时间间隔内客户端主动发起查询. 譬如有这样一个app,实时性要求不高,每天只要能获取10次最新数据就能满足要

Hush Framework框架配置(续) 转自《Android和PHP最佳实践》官方站

图书资源下载 Xampp 开发环境下载:http://pan.baidu.com/share/link?shareid=531771&uk=773037279 微博实例完整源码包下载:http://pan.baidu.com/share/link?shareid=531769&uk=773037279 Hush Framework 框架源码及相关资源下载:http://code.google.com/p/hush-framework/downloads/list 其他 Android 客户