已经大半年不碰Android了,以下是一些记忆中的经验,供参考。
首先确实是内存问题,这些都是DalvikVM的GC日志,格式如下:
[GC原因] [释放了多少VM内存], [VM堆内存统计], [外部内存统计], [VM暂停时间]
其中[原因]的内容可以参考Android源码中/dalvik/vm/alloc/Heap.h中enum GcReason的定义:
typedef enum {
/* Not enough space for an "ordinary" Object to be allocated. */
GC_FOR_MALLOC,
/* Automatic GC triggered by exceeding a heap occupancy threshold. */
GC_CONCURRENT,
/* Explicit GC via Runtime.gc(), VMRuntime.gc(), or SIGUSR1. */
GC_EXPLICIT,
/* GC to try to reduce heap footprint to allow more non-GC'ed memory. */
GC_EXTERNAL_ALLOC,
/* GC to dump heap contents to a file, only used under WITH_HPROF */
GC_HPROF_DUMP_HEAP
} GcReason;
你日志中的GC_EXPLICIT就是说有调用过Runtime.gc()或VMRuntime.gc()(可能是SDK调的)。然后[VM暂停时间]是“hold the whole world”,就是所有东西都停止,会非常影响体验,就是造成你觉得操作卡顿的主要原因。
然后是解决方案。在ListView/GridView之类的东西里面展现图片很容易就会遇上内存问题,说白了就是内存里的图片太大内存不足,好点的引发GC卡顿,差点的就直接OOM了。所以有几个解决思路:
1. 减少图片文件大小
调整图片大小或是降低图片色彩值或是修改图片格式都可以有效降低图片文件大小。例如色彩较丰富的图片jpg就比png要小一些,能做点九的尽量点九,减少色彩值数量可以显著降低png图片大小,特别是如果可以控制在256种颜色之内就可以启动png的色彩索引方案,加大jpg的压缩比例也可以让图片小很多。此方案的缺点则是图片必须是由我们自己处理的,例如静态资源或是从自己服务器获取的图,可以做预先处理。
2. 减少图片在内存中的大小
图片文件有多大并不代表它在内存里就占用多少空间,因为我们可以在图片加载过程中对其数据进行一些修改以达到减少在内存中占用的效果。一般来说有两种办法:
1) 动态修改图片色值,例如从8888改为565这种,以前我们用过对大图片非常有效。
2) 先用仅读取图片的bounds,然后依此缩小图片(代码网上很多),销毁掉大图仅使用小图,再做显示处理,也是一个常用方法。
3. 更好的利用内存
首先要理解Android中bitmap用的是native的内存,你可以理解为是C用的那段内存,与java用的不在一起。Android从3.2版本以后加强了这块的管理,使得native内存会随着JVM的GC一起回收,所以会大大减少这块的问题(3.2之前的版本也会回收,但因为与Java GC不同步所以一般回收不及时就会暴内存溢出,因此需要开发者手工调用recycle())
然后遵循一条法则:“永远只存放要显示的图片在内存中”,也就是说只有用户看得见的图片才在内存里,看不见的就销毁(recycle()),需要时再重新加载。这点与ListView的滑动窗口是一个道理。
要做到这些,recycle()总是需要自己写逻辑(对于3.2之前的版本),但留在JVM内存的那部分可以交给系统来管理,以前较为常用的建议是SoftReference和WeakReference,现在一般建议使用Android自带的缓存类LruCache或LruDiskCache,它会自动按照LRU算法销毁队列尾部的元素(最不常用),而非看GC心情,使用参见 Android Developer - Caching Bitmaps
另外楼主也应该尽量减少View的个数,能合并的尽量合并,尽量少使用重量级的View(可以查看View的源码由其成员变量组成来确定是轻是重)。如果以上这些都做了依然有大量的GC,建议在业务可接受的范围内与产品讨论简化界面。