2019独角兽企业重金招聘Python工程师标准>>>

转载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-) 

http://my.oschina.net/ryanhoo/blog/88443

译者:Ryan Hoo

来源:https://developer.android.com/develop/index.html

译者按: 在Google最新的文档中,提供了一系列含金量相当高的教程。因为种种原因而鲜为人知,真是可惜!Ryan将会细心整理,将之翻译成中文,希望对开发者有所帮助。

本系列是Google关于展示大Bitmap(位图)的官方演示,可以有效的解决内存限制,更加有效的加载并显示图片,同时避免让人头疼的OOM(Out Of Memory)。

-------------------------------------------------------------------------------------

译文:

加载一个Bitmap(位图)到你的UI界面是非常简单的,但是如果你要一次加载一大批,事情就变得复杂多了。在大多数的情况下(如ListView、GridView或者ViewPager这样的组件),屏幕上的图片以及马上要在滚动到屏幕上显示的图片的总量,在本质上是不受限制的。

像这样的组件在子视图移出屏幕后会进行视图回收,内存使用仍被保留。但假设你不保留任何长期存活的引用,垃圾回收器也会释放你所加载的Bitmap。这自然再好不过了,但是为了保持流畅且快速加载的UI,你要避免继续在图片回到屏幕上的时候重新处理。使用内存和硬盘缓存通常能解决这个问题,使用缓存允许组件快速加载并处理图片。

这节课将带你使用内存和硬盘缓存Bitmap,以在加载多个Bitmap的时候提升UI的响应性和流畅性。

使用内存缓存

以牺牲宝贵的应用内存为代价,内存缓存提供了快速的Bitmap访问方式。LruCache类(可以在Support Library中获取并支持到API  Level 4以上,即1.6版本以上)是非常适合用作缓存Bitmap任务的,它将最近被引用到的对象存储在一个强引用的LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉。

注意:以前有一个非常流行的内存缓存实现是SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,然而现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,这使得上述的方案相当无效。此外,Android 3.0(API Level 11)之前的版本中,Bitmap的备份数据直接存储在本地内存中并以一种不可预测的方式从内存中释放,很可能短暂性的引起程序超出内存限制而崩溃。

为了给LruCache选择一个合适的大小,要考虑到很多原因,例如:

  • 其他的Activity(活动)和(或)程序都是很耗费内存的吗?
  • 屏幕上一次会显示多少图片?有多少图片将在屏幕上显示?
  • 设备的屏幕大小和密度是多少?一个超高清屏幕(xhdpi)的设备如Galaxy Nexus,相比Nexus S(hdpi)来说,缓存同样数量的图片需要更大的缓存空间。
  • Bitmap的尺寸、配置以及每张图片需要占用多少内存?
  • 图片的访问是否频繁?有些会比其他的更加被频繁的访问到吗?如果是这样,也许你需要将某些图片一直保留在内存中,甚至需要多个LruCache对象分配给不同组的Bitmap。
  • 你能平衡图片的质量和数量么?有的时候存储大量低质量的图片更加有用,然后可以在后台任务中加载另一个高质量版本的图片。

对于设置缓存大小,并没有适用于所有应用的规范,它取决于你在内存使用分析后给出的合适的解决方案。缓存空间太小并无益处,反而会引起额外的开销,而太大了又可能再次引起java.lang.OutOfMemory异常或只留下很小的空间给应用的其他程序运行。

这里有一个设置Bitmap的LruCache示例:

private LruCache<String, Bitmap> mMemoryCache;@Override
protected void onCreate(Bundle savedInstanceState) {...// Get memory class of this device, exceeding this amount will throw an// OutOfMemory exception.final int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();// Use 1/8th of the available memory for this memory cache.final int cacheSize = 1024 * 1024 * memClass / 8;mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap bitmap) {// The cache size will be measured in bytes rather than number of items.return bitmap.getByteCount();}};...
}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的应用内存被分配给缓存。在一个普通的/hdpi设备上最低也在4M左右(32/8)。一个分辨率为800*480的设备上,全屏的填满图片的GridView占用的内存约1.5M(800*480*4字节),因此这个大小的内存可以缓存2.5页左右的图片。

当加载一个Bitmap到ImageView中,先要检查LruCache。如果有相应的数据,则立即用来更新ImageView,否则将启动后台线程来处理这个图片。

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);}
}

BitmapWorkerTask 也需要更新内存中的数据:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {...// Decode image in background.@Overrideprotected Bitmap doInBackground(Integer... params) {final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);return bitmap;}...
}

使用硬盘缓存

一个内存缓存对加速访问最近浏览过的Bitmap非常有帮助,但是你不能局限于内存中的可用图片。GridView这样有着更大的数据集的组件可以很轻易消耗掉内存缓存。你的应用有可能在执行其他任务(如打电话)的时候被打断,并且在后台的任务有可能被杀死或者缓存被释放。一旦用户重新聚焦(resume)到你的应用,你得再次处理每一张图片。

在这种情况下,硬盘缓存可以用来存储Bitmap并在图片被内存缓存释放后减小图片加载的时间(次数)。当然,从硬盘加载图片比内存要慢,并且应该在后台线程进行,因为硬盘读取的时间是不可预知的。

注意:如果访问图片的次数非常频繁,那么ContentProvider可能更适合用来存储缓存图片,例如Image Gallery这样的应用程序。

这个类中的示例代码使用DiskLruCache(来自Android源码)实现。在示例代码中,除了已有的内存缓存,还添加了硬盘缓存。

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 threadFile cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);new InitDiskCacheTask().execute(cacheDir);...
}class InitDiskCacheTask extends AsyncTask<File, Void, Void> {@Overrideprotected Void doInBackground(File... params) {synchronized (mDiskCacheLock) {File cacheDir = params[0];mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);mDiskCacheStarting = false; // Finished initializationmDiskCacheLock.notifyAll(); // Wake any waiting threads}return null;}
}class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {...// Decode image in background.@Overrideprotected Bitmap doInBackground(Integer... params) {final String imageKey = String.valueOf(params[0]);// Check disk cache in background threadBitmap bitmap = getBitmapFromDiskCache(imageKey);if (bitmap == null) { // Not found in disk cache// Process as normalfinal Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));}// Add final bitmap to cachesaddBitmapToCache(imageKey, bitmap);return bitmap;}...
}public void addBitmapToCache(String key, Bitmap bitmap) {// Add to memory cache as beforeif (getBitmapFromMemCache(key) == null) {mMemoryCache.put(key, bitmap);}// Also add to disk cachesynchronized (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 threadwhile (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 dirfinal String cachePath =Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :context.getCacheDir().getPath();return new File(cachePath + File.separator + uniqueName);
}

注意:即便是硬盘缓存初始化也需要硬盘操作,因此不应该在主线程执行。但是,这意味着硬盘缓存在初始化前就能被访问到。为了解决这个问题,在上面的实现中添加了一个锁对象(lock object),以确保在缓存被初始化之前应用无法访问硬盘缓存。

在UI线程中检查内存缓存,相应的硬盘缓存检查应在后台线程中进行。硬盘操作永远不要在UI线程中发生。当图片处理完成后,最终的Bitmap要被添加到内存缓存和硬盘缓存中,以便后续的使用。

 处理配置更改

运行时的配置会发生变化,例如屏幕方向的改变,会导致Android销毁并以新的配置重新启动Activity(关于此问题的更多信息,请参阅Handling Runtime Changes)。为了让用户有着流畅而快速的体验,你需要在配置发生改变的时候避免再次处理所有的图片。

幸运的是,你在“使用内存缓存”一节中为Bitmap构造了很好的内存缓存。这些内存可以通过使用Fragment传递到信的Activity(活动)实例,这个Fragment可以调用setRetainInstance(true)方法保留下来。在Activity(活动)被重新创建后,你可以在上面的Fragment中访问到已经存在的缓存对象,使得图片能快加载并重新填充到ImageView对象中。

下面是一个使用Fragment将LruCache对象保留在配置更改中的示例:

private LruCache<String, Bitmap> mMemoryCache;@Override
protected void onCreate(Bundle savedInstanceState) {...RetainFragment mRetainFragment =RetainFragment.findOrCreateRetainFragment(getFragmentManager());mMemoryCache = RetainFragment.mRetainedCache;if (mMemoryCache == null) {mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {... // Initialize cache here as usual}mRetainFragment.mRetainedCache = mMemoryCache;}...
}class RetainFragment extends Fragment {private static final String TAG = "RetainFragment";public LruCache<String, Bitmap> mRetainedCache;public RetainFragment() {}public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);if (fragment == null) {fragment = new RetainFragment();}return fragment;}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setRetainInstance(true);}
}

为了测试这个,可以在不适用Fragment的情况下旋转设备屏幕。在保留缓存的情况下,你应该能发现填充图片到Activity中几乎是瞬间从内存中取出而没有任何延迟的感觉。任何图片优先从内存缓存获取,没有的话再到硬盘缓存中找,如果都没有,那就以普通方式加载图片。

转载于:https://my.oschina.net/u/559701/blog/88596

【Google官方教程】第三课:缓存Bitmap相关推荐

  1. Lance老师UI系列教程第三课-QQ登录注册界面的实现(android-2012最新版)

    分类: android UI教程2012-08-06 22:37 3731人阅读 评论(6) 收藏 举报 uiandroidqqlayoutbutton UI系列教程第三课:腾讯登录注册界面的实现 今 ...

  2. 火山PC抓取快递物流查询接口教程第三课

    本源码转载自利快云https://www.lkuaiy.com/ 火山PC抓取快递物流查询接口教程第三课 一.本课目标 本节以网页公开快递查询网站(http://www.kuaidi.com)进行教学 ...

  3. Unity3D Shader官方教程翻译(三)

    Unity3D Shader官方教程翻译(三) 1.Shader语法:Pass 1个Pass块可以使一个几何物体被一次渲染. Pass { [Name and Tags] [RenderSetup] ...

  4. Java官方教程(三-1)运算符 operator(2020.12.18)

    前言 本文是橙子出于兴趣爱好对Java官方教程的尝试翻译,几乎每日更新,感兴趣的朋友可以关注一下橙子:翻译过程中尽可能多的对一些关键词保留了英文原文,如果你想看最纯正的英文原版教材却又看不懂,可以试着 ...

  5. Java官方教程(三-3)相等 关系和条件运算符(2020.12.20)

    前言 本文是橙子出于兴趣爱好对Java官方教程的尝试翻译,几乎每日更新,感兴趣的朋友可以关注一下橙子:翻译过程中尽可能多的对一些关键词保留了英文原文,如果你想看最纯正的英文原版教材却又看不懂,可以试着 ...

  6. 易语言miniblink交互教程——第三课 易语言与 Miniblink 交互

    今天来给大家讲解一下易语言&miniblink交互教程的第三课,也就是如何让易语言与Miniblink进行数据交互,相互传递参数. 1.用易语言调用JS 调用JS的方式很简单,只需要一句代码即 ...

  7. Java官方教程(三-2)赋值 运算和一元运算符(2020.12.19)

    前言 本文是橙子出于兴趣爱好对Java官方教程的尝试翻译,几乎每日更新,感兴趣的朋友可以关注一下橙子:翻译过程中尽可能多的对一些关键词保留了英文原文,如果你想看最纯正的英文原版教材却又看不懂,可以试着 ...

  8. React入门教程第三课--gulp编译优化

    上节课中我提到了,在gulpfile中编写task编译js和sass的task. 然后在项目中使用时发现,当项目内容越写越多时,编译速度会越来越久. 看了一下编译过程.上次gulpfile中的定义是当 ...

  9. 前端React教程第三课 数据是如何在 React 组件之间流动

    04 数据是如何在 React 组件之间流动的?(上) 通过前面 3 个课时的学习,相信你已经对 React 生命周期相关的"Why""What"和" ...

最新文章

  1. 【Android 异步操作】AsyncTask 异步任务 ( FutureTask 模拟 AsyncTask 执行过程 | AsyncTask 执行过程回顾 | FutureTask 分析 )
  2. Element el-upload上传组件详解
  3. 从Java新手到大神需要学哪些知识?
  4. 【TensorFlow-windows】name_scope与variable_scope
  5. 快速构建Windows 8风格应用10-设备方向
  6. 【转载】pyinstaller的使用和几个坑
  7. JDK 9 发布仅数月,为何在生产环境中却频遭嫌弃?
  8. dhcp工具_自制的树莓派网络工具集
  9. web文件操作常见安全漏洞(目录、文件名检测漏洞)
  10. 【免费模版分享】任务管理移动端Axure原型模板
  11. #1024程序员节# cc2530 按键唤醒功耗模式PM3例程
  12. 力扣887题-鸡蛋掉落
  13. 焦虑症和抑郁症有什么区别吗?
  14. resultful 风格
  15. Aria2离线下载搭建
  16. 面试-JVM-类加载-类加载器--自定义类加载器-JVM调优
  17. 模型压缩工具Distiller-INT8量化
  18. jpa整合querydsl实现简单查询以及左联查询
  19. 解决URL请求中的中文乱码问题
  20. DDR5相比DDR4有什么新特性?

热门文章

  1. ecshop 整合 QQ登陆 和 支付宝快捷登陆代码
  2. Dynamic Rankings——带修改区间第k大
  3. c语言实现字符指针(字符串)数组的排序
  4. Oracle数据库的下载和安装
  5. Worksheet.get_Range Method
  6. jQuery 表格插件
  7. 机器学习面试知识点汇总(Machine Learning Core Concepts Collection)
  8. php jquery点击事件,jQuery操作html元素点击事件详解
  9. 10.1 掌握大数据-机器学习笔记-斯坦福吴恩达教授
  10. linux中的查找文件夹