加载一张图片到UI相对比较简单,如果一次要加载一组图片,就会变得麻烦很多。像ListView,GridView,ViewPager等控件,需要显示的图片和将要显示的图片数量可能会很大。

为了减少内存使用,这类控件都重复利用移出屏幕的子视图,如果你没有持用引用,垃圾回收器也会回收你加载过的图片。这种做法很好,但是如果想要图片加载快速流畅且不想当控件拖回来时重新运算获取加载过的图片,通常会使用内存和磁盘缓存。这节主要介绍当加载多张图片时利用内存缓存和磁盘缓存使加载图片时更快。

一、使用内存缓存

内存缓存以牺牲有限的应用内存为代价提供快速访问缓存的图片方法。LruCache类(有兼容包可以支持到API Level 4)很适合缓存图片的功能,它在LinkedHashMap中保存着最近使用图片对象的引用,并且在内容超过它指定的容量前删除近期最少使用的对象的引用。

注意:这前,很流行的图片缓存的方法是使用SoftReference和WeakReference,但是这种方法不提倡。因为从Android2.3(Level 9)开始,内存回收器会对软引用和弱引用进行回收。另外,在Android3.0(Levle 11)之前,图片的数据是存储在系统native内存中的,它的内存释放不可预料,这也是造成程序内存溢出的一个潜在原因。

为了给LruCache选择一个合适的大小,一些因素需要考虑:

》应用其它的模块对内存大小的要求

》有多少张图片会同时在屏幕上显示,有多少张图片需要提前加载

》屏幕的大小和密度

》图片的尺寸和设置

》图片被访问的频度

》平衡数量和质量,有时候存储大量的低质量的图片会比少量的高质量图片要有用

对于缓存,没有大小或者规则适用于所有应用,它依赖于你分析自己应用的内存使用确定自己的方案。缓存太小可能只会增加额外的内存使用,缓存太大可能会导致内存溢出或者应用其它模块可使用内存太小。

下面是为图片缓存设置LruCache的一个例子:

  1. private LruCache 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(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用来做缓存。在普通hdpi设备上这个值通常为4M(32/8)。一个全屏的GridView,尺寸为800x480大小通常为1.5M左右(800*480*4sbytes),所以在内存中可以缓存2.5张图片。

当一个ImageView加载图片时,先检查LruCache。如果缓存中存在,会用它马上更新ImageView,否则的话,启动一个后台线程来加载图片:

  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加载图片后,也要把图片缓存到内存中:

  1. class BitmapWorkerTask extends AsyncTask {
  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线程中完成。示例代码中是磁盘缓存的一个实现,在Android4.0源码中(

libcore/luni/src/main/java/libcore/io/DiskLruCache.java),有更加强大和推荐的一个实现,它的向后兼容使在已发布过的库中很方便使用它。下面是它的例子:

  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 {
  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线程中进行。当图片获取完成,把它同时缓存到内存中和磁盘中。

三、处理配置改变

运行时的配置改变,例如屏幕横竖屏切换,会导致系统结束当前正在运行的Activity并用新配置重新启动(可参考:http://developer.android.com/guide/topics/resources/runtime-changes.html)。为了有好的用户体验,你可能不想在这种情况下,重新获取一遍图片。幸好你可以使用上面讲的内存缓存。缓存可以通过使用一个Fragment(调用setRetainInstance(true)被传到新的Activity,当新的Activity被创建后,只需要重新附加Fragment,你就可以得到这个Fragment并访问到存在的缓存,把里面的图片快速的显示出来。下面是一个示例:
 
  1. private LruCache 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(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 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. setRetainInstance(true);
  31. }
  32. }

Android中高效的显示图片之三——缓存图片的更多相关文章

  1. Android中高效的显示图片之一 ——加载大图

    在网上看了不少文章,发现还是官方文档介绍最详细,把重要的东西简单摘要出来.详细可看官方文档地址 ( http://www.bangchui.org/read.php?tid=9 ) . 在应用中显示图 ...

  2. Android中高效的显示图片之二——在非UI线程中处理图片

    在“加载大图”文章中提到的BitmapFactory.decode*方法,如果源数据是在磁盘.网络或其它任何不是在内存中的位置,那么它都不应该在UI线程中执行.因为它的加载时间不可预测且依赖于一系列因 ...

  3. Android异步下载图片并且缓存图片到本地

    Android异步下载图片并且缓存图片到本地 在Android开发中我们经常有这样的需求,从服务器上下载xml或者JSON类型的数据,其中包括一些图片资源,本demo模拟了这个需求,从网络上加载XML ...

  4. Android训练课程(Android Training) - 高效的显示图片

    高效的显示图片(Displaying BitmapsEfficiently) 了解如何使用通用的技术来处理和读取位图对象,让您的用户界面(UI)组件是可响应的,并避免超过你的应用程序内存限制的方式.如 ...

  5. Android之简单了解Bitmap显示图片及缓存图片

    昨天我们学了如何连接网络,今天我们就学习一下如何从把网上图片显示到项目中 今天主要用到的是Bitmap 类 Bitmap是Android系统中的图像处理的最重要类之一.用它可以获取图像文件信息,进行图 ...

  6. [置顶] Android中使用Movie显示gif动态图

    转载请注明:  http://blog.csdn.net/u012975705/article/details/48717391 在看这篇博文之前对attr自定义属性还是不是很熟的童鞋可以先看看:An ...

  7. 【有人@我】Android中高亮变色显示文本中的关键字

    应该是好久没有写有关技术类的文章了,前天还有人在群里问我,说群主很长时间没有分享干货了,今天分享一篇Android中TextView在大段的文字内容中如何让关键字高亮变色的文章 ,希望对大家有所帮助, ...

  8. Android中高亮变色显示文本中的关键字

    应该是好久没有写有关技术类的文章了,前天还有人在群里问我,说群主很长时间没有分享干货了,今天分享一篇Android中TextView在大段的文字内容中如何让关键字高亮变色的文章 ,希望对大家有所帮助, ...

  9. android 删除SD卡或者手机的缓存图片和目录

    public static final String TEMP_PHOTO_FILE_NAME = "temp_photo.jpg"; private static String ...

随机推荐

  1. Shader 结构体中语义的理解

    Shader编写通常会遇到语义 1 float4 vert(float4:POSITION):SV_POSITION 2 { 3 return mul(UNITY_MATRIX_MVP,v); 4 } ...

  2. Tomcat startup.bat启动隐藏弹出的信息窗口

    to make tomcat to use javaw.exe instead of java.exe using some startup parameter or environment vari ...

  3. PIL+百度aip

    1.PIL模块安装 选择PIL 官方没有支持python3.6的PIL库,所以用pillow代替 http://www.lfd.uci.edu/~gohlke/pythonlibs/#pillow 链 ...

  4. iOS tableView自定义删除按钮

    // 自定义左滑显示编辑按钮 - (NSArray<UITableViewRowAction*>*)tableView:(UITableView *)tableView editActio ...

  5. uiwebview 屏幕自适应 -- 根据 内容适应或者 webview适应

    #import <UIKit/UIKit.h> @interface ViewController : UIViewController<UIWebViewDelegate,UISe ...

  6. jquery 通过属性选择器获取input不为disabled的对象

    $("input[id^='input_001']:not(:disabled)").each(function(){ console.log(this); });

  7. Hadoop DataNode 节点的动态添加和动态删除

    动态添加 DataNode 节点 hadoop环境是必须的 需要加入新的 DataNode 节点,前提是已经配置好 SSH 无密登录:直接复制已有DataNode中.ssh目录中的authorized ...

  8. 九度OJ 1001:A+B for Matrices

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:17682 解决:7079 题目描述: This time, you are supposed to find A+B where A and ...

  9. transport connector和network connector

    1 什么是transport connector 用于配置activemq服务器端和客户端之间的通信方式. 2 什么是network connector 用于配置activemq服务器之间的通信方式, ...

  10. The space of such functions is known as a reproducing kernel Hilbert space.

    Reproducing kernel Hilbert space Mapping the points to a higher dimensional feature space http://www ...