Android中高效的显示图片之三——缓存图片
加载一张图片到UI相对比较简单,如果一次要加载一组图片,就会变得麻烦很多。像ListView,GridView,ViewPager等控件,需要显示的图片和将要显示的图片数量可能会很大。
为了减少内存使用,这类控件都重复利用移出屏幕的子视图,如果你没有持用引用,垃圾回收器也会回收你加载过的图片。这种做法很好,但是如果想要图片加载快速流畅且不想当控件拖回来时重新运算获取加载过的图片,通常会使用内存和磁盘缓存。这节主要介绍当加载多张图片时利用内存缓存和磁盘缓存使加载图片时更快。
一、使用内存缓存
内存缓存以牺牲有限的应用内存为代价提供快速访问缓存的图片方法。LruCache类(有兼容包可以支持到API Level 4)很适合缓存图片的功能,它在LinkedHashMap中保存着最近使用图片对象的引用,并且在内容超过它指定的容量前删除近期最少使用的对象的引用。
注意:这前,很流行的图片缓存的方法是使用SoftReference和WeakReference,但是这种方法不提倡。因为从Android2.3(Level 9)开始,内存回收器会对软引用和弱引用进行回收。另外,在Android3.0(Levle 11)之前,图片的数据是存储在系统native内存中的,它的内存释放不可预料,这也是造成程序内存溢出的一个潜在原因。
为了给LruCache选择一个合适的大小,一些因素需要考虑:
》应用其它的模块对内存大小的要求
》有多少张图片会同时在屏幕上显示,有多少张图片需要提前加载
》屏幕的大小和密度
》图片的尺寸和设置
》图片被访问的频度
》平衡数量和质量,有时候存储大量的低质量的图片会比少量的高质量图片要有用
对于缓存,没有大小或者规则适用于所有应用,它依赖于你分析自己应用的内存使用确定自己的方案。缓存太小可能只会增加额外的内存使用,缓存太大可能会导致内存溢出或者应用其它模块可使用内存太小。
下面是为图片缓存设置LruCache的一个例子:
- private LruCache 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(cacheSize) {
- @Override
- protected 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)。一个全屏的GridView,尺寸为800x480大小通常为1.5M左右(800*480*4sbytes),所以在内存中可以缓存2.5张图片。
当一个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 {
- ...
- // 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;
- }
- ...
- }
二、使用磁盘缓存
内存缓存最加载最近访问过的图片有很大帮助,但你并不能依赖于图片会存在于缓存中。像GridView这样的控件如果数据稍微多一点,就可以轻易的把内存缓存用完。你的应用也有可能被其他任务打断,如电话呼入,应用在后台有可能会被结束,这样缓存的数据也会丢失。当用户回到应用时,所有的图片还需要重新获取一遍。
磁盘缓存可应用到这种场景中,它可以减少你获取图片的次数,当然,从磁盘获取图片比从内存中获取要慢的多,所以它需要在非UI线程中完成。示例代码中是磁盘缓存的一个实现,在Android4.0源码中(
libcore/luni/src/main/java/libcore/io/DiskLruCache.java),有更加强大和推荐的一个实现,它的向后兼容使在已发布过的库中很方便使用它。下面是它的例子:
- private DiskLruCache mDiskCache;
- 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
- ...
- File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);
- mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE);
- ...
- }
- class BitmapWorkerTask extends AsyncTask {
- ...
- // 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(String.valueOf(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
- if (!mDiskCache.containsKey(key)) {
- mDiskCache.put(key, bitmap);
- }
- }
- public Bitmap getBitmapFromDiskCache(String key) {
- return mDiskCache.get(key);
- }
- // 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 getCacheDir(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.getExternalStorageState() == Environment.MEDIA_MOUNTED
- || !Environment.isExternalStorageRemovable() ?
- context.getExternalCacheDir().getPath() : context.getCacheDir().getPath();
- return new File(cachePath + File.separator + uniqueName);
- }
在UI线程中检查内存缓存,而在后台线程中检查磁盘缓存。磁盘操作最好永远不要在UI线程中进行。当图片获取完成,把它同时缓存到内存中和磁盘中。
三、处理配置改变
- private LruCache mMemoryCache;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- ...
- RetainFragment mRetainFragment =
- RetainFragment.findOrCreateRetainFragment(getFragmentManager());
- mMemoryCache = RetainFragment.mRetainedCache;
- if (mMemoryCache == null) {
- mMemoryCache = new LruCache(cacheSize) {
- ... // Initialize cache here as usual
- }
- mRetainFragment.mRetainedCache = mMemoryCache;
- }
- ...
- }
- class RetainFragment extends Fragment {
- private static final String TAG = "RetainFragment";
- public LruCache mRetainedCache;
- public RetainFragment() {}
- public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
- RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
- if (fragment == null) {
- fragment = new RetainFragment();
- }
- return fragment;
- }
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setRetainInstance(true);
- }
- }
Android中高效的显示图片之三——缓存图片的更多相关文章
- Android中高效的显示图片之一 ——加载大图
在网上看了不少文章,发现还是官方文档介绍最详细,把重要的东西简单摘要出来.详细可看官方文档地址 ( http://www.bangchui.org/read.php?tid=9 ) . 在应用中显示图 ...
- Android中高效的显示图片之二——在非UI线程中处理图片
在“加载大图”文章中提到的BitmapFactory.decode*方法,如果源数据是在磁盘.网络或其它任何不是在内存中的位置,那么它都不应该在UI线程中执行.因为它的加载时间不可预测且依赖于一系列因 ...
- Android异步下载图片并且缓存图片到本地
Android异步下载图片并且缓存图片到本地 在Android开发中我们经常有这样的需求,从服务器上下载xml或者JSON类型的数据,其中包括一些图片资源,本demo模拟了这个需求,从网络上加载XML ...
- Android训练课程(Android Training) - 高效的显示图片
高效的显示图片(Displaying BitmapsEfficiently) 了解如何使用通用的技术来处理和读取位图对象,让您的用户界面(UI)组件是可响应的,并避免超过你的应用程序内存限制的方式.如 ...
- Android之简单了解Bitmap显示图片及缓存图片
昨天我们学了如何连接网络,今天我们就学习一下如何从把网上图片显示到项目中 今天主要用到的是Bitmap 类 Bitmap是Android系统中的图像处理的最重要类之一.用它可以获取图像文件信息,进行图 ...
- [置顶] Android中使用Movie显示gif动态图
转载请注明: http://blog.csdn.net/u012975705/article/details/48717391 在看这篇博文之前对attr自定义属性还是不是很熟的童鞋可以先看看:An ...
- 【有人@我】Android中高亮变色显示文本中的关键字
应该是好久没有写有关技术类的文章了,前天还有人在群里问我,说群主很长时间没有分享干货了,今天分享一篇Android中TextView在大段的文字内容中如何让关键字高亮变色的文章 ,希望对大家有所帮助, ...
- Android中高亮变色显示文本中的关键字
应该是好久没有写有关技术类的文章了,前天还有人在群里问我,说群主很长时间没有分享干货了,今天分享一篇Android中TextView在大段的文字内容中如何让关键字高亮变色的文章 ,希望对大家有所帮助, ...
- android 删除SD卡或者手机的缓存图片和目录
public static final String TEMP_PHOTO_FILE_NAME = "temp_photo.jpg"; private static String ...
随机推荐
- 再理解 as3.0接口
As3.0 接口的理解与运用 1.把接口当作"类"来理解.你easy接受她. 我们看她的标准结构: package 包路径{ public interface 接口名称{ func ...
- cg语言的一些术语
在Cg中,用uniform修饰符声明一个参数表明它的值是由外部的数据源初始化的,而且在给定这批向量的处理中保持不变. Uniform inputs,表示一些与三维渲染有关的离散信息数据,这些数据通常由 ...
- Mysql主从复制,实现数据同步
大型网站为了软解大量的并发访问,除了在网站实现分布式负载均衡,远远不够.到了数据业务层.数据访问层,如果还是传统的数据结构,或者只是单单靠一台服务器扛,如此多的数据库连接操作,数据库必然会崩溃,数据丢 ...
- Java线程面试题:子线程先运行 2 次,然后主线程运行 4 次,如此反复运行 3 次
package thread; /** * 需求:线程编程:子线程先运行 2 次,然后主线程运行 4 次,如此反复运行 3 次. * @author zhongfg * @date 2015-06-1 ...
- Codeforces Round #FF (Div. 2) A. DZY Loves Hash
DZY has a hash table with p buckets, numbered from 0 to p - 1. He wants to insert n numbers, in the ...
- Paint的setPathEffect(PathEffect effect)、以及Path的具体使用,收益多多!
Paint的setPathEffect(PathEffect effect).以及Path的具体使用,收益多多! 在这首先申明一下介绍只是为了学习使用 内容都来自:http://www.cnblogs ...
- python接口自动化(四十二)- 项目结构设计之大结局(超详解)
简介 这一篇主要是将前边的所有知识做一个整合,把各种各样的砖块---模块(post请求,get请求,logging,参数关联,接口封装等等)垒起来,搭建一个房子.并且有很多小伙伴对于接口项目测试的框架 ...
- Gaby Ivanushka(快排)
Gaby Ivanushka Once upon a time there lived a tsar that has a daughter — Beautiful Vasilisa. There w ...
- 浅谈命令查询职责分离(CQRS)模式---转载
在常用的三层架构中,通常都是通过数据访问层来修改或者查询数据,一般修改和查询使用的是相同的实体.在一些业务逻辑简单的系统中可能没有什么问题,但是随着系统逻辑变得复杂,用户增多,这种设计就会出现一些性能 ...
- Mac idea maven 创建web项目
这样项目就创建完成了.然后把Tomcat加入进去就可以跑通了.