Android开发之 Memory 最佳实践
2015-06-11 22:24 阅读(180)

在决定应用的行为,是否有好的用户体验以及整体的设备体验来说,内存的使用可能是独立因素中最重要的。内存因素包括应用的内存占用,以及内存搅动(导致的垃圾回收会对运行期间的性能有影响)。

避免在循环中分配内存

内存分配虽然不可避免,但是应尽可能的避免,特别是在平凡的调用的代码块中。比如在绘制代码中,因为每一帧的渲染都会执行该方法。

避免在自定义View的onDraw方法中分配内存,因为动画也许会调用它。使用缓存对象替换临时对象可以避免新的内存开销。典型的例子就是在onDraw方法中分配了一个新的Paint对象,因为Canvas需要一个Paint对象。对于这种自定义View的实例只分配一个独立的Paint对象而后在onDraw方法中临时使用更好。

尽可能的避免内存分配

避免常量,临时变量的内存分配。下面有一些可参考的策略,也许并不适用于传统的java编码,但是对于Android开发是推荐的。通常可以使用工具帮助我们去决定是否某一块代码需要优化。如果代码的某一部分很少执行(比如用户改变一些设置的操作),更简洁和传统的抽象层是不错的选择。但是如果分析表明某些代码频繁执行并导致了大量的内存搅动,考虑以下策略:

Rect getRect(int w, int h)

考虑使用

void getRect(Rect rect, int w, int h)

避免Interators

明确的(List.iterator())或者不明确的(for(Object o : myObjects))使用Interator会导致一个Interator对象的内存分配。单独一个内存分配不是什么大事,但是应该尽量避免在内部循环中分配内存。当然,直接的使用角标进行集合遍历可以不用分配任何的内存。

final int count = myList.size();
for (int i = 0; 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提供了很多机制来确保组件只在特定的范围内运行:

优化代码大小

瘦身的应用会运行更快。加载的代码量越少,用户下载你的app的时间就会越少,你的应用也会更快的启动和初始化。下面是一些建议:

作者 : lightsky