在app中通常最占内存、占流量的元素就是图片了,图片往往又无处不在,特别是伴随着list,GridView或者ViewPager出现,这些图片随着你的滑动操作,时而出现在你的屏幕中,时而消失在屏幕之外。

对应滑出屏幕之外的图片,你可以缓存在内存中以便下次加载快速渲染,但这回增加内存的开销,你也可以立即释放掉这部分内存,但下次加载会变的很慢,因为来讲回收影响UI渲染,获取图片资源更加事一个耗时的过程。所以怎么样才能做到节省内存的开销又能提高加载速度?这是一个策略平衡问题,取决于你如何去使用 memory cachedisk cache来缓存Bitmap对象。

  • 使用Memory Cache(软引用、弱引用还在流行?)

memory cache 能使你从你的应用内存空间快速的访问到你的Bitmap对象。对应Bitmap的缓存,LruCache(Least Recently Used)应运而生,关于LruCache的介绍请看官方文档https://developer.android.com/reference/android/util/LruCache.html(翻墙),简单的说
LruCache使用强引用方式把最近使用的内存对象使用LinkedHashMap存储起来,在你使用LruCache时需要设置一个最大缓存值,当内存即将接近这个最大值的时候,它将帮你把那些 Least Recently Used 的内存对象释放掉。在过去,一个通常的 memory cache 实现基本上是使用软引用或弱引用来缓存bitmap,然而现在已经不推荐使用了,为什么呢?一、从 android 2.3 以后,垃圾回收器对应软引用和弱引用的回收变动十分积极,这使得缓存的意义在极大程度上丢失;二, 在android 3.0 以前bitmpa的内存是存储在native内存中的,使得垃圾回收器很难回收,对应内存的预算很难把握。

使用LruCache,那么对于最大缓存值设置是一门艺术,你需要考虑很多因素。例如:

  • 你的 activity 使用了多少内存?
  • 有多少张图片会同时出现在你的屏幕中?
  • 你的缓存的图片被访问的频率是多少?
  • 你对图片显示清新度的取舍?

总之,没有一个固定的值适合所有的app,取决于你的app的具体身的很多因素,设置太小可能会降低你使用LruCache的福利,设置太大,在缓存峰值时候可能会引起OOM,这里有个例子参考:

private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
...
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}

在这个例子中,使用了应用最大内存的1/8最为LruCache的最大值。

加载Bitmap对象到ImageView的经典模型

通常我们会先到 LruCache 中去检测一下存不存在,如果存在直接更新ImageView;如果不存在则开启一个线程去获取Bitmap对象(通常是到网络上获取,也有可能从disk中读取),然后再把这个Bitmap对象缓存到LruCache中。例如:

public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = String.valueOf(resId); final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
mImageView.setImageBitmap(bitmap);
} else {
mImageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
task.execute(resId);
}
}
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}

 

  • 使用disk缓存(硬盘缓存)

memory cache 在快速访问Bitmap上十分有用,然而我们不能一直依赖它,为什么呢?对于像GridView这样承载大量图片的组件来说,memory cache 会很快就被使用殆尽。另外当我们的应用被切换到后台的时候或者像来电话等这样的高优先级应用启用的时候,我们的app内存很可能会被回收,甚至LruCache对象也可能会销毁,一旦app再次切换到前台的话,所有的Bitmap对象都重新获取(通常网络请求),从而影响体验而且耗费流量。于是DiskLruCache出场了,关于DiskLruCache实现源码,有兴趣深究的可以点击这里查看。先来看一个在使用LruCache的基础上使用DiskLruCache的例子:

private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails"; @Override
protected void onCreate(Bundle savedInstanceState) {
...
// Initialize memory cache
...
// Initialize disk cache on background thread
File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
new InitDiskCacheTask().execute(cacheDir);
...
} class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
@Override
protected Void doInBackground(File... params) {
synchronized (mDiskCacheLock) {
File cacheDir = params[0];
mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
mDiskCacheStarting = false; // Finished initialization
mDiskCacheLock.notifyAll(); // Wake any waiting threads
}
return null;
}
} class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
final String imageKey = String.valueOf(params[0]); // Check disk cache in background thread
Bitmap bitmap = getBitmapFromDiskCache(imageKey); if (bitmap == null) { // Not found in disk cache
// Process as normal
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100));
} // Add final bitmap to caches
addBitmapToCache(imageKey, bitmap); return bitmap;
}
...
} public void addBitmapToCache(String key, Bitmap bitmap) {
// Add to memory cache as before
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
} // Also add to disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
mDiskLruCache.put(key, bitmap);
}
}
} public Bitmap getBitmapFromDiskCache(String key) {
synchronized (mDiskCacheLock) {
// Wait while disk cache is started from background thread
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
if (mDiskLruCache != null) {
return mDiskLruCache.get(key);
}
}
return null;
} // Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName);
}

注意:所有的disk读取操作都不应该发生在UI线程中,当从网络中获取Bitmap对象后应该同时保存到LruCache中和LruDiskCache中以便后续使用。

推荐:

内存泄露就应该用LeakCanary

最新文章

  1. RabbitMQ学习系列(二): RabbitMQ安装与配置
  2. String的按值传递,java传参都是传值
  3. X264库直接压缩BITMAP格式数据
  4. linux命令后台运行
  5. new-nav-css
  6. Android开发之创建桌面快捷方式
  7. 蛋疼的Apple IOS Push通知协议
  8. ABP入门系列(1)——通过模板创建MAP版本项目
  9. abap 常用 function
  10. [ZJOI2005]九数码游戏(BFS+hash)
  11. 100-days: eleven
  12. adb查看安卓设备系统Android版本
  13. 对类方法进行约束(类的抽象方法ABC+raise抛出异常 )
  14. 《Unix&amp;Linux大学教程》学习笔记5 :正则表达式
  15. iOS应用图标及尺寸
  16. 使用iptables禁止外网访问tomcat的8080端口
  17. DELPHI 数据集在内存中快速查询方法
  18. 了解Linux的进程与线程
  19. HTML常用的标签中,行内元素和块级元素
  20. FFmpeg 入门(6):音频同步

热门文章

  1. Eclipse中安装svn的插件安装和使用
  2. 【bzoj4177】Mike的农场 网络流最小割
  3. Window系统命令行调用控制面板程序
  4. 「JOISC 2015 Day 1」卡片占卜
  5. eclipse增加jar包方式对比
  6. Python之多线程:线程互斥与线程同步
  7. python注解(装饰器)的用法
  8. 学习webservice
  9. MFC中GetParent()的作用(转)
  10. Synthesis of memory barriers