在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,这里有个例子参考:

  1. private LruCache<String, Bitmap> mMemoryCache;
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. ...
  5. // Get max available VM memory, exceeding this amount will throw an
  6. // OutOfMemory exception. Stored in kilobytes as LruCache takes an
  7. // int in its constructor.
  8. final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
  9. // Use 1/8th of the available memory for this memory cache.
  10. final int cacheSize = maxMemory / 8;
  11. mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
  12. @Override
  13. protected int sizeOf(String key, Bitmap bitmap) {
  14. // The cache size will be measured in kilobytes rather than
  15. // number of items.
  16. return bitmap.getByteCount() / 1024;
  17. }
  18. };
  19. ...
  20. }
  21. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
  22. if (getBitmapFromMemCache(key) == null) {
  23. mMemoryCache.put(key, bitmap);
  24. }
  25. }
  26. public Bitmap getBitmapFromMemCache(String key) {
  27. return mMemoryCache.get(key);
  28. }

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

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

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

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

 

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

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

  1. private DiskLruCache mDiskLruCache;
  2. private final Object mDiskCacheLock = new Object();
  3. private boolean mDiskCacheStarting = true;
  4. private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
  5. private static final String DISK_CACHE_SUBDIR = "thumbnails";
  6.  
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. ...
  10. // Initialize memory cache
  11. ...
  12. // Initialize disk cache on background thread
  13. File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
  14. new InitDiskCacheTask().execute(cacheDir);
  15. ...
  16. }
  17.  
  18. class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
  19. @Override
  20. protected Void doInBackground(File... params) {
  21. synchronized (mDiskCacheLock) {
  22. File cacheDir = params[0];
  23. mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
  24. mDiskCacheStarting = false; // Finished initialization
  25. mDiskCacheLock.notifyAll(); // Wake any waiting threads
  26. }
  27. return null;
  28. }
  29. }
  30.  
  31. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
  32. ...
  33. // Decode image in background.
  34. @Override
  35. protected Bitmap doInBackground(Integer... params) {
  36. final String imageKey = String.valueOf(params[0]);
  37.  
  38. // Check disk cache in background thread
  39. Bitmap bitmap = getBitmapFromDiskCache(imageKey);
  40.  
  41. if (bitmap == null) { // Not found in disk cache
  42. // Process as normal
  43. final Bitmap bitmap = decodeSampledBitmapFromResource(
  44. getResources(), params[0], 100, 100));
  45. }
  46.  
  47. // Add final bitmap to caches
  48. addBitmapToCache(imageKey, bitmap);
  49.  
  50. return bitmap;
  51. }
  52. ...
  53. }
  54.  
  55. public void addBitmapToCache(String key, Bitmap bitmap) {
  56. // Add to memory cache as before
  57. if (getBitmapFromMemCache(key) == null) {
  58. mMemoryCache.put(key, bitmap);
  59. }
  60.  
  61. // Also add to disk cache
  62. synchronized (mDiskCacheLock) {
  63. if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
  64. mDiskLruCache.put(key, bitmap);
  65. }
  66. }
  67. }
  68.  
  69. public Bitmap getBitmapFromDiskCache(String key) {
  70. synchronized (mDiskCacheLock) {
  71. // Wait while disk cache is started from background thread
  72. while (mDiskCacheStarting) {
  73. try {
  74. mDiskCacheLock.wait();
  75. } catch (InterruptedException e) {}
  76. }
  77. if (mDiskLruCache != null) {
  78. return mDiskLruCache.get(key);
  79. }
  80. }
  81. return null;
  82. }
  83.  
  84. // Creates a unique subdirectory of the designated app cache directory. Tries to use external
  85. // but if not mounted, falls back on internal storage.
  86. public static File getDiskCacheDir(Context context, String uniqueName) {
  87. // Check if media is mounted or storage is built-in, if so, try and use external cache dir
  88. // otherwise use internal cache dir
  89. final String cachePath =
  90. Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
  91. !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
  92. context.getCacheDir().getPath();
  93.  
  94. return new File(cachePath + File.separator + uniqueName);
  95. }

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

转自:大利猫

缓存你的BITMAP对象的更多相关文章

  1. Android如何缓存你的BITMAP对象

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

  2. 获取View的截图-将View转换为Bitmap对象

    开发中,有时候需要获取View的截图来做动画来达到动画流程的目的 原理:将View的内容画到一个Bitmap画布上,然后取出 下面封装了一个从View生成Bitmap的工具类 /** * 将View转 ...

  3. 缓存依赖中cachedependency对象

    缓存依赖主要提供以下功能:1.SQL 缓存依赖项可用于应用程序缓存和页输出缓存.2.可在 SQL Server 7.0 及更高版本中使用 SQL 缓存依赖项.3.可以在网络园(一台服务器上存在多个处理 ...

  4. Android中将Bitmap对象以PNG格式保存在内部存储中

    在Android中进行图像处理的任务时,有时我们希望将处理后的结果以图像文件的格式保存在内部存储空间中,本文以此为目的,介绍将Bitmap对象的数据以PNG格式保存下来的方法. 1.添加权限 由于是对 ...

  5. android——获取ImageView上面显示的图片bitmap对象

    获取的函数方法为:Bitmap bitmap=imageView.getDrawingCache(); 但是如果只是这样写我们得到的bitmap对象可能为null值,正确的方式为: imageView ...

  6. Activity跳转时传递Bitmap对象的实现

    前言 相信大家可能都了解Activity跳转时我们是能够传递參数的,比方使用Intent传递或者Bundle来传递,把当前Activity的一些信息传递给将要跳转到的新的Activity.可是不知道大 ...

  7. android Bitmap(将视图转为bitmap对象)

    1)从android的资源文件夹layout中加载xml布局文件,并把布局文件映射为Bitmap main.xml文件如下: <?xmlversion="1.0"encodi ...

  8. ResDrawableImgUtil【根据图片名称获取resID值或者Bitmap对象】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 根据图片名称获取项目的res/drawable-xxdhpi中相应资源的ID值以及bitmap值的封装类. 效果图 代码分析 根据图 ...

  9. Android中Bitmap对象和字节流之间的相互转换

    android 将图片内容解析成字节数组,将字节数组转换为ImageView可调用的Bitmap对象,图片缩放,把字节数组保存为一个文件,把Bitmap转Byte   import java.io.B ...

随机推荐

  1. 利用IDE编写C语言程序的一点注意事项

    前言:我是喜欢编程的一只菜鸟,在自学过程中,对遇到的一些问题和困惑,有时虽有一点体会感悟,但时间一长就会淡忘,很不利于知识的积累.因此,想通过博客园这个平台,一来记录自己的学习体会,二来便于向众多高手 ...

  2. JS 原型链图形详解

    JS原型链 这篇文章是「深入ECMA-262-3」系列的一个概览和摘要.每个部分都包含了对应章节的链接,所以你可以阅读它们以便对其有更深的理解. 对象 ECMAScript做为一个高度抽象的面向对象语 ...

  3. PL/SQL中如何执行DDL、SCL?

    PL/SQL程序中不能直接执行DDL语句.为什么? 假设我们在pl/sql程序中有这样的一条DDL语句—— drop table emp:在第一次解析pl/sql中的“drop table emp;” ...

  4. ContextMenuStrip控件

    设置单击窗体右键菜单 注意如果想在form1中显示右键菜单,那么要设置form1的属性

  5. C#.Net EF实体框架入门视频教程

    当前位置: 主页 > 编程开发 > C_VC视频教程 > C#.Net EF实体框架入门视频教程 > kingstone金士顿手机内存卡16G仅65元 1.EF实体框架之增加查 ...

  6. Windows.Andy.Code4App.dll Win8.1/WP8.1通用类库@ver1.0.0

    直接入题! Win8.1和WP8.1眼下已经渐渐融为一体,WP8.1不断向Win8.1靠拢,虽然一些方法上WP8.1和Win8.1不同(ps:WP8.1和Win8.1的不同之处),但大部分还是相同的. ...

  7. iOS5编程--ARC在工程上的相关设置

    在创建工程的时候,我们可以指定工程是否使用ARC技术,如下图 选中表示支持ARC, 在Beta5以前的版本中,不提供这个选项,非常麻烦. 如果是你拿到的工程,那么可以通过设置来改变,如下图所示 如果不 ...

  8. Workspace in use or cannot be created, choose a different one.--错误解决办法

    eclipse 使用一段时间后,有时会因为一些故障自己就莫名奇妙的关闭了,再打开时有时没有问题,有时有会提示错误 Workspace Unavailable: 原因:出现这种情况一般是workspac ...

  9. PreparedStatement是如何大幅度提高性能的

    本文讲述了如何正确的使用prepared statements.为什么它可以让你的应用程序运行的更快,和同样的让数据库操作变的更快.  为什么Prepared Statements非常重要?如何正确的 ...

  10. Linux资源监控_Nmon

    性能测试中,各个服务器资源占用统计分析是一个很重要的组成部分,通常我们使用nmon这个工具来进行监控以及监控结果输出. 一. 在监控阶段使用类似下面的命令 ./nmon -f write_3s_20v ...