在您的UI中显示单个图片是非常简单的,如果您需要一次显示很多图片就有点复杂了。在很多情况下

(例如使用 ListView, GridView 或者 ViewPager控件),

显示在屏幕上的图片以及即将显示在屏幕上的图片数量是非常大的(例如在图库中浏览大量图片)。

在这些控件中,当一个子控件不显示的时候,系统会重用该控件来循环显示 以便减少对内存的消耗。同时垃圾回收机制还会

释放那些已经载入内存中的Bitmap资源(假设您没有强引用这些Bitmap)。一般来说这样都是不错的,但是在用户来回滑动屏幕的时候,为了保证UI

的流畅性和载入图片的效率,您需要避免重复的处理这些需要显示的图片。 使用内存缓存和磁盘缓存可以解决这个问题,使用缓存可以让控件快速的加载已经处理过的图片。

这节内容介绍如何使用缓存来提高UI的载入输入和滑动的流畅性。

使用内存缓存

内存缓存提高了访问图片的速度,但是要占用不少内存。 LruCache

类(在API 4之前可以使用Support Library 中的类 )特别适合缓存Bitmap, 把最近使用到的

Bitmap对象用强引用保存起来(保存到LinkedHashMap中),当缓存数量达到预定的值的时候,把

不经常使用的对象删除。

注意: 过去,实现内存缓存的常用做法是使用

SoftReference 或者

WeakReference bitmap 缓存,

但是不推荐使用这种方式。从Android 2.3 (API Level 9) 开始,垃圾回收开始强制的回收掉 soft/weak 引用 从而导致这些缓存没有任何效率的提升。

另外,在 Android 3.0 (API Level 11)之前,这些缓存的Bitmap数据保存在底层内存(native memory)中,并且达到预定条件后也不会释放这些对象,从而可能导致

程序超过内存限制并崩溃。

在使用 LruCache 的时候,需要考虑如下一些因素来选择一个合适的缓存数量参数:

  • 程序中还有多少内存可用

  • 同时在屏幕上显示多少图片?要先缓存多少图片用来显示到即将看到的屏幕上?

  • 设备的屏幕尺寸和屏幕密度是多少?超高的屏幕密度(xhdpi 例如 Galaxy Nexus)

    设备显示同样的图片要比低屏幕密度(hdpi 例如 Nexus S)设备需要更多的内存。

  • 图片的尺寸和格式决定了每个图片需要占用多少内存

  • 图片访问的频率如何?一些图片的访问频率要比其他图片高很多?如果是这样的话,您可能需要把这些经常访问的图片放到内存中。

  • 在质量和数量上如何平衡?有些情况下保存大量的低质量的图片是非常有用的,当需要的情况下使用后台线程来加入一个高质量版本的图片。

这里没有万能配方可以适合所有的程序,您需要分析您的使用情况并在指定自己的缓存策略。使用太小的缓存并不能起到应有的效果,而使用太大的缓存会消耗更多

的内存从而有可能导致 java.lang.OutOfMemory 异常或者留下很少的内存供您的程序其他功能使用。

下面是一个使用 LruCache 缓存的示例:

[java] view
plain
copy

  1. private LruCache<string, bitmap=""> mMemoryCache;
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. ...
  5. // Get memory class of this device, exceeding this amount will throw an
  6. // OutOfMemory exception.
  7. final int memClass = ((ActivityManager) context.getSystemService(
  8. Context.ACTIVITY_SERVICE)).getMemoryClass();
  9. // Use 1/8th of the available memory for this memory cache.
  10. final int cacheSize = 1024 * 1024 * memClass / 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 bytes rather than number of items.
  15. return bitmap.getByteCount();
  16. }
  17. };
  18. ...
  19. }
  20. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
  21. if (getBitmapFromMemCache(key) == null) {
  22. mMemoryCache.put(key, bitmap);
  23. }
  24. }
  25. public Bitmap getBitmapFromMemCache(String key) {
  26. return mMemoryCache.get(key);
  27. }

注意: 在这个示例中,该程序的1/8内存都用来做缓存用了。在一个normal/hdpi设备中,这至少有4MB(32/8)内存。

在一个分辨率为 800×480的设备中,满屏的GridView全部填充上图片将会使用差不多1.5MB(800*480*4 bytes)

的内存,所以这样差不多在内存中缓存了2.5页的图片。

当在 ImageView 中显示图片的时候,

先检查LruCache 中是否存在。如果存在就使用缓存后的图片,如果不存在就启动后台线程去载入图片并缓存:

[java] view
plain
copy

  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. }

BitmapWorkerTask 需要把新的图片添加到缓存中:

[java] view
plain
copy

  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. }

使用磁盘缓存

在访问最近使用过的图片中,内存缓存速度很快,但是您无法确定图片是否在缓存中存在。像

GridView 这种控件可能具有很多图片需要显示,很快图片数据就填满了缓存容量。

同时您的程序还可能被其他任务打断,比如打进的电话 — 当您的程序位于后台的时候,系统可能会清楚到这些图片缓存。一旦用户恢复使用您的程序,您还需要重新处理这些图片。

在这种情况下,可以使用磁盘缓存来保存这些已经处理过的图片,当这些图片在内存缓存中不可用的时候,可以从磁盘缓存中加载从而省略了图片处理过程。

当然, 从磁盘载入图片要比从内存读取慢很多,并且应该在非UI线程中载入磁盘图片。

注意: 如果缓存的图片经常被使用的话,可以考虑使用

ContentProvider ,例如在图库程序中就是这样干滴。

在示例代码中有个简单的 DiskLruCache 实现。然后,在Android 4.0中包含了一个更加可靠和推荐使用的DiskLruCache(libcore/luni/src/main/java/libcore/io/DiskLruCache.java)

。您可以很容易的把这个实现移植到4.0之前的版本中使用(来 href="http://www.google.com/search?q=disklrucache">Google一下 看看其他人是否已经这样干了!)。

这里是一个更新版本的 DiskLruCache :

[java] view
plain
copy

  1. private DiskLruCache mDiskCache;
  2. private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
  3. private static final String DISK_CACHE_SUBDIR = "thumbnails";
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState) {
  6. ...
  7. // Initialize memory cache
  8. ...
  9. File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);
  10. mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE);
  11. ...
  12. }
  13. class BitmapWorkerTask extends AsyncTask<integer, void,="" bitmap=""> {
  14. ...
  15. // Decode image in background.
  16. @Override
  17. protected Bitmap doInBackground(Integer... params) {
  18. final String imageKey = String.valueOf(params[0]);
  19. // Check disk cache in background thread
  20. Bitmap bitmap = getBitmapFromDiskCache(imageKey);
  21. if (bitmap == null) { // Not found in disk cache
  22. // Process as normal
  23. final Bitmap bitmap = decodeSampledBitmapFromResource(
  24. getResources(), params[0], 100, 100));
  25. }
  26. // Add final bitmap to caches
  27. addBitmapToCache(String.valueOf(imageKey, bitmap);
  28. return bitmap;
  29. }
  30. ...
  31. }
  32. public void addBitmapToCache(String key, Bitmap bitmap) {
  33. // Add to memory cache as before
  34. if (getBitmapFromMemCache(key) == null) {
  35. mMemoryCache.put(key, bitmap);
  36. }
  37. // Also add to disk cache
  38. if (!mDiskCache.containsKey(key)) {
  39. mDiskCache.put(key, bitmap);
  40. }
  41. }
  42. public Bitmap getBitmapFromDiskCache(String key) {
  43. return mDiskCache.get(key);
  44. }
  45. // Creates a unique subdirectory of the designated app cache directory. Tries to use external
  46. // but if not mounted, falls back on internal storage.
  47. public static File getCacheDir(Context context, String uniqueName) {
  48. // Check if media is mounted or storage is built-in, if so, try and use external cache dir
  49. // otherwise use internal cache dir
  50. final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
  51. || !Environment.isExternalStorageRemovable() ?
  52. context.getExternalCacheDir().getPath() : context.getCacheDir().getPath();
  53. return new File(cachePath + File.separator + uniqueName);
  54. }

在UI线程中检测内存缓存,在后台线程中检测磁盘缓存。磁盘操作从来不应该在UI线程中实现。当图片处理完毕后,最终的结果会同时添加到

内存缓存和磁盘缓存中以便将来使用。

处理配置改变事件

运行时的配置变更 — 例如 屏幕方向改变 — 导致Android摧毁正在运行的Activity,然后使用

新的配置从新启动该Activity (详情,参考这里 Handling Runtime Changes)。

您需要注意避免在配置改变的时候导致重新处理所有的图片,从而提高用户体验。

幸运的是,您在 使用内存缓存 部分已经有一个很好的图片缓存了。该缓存可以通过

Fragment (Fragment会通过setRetainInstance(true)函数保存起来)来传递给新的Activity

当Activity重新启动 后,Fragment 被重新附加到Activity中,您可以通过该Fragment来获取缓存对象。

下面是一个在 Fragment中保存缓存的示例:

[java] view
plain
copy

  1. private LruCache<string, bitmap=""> mMemoryCache;
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. ...
  5. RetainFragment mRetainFragment =
  6. RetainFragment.findOrCreateRetainFragment(getFragmentManager());
  7. mMemoryCache = RetainFragment.mRetainedCache;
  8. if (mMemoryCache == null) {
  9. mMemoryCache = new LruCache<string, bitmap="">(cacheSize) {
  10. ... // Initialize cache here as usual
  11. }
  12. mRetainFragment.mRetainedCache = mMemoryCache;
  13. }
  14. ...
  15. }
  16. class RetainFragment extends Fragment {
  17. private static final String TAG = "RetainFragment";
  18. public LruCache<string, bitmap=""> mRetainedCache;
  19. public RetainFragment() {}
  20. public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
  21. RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
  22. if (fragment == null) {
  23. fragment = new RetainFragment();
  24. }
  25. return fragment;
  26. }
  27. @Override
  28. public void onCreate(Bundle savedInstanceState) {
  29. super.onCreate(savedInstanceState);
  30. <strong>setRetainInstance(true);</strong>
  31. }
  32. }

您可以尝试分别使用和不使用Fragment来旋转设备的屏幕方向来查看具体的图片载入情况。

使用LRU算法缓存图片,android 3.0的更多相关文章

  1. mybatis PageHelper分页插件 和 LRU算法缓存读取数据

    分页: PageHelper的优点是,分页和Mapper.xml完全解耦.实现方式是以插件的形式,对Mybatis执行的流程进行了强化,添加了总数count和limit查询.属于物理分页. 一.首先注 ...

  2. LRU算法 缓存淘汰策略

    四种实现方式 LRU 1.1. 原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也 ...

  3. Go -- LRU算法(缓存淘汰算法)(转)

    1. LRU1.1. 原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”. ...

  4. 借助LinkedHashMap实现基于LRU算法缓存

    一.LRU算法介绍 LRU(Least Recently Used)最近最少使用算法,是用在操作系统中的页面置换算法,因为内存空间是有限的,不可能把所有东西都放进来,所以就必须要有所取舍,我们应该把什 ...

  5. LRU算法---缓存淘汰算法

    计算机中的缓存大小是有限的,如果对所有数据都缓存,肯定是不现实的,所以需要有一种淘汰机制,用于将一些暂时没有用的数据给淘汰掉,以换入新鲜的数据进来,这样可以提高缓存的命中率,减少磁盘访问的次数. LR ...

  6. 缓存淘汰算法--LRU算法

    1. LRU1.1. 原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是"如果数据最近被访问过,那么将来被访问的几率也 ...

  7. 缓存淘汰算法--LRU算法(转)

    (转自:http://flychao88.iteye.com/blog/1977653) 1. LRU1.1. 原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访 ...

  8. Android 8.0 功能和 API

    Android 8.0 为用户和开发者引入多种新功能.本文重点介绍面向开发者的新功能. 用户体验 通知 在 Android 8.0 中,我们已重新设计通知,以便为管理通知行为和设置提供更轻松和更统一的 ...

  9. 使用guava实现找回密码的tokenCache以及LRU算法

    源码包的简单说明: com.google.common.annotations:普通注解类型. com.google.common.base:基本工具类库和接口. com.google.common. ...

随机推荐

  1. hdu 5592 BestCoder Round #65(树状数组)

    题意: ZYB有一个排列PP,但他只记得PP中每个前缀区间的逆序对数,现在他要求你还原这个排列. (i,j)(i < j)(i,j)(i<j)被称为一对逆序对当且仅当A_i>A_jA ...

  2. HDU1348 Wall 凸包

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1348 题意:给出一个凸包,求出与凸包距离 L的外圈周长 凸包模板题,练练Andrew算法求出凸包周长再 ...

  3. python设计模式浅析

    今天简单聊聊python的设计模式,GOF设计模式(c++)和Head first design pattern(Java)是两本设计模式的经典,基本可以照搬在python上面,但是你会发现pytho ...

  4. python 2week

    本节内容 列表.元组操作 字符串操作 字典操作 集合操作 文件操作 字符编码与转码 列表是我们最以后最常用的数据类型之一,通过列表可以对数据实现最方便的存储.修改等操作 定义列表 1 names =  ...

  5. Java 反射实现实体转Map时,父类元素丢失

    public class BeanToMap { public static Map<String, Object> ConvertObjToMap(Object obj) { Map&l ...

  6. 47. Permutations II(medium, backtrack, 重要, 条件较难思考)

    Given a collection of numbers that might contain duplicates, return all possible unique permutations ...

  7. 89. Gray Code(中等,了解啥是 gray code)

    知道啥是 gray code 就是收获了. 下面介绍了 gray code 发明的 motivation, 了解动机后就知道啥是 gray code 了. https://zh.wikipedia.o ...

  8. iOS开发-文件管理

    iOS学习笔记(十七)--文件操作(NSFileManager) 浅析 RunLoop 解决EXC_BAD_ACCESS错误的一种方法--NSZombieEnabled iOS开发--Swift篇&a ...

  9. 使用Java可以做得一些事

    安卓 Web JSP使用Echarts的最简单的例子 微信 wechat4j weixin-java-tools weixin4j 网络服务器

  10. SpringBatch配置数据库

    Spring Batch提供一个任务的仓库实现将任务元数据保存在数据库中,以便监控你的批量处理进程以及其结果. Spring Batch 数据库引擎支持的有:DB2,Derby, H2, HSQLDB ...