ImageCache

由于手机流量有限,又要加快app的运行效率,因此好的app都有做图片缓存。图片缓存说起来简单,做起来就用到很多知识点,可算是集Android技术之大全了。只要理解图片缓存的算法,并加以实践把它做好,我觉得差不多可以懂半个Android的开发。

缓存策略

图片缓存一般分为三级,分别是内存、磁盘文件与网络图片。正常情况下,app会先到内存寻找图片,如果有找到,则直接显示内存中的图片。如果内存没找到,再到磁盘寻找,如果有找到,则读取磁盘图片并显示。如果磁盘也没找到,就得根据uri去网络下载图片,下载成功后显示图片。经过三级的缓存,即使网速很慢或者断网,app也能迅速加载部分图片,从而提高了用户体验。

内存缓存的数据结构可使用映射表HashMap,通过唯一的uri来定位图像的Bitmap对象;排队算法一般采用先进先出FIFO策略,考虑到FIFO需要对队列两端做操作,从队列顶端移除溢出的图像,把新增的图像加到队列末端,所以排队的缓存采用双端队列LinkedList。映射表和双端队列的介绍参见《 Android开发笔记(二十六)Java的容器类》,另外,为防止并发操作双端队列,引起不必要的资源冲突,在声明相关方法时要加上synchronized关键字。

磁盘操作分两块,一块是创建图片文件的缓存目录,首先检查缓存目录是否存在,不存在则先创建目录;其次根据哈希值检查图片文件是否存在,存在则读取图像,不存在则跳到网络处理;目录与文件的介绍参见《 Android开发笔记(三十二)文件基础操作》。另一块是从文件中读写Bitmap对象,图片文件的读写操作参见《 Android开发笔记(三十三)文本文件和图片文件的读写》。

下载策略

图片在内存和磁盘都找不到,那只好到网络上获取图片了。根据http地址获取图片,采用的是GET方式,具体编码参见《 Android开发笔记(六十三)HTTP访问的通信方式》。

由于访问网络属于异步操作,不能在主线程中直接处理,因此必须另外开线程,沟通异步方式的Handler介绍参见《 Android开发笔记(四十八)Thread类实现多线程》。另外,考虑到图片缓存可能同时访问多张图片,所以为提高效率要引入线程池,由线程池对象统一管理图片下载任务,线程池的介绍参见《 Android开发笔记(七十六)线程池管理》。

显示策略及相关优化

历经千辛万苦,终于把图片从三级缓存中找出来了,现在要在ImageView控件上显示图片,通常会使用淡入淡出动画效果,不至于很突兀,淡入淡出动画的用法参见《 Android开发笔记(十五)淡入淡出动画》。这里注意,如果内存中已经存在该图像,则无需淡入淡出动画;只有从网络上获取图片,这种需要用户等待的情况,才需要淡入淡出效果。

另外,为提高用户体验,经常在图片加载之前,就在原图位置先放一张占位图片;如果图片加载失败,也在原图位置提示错误图片或者默认图片;这些占位图片和错误图片可在配置缓存信息时进行设置。

图片缓存在提高性能的同时,不要忘记预防内存泄漏。因为Handler对象和Bitmap对象都存在内存泄漏的风险,所以我们要及时释放Handler对象的引用,并及时回收Bitmap对象的数据,具体优化处理参见《 Android开发笔记(七十五)内存泄漏的处理》。

代码示例

下面是图片缓存的一个简单实现代码例子:

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.animation.AlphaAnimation;
import android.widget.ImageView;public class ImageCache {private final static String TAG = "ImageCache";//内存中的图片缓存private HashMap<String, Bitmap> mImageMap = new HashMap<String, Bitmap>();//uri与视图控件的映射关系private HashMap<String, ImageView> mViewMap = new HashMap<String, ImageView>();//缓存队列,采用FIFO先进先出策略,需操作队列首尾两端,故采用双端队列private LinkedList<String> mUriList = new LinkedList<String>();private ImageCacheConfig mConfig;private String mDir = "";private ThreadPoolExecutor mPool;private static Handler mMyHandler;private static ImageCache mCache = null;private static Context mContext;public static ImageCache getInstance(Context context) {if (mCache == null) {mCache = new ImageCache();mCache.mContext = context;}return mCache;}public ImageCache initConfig(ImageCacheConfig config) {mCache.mConfig = config;mCache.mDir = mCache.mConfig.mDir;if (mCache.mDir==null || mCache.mDir.length()<=0) {mCache.mDir = Environment.getExternalStorageDirectory() + "/image_cache";}Log.d(TAG, "mDir="+mCache.mDir);//若目录不存在,则先创建新目录File dir = new File(mCache.mDir);if (dir.exists() != true) {dir.mkdirs();}mCache.mPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(mCache.mConfig.mThreadCount);mCache.mMyHandler = new MyHandler((Activity)mCache.mContext);return mCache;}public void show(String uri, ImageView iv) {if (mConfig.mBeginImage != 0) {iv.setImageResource(mConfig.mBeginImage);}mViewMap.put(uri, iv);if (mImageMap.containsKey(uri) == true) {mCache.render(uri, mImageMap.get(uri));} else {String path = getFilePath(uri);if ((new File(path)).exists() == true) {Bitmap bitmap = ImageUtil.openBitmap(path);if (bitmap != null) {mCache.render(uri, bitmap);} else {mPool.execute(new MyRunnable(uri));}} else {mPool.execute(new MyRunnable(uri));}}}private String getFilePath(String uri) {String file_path = String.format("%s/%d.jpg", mDir, uri.hashCode());return file_path;}private static class MyHandler extends Handler {public static WeakReference<Activity> mActivity;public MyHandler(Activity activity) {mActivity = new WeakReference<Activity>(activity);}@Overridepublic void handleMessage(Message msg) {Activity act = mActivity.get();if (act != null) {ImageData data = (ImageData) (msg.obj);if (data!=null && data.bitmap!=null) {mCache.render(data.uri, data.bitmap);} else {mCache.showError(data.uri);}}}}private class MyRunnable implements Runnable {private String mUri;public MyRunnable(String uri) {mUri = uri;}@Overridepublic void run() {Activity act = MyHandler.mActivity.get();if (act != null) {Bitmap bitmap = ImageHttp.getImage(mUri);if (bitmap != null) {if (mConfig.mSize != null) {bitmap = Bitmap.createScaledBitmap(bitmap, mConfig.mSize.x, mConfig.mSize.y, false);}ImageUtil.saveBitmap(getFilePath(mUri), bitmap);}ImageData data = new ImageData(mUri, bitmap);Message msg = mMyHandler.obtainMessage();msg.obj = data;mMyHandler.sendMessage(msg);}}};private void render(String uri, Bitmap bitmap) {ImageView iv = mViewMap.get(uri);if (mConfig.mFadeInterval <= 0) {iv.setImageBitmap(bitmap);} else {//内存中已有图片的就直接显示,不再展示淡入淡出动画if (mImageMap.containsKey(uri) == true) {iv.setImageBitmap(bitmap);} else {iv.setAlpha(0.0f);AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);alphaAnimation.setDuration(mConfig.mFadeInterval);alphaAnimation.setFillAfter(true);iv.setImageBitmap(bitmap);iv.setAlpha(1.0f);iv.setAnimation(alphaAnimation);alphaAnimation.start();mCache.refreshList(uri, bitmap);}}}private synchronized void refreshList(String uri, Bitmap bitmap) {if (mUriList.size() >= mConfig.mMemoryFileCount) {String out_uri = mUriList.pollFirst();mImageMap.remove(out_uri);}mImageMap.put(uri, bitmap);mUriList.addLast(uri);}private void showError(String uri) {ImageView iv = mViewMap.get(uri);if (mConfig.mErrorImage != 0) {iv.setImageResource(mConfig.mErrorImage);}}public void clear() {for (Map.Entry<String, Bitmap> item_map : mImageMap.entrySet()) {Bitmap bitmap = item_map.getValue();bitmap.recycle();}mCache = null;}}

下面是该缓存的调用代码示例:

         ImageCacheConfig config = new ImageCacheConfig.Builder().setBeginImage(R.drawable.bliss).setErrorImage(R.drawable.error).setFadeInterval(2000).build();ImageCache.getInstance(this).initConfig(config).show(file, iv_hello);

picasso

picasso是Square公司开源的一个Android图片缓存库,使用相对简单,一般只需一句代码即可下载图片并显示到视图。

Picasso

Picasso是主要的处理类,常用方法如下:
with : 静态方法。初始化一个默认实例。
setSingletonInstance : 静态方法。指定已设置好的实例。
setIndicatorsEnabled : 设置标志是否可用。其实就是开发模式,会在图片左上角显示三角标志,绿色表示图片取自内存,蓝色表示取自磁盘,红色表示取自网络。
setLoggingEnabled : 设置日志是否可用。
load : 从指定位置加载图片。该方法返回一个RequestCreator对象,供后续处理使用。
cancelRequest : 取消指定控件的图片加载请求。
shutdown : 关闭Picasso。

RequestCreator

RequestCreator对象来源于Picasso的load方法,主要处理图片的展示操作,常用方法如下:
placeholder : 指定图片加载前的占位图片。
error : 指定图片加载失败的占位图片。
resize : 指定图片缩放的尺寸。
centerCrop : 指定图片居中时裁剪。
centerInside : 指定图片在内部居中。
rotate : 指定图片的旋转角度。
config : 指定图片的色彩模式。
noFade : 指定不显示淡入淡出动画。默认有显示动画。
into : 指定图片显示的控件。

设置缓存目录

picasso除了能加载网络图片,还能加载资源图片(包括assets和drawable)。另外,若想自定义picasso的图片缓存目录,可按如下方式进行设置:

 private void setImageCacheDir() {String imageCacheDir = Environment.getExternalStorageDirectory() + "/picasso_image";tv_hello.setText(imageCacheDir);Picasso picasso = new Picasso.Builder(this).downloader(new OkHttpDownloader(new File(imageCacheDir))).build();Picasso.setSingletonInstance(picasso);}

需要注意的是,picasso依赖于okhttp,而okhttp又依赖于okio,所以若想使用picasso的全部功能(比如自定义缓存目录时用到OkHttpDownloader),需要同时导入picasso、okhttp、okio三个jar包。

代码示例

下面是picasso几个常用场景下的代码例子:

//简单加载
Picasso.with(this).load(url).into(iv_hello);
//缩放加载
Picasso.with(this).load(url).resize(512, 384).centerCrop().into(iv_hello);
//占位加载
Picasso.with(this).load(url).placeholder(R.drawable.bliss).error(R.drawable.error).into(iv_hello);

Universal-Image-Loader

Universal-Image-Loader是个广泛应用的图片加载框架,它的功能比Picasso更丰富,当然用起来也会复杂一些。

ImageLoader

Universal把缓存图片分为两个过程:Load加载、Display显示。加载信息由ImageLoaderConfiguration类处理,显示信息由DisplayImageOptions类处理,最后再由ImageLoader统一设置和显示。ImageLoader的常用方法如下:
getInstance : 静态方法。获取ImageLoader的实例。
init : 初始化加载信息。
displayImage : 在指定控件ImageView上显示图片,同时指定显示信息。
cancelDisplayTask : 取消指定控件上的图片显示任务。
loadImage : 在指定控件ImageView上加载图片,可设置图片加载的监听器(包括开始加载onLoadingStarted、取消加载onLoadingCancelled、加载完成onLoadingComplete、加载失败onLoadingFailed四个方法)。

ImageLoaderConfiguration

加载信息的设置采用了建造者模式,主要指定线程、内存、磁盘的相关处理,详细的方法使用举例如下:

        File imageCacheDir = new File(Environment.getExternalStorageDirectory() + "/universal_image");ImageLoaderConfiguration config = new ImageLoaderConfiguration  .Builder(this).threadPoolSize(3) //线程池内加载的数量  .threadPriority(Thread.NORM_PRIORITY - 2) //设置当前线程的优先级.denyCacheImageMultipleSizesInMemory() //拒绝缓存同一图片的多个尺寸版本
//              .taskExecutor(new Executor() {
//                  @Override
//                  public void execute(Runnable command) {
//                  }
//              })  //设置图片加载的任务,如无必要不必重写
//              .taskExecutorForCachedImages(new Executor() {
//                  @Override
//                  public void execute(Runnable command) {
//                  }
//              })  //设置已缓存的图片的加载任务,如无必要不必重写.tasksProcessingOrder(QueueProcessingType.FIFO) //队列的排队算法,默认FIFO。FIFO表示先进先出,LIFO表示后进先出.memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)) //你可以通过自己的内存缓存实现  .memoryCacheSize(2 * 1024 * 1024) //使用的内存大小.memoryCacheSizePercentage(13) //使用的内存百分比.memoryCacheExtraOptions(480, 800) //设置内存中图片的长宽 .diskCache(new UnlimitedDiskCache(imageCacheDir)) //自定义磁盘的路径.diskCacheSize(50 * 1024 * 1024) //使用的磁盘大小.diskCacheFileCount(100) //磁盘的文件数量上限.diskCacheFileNameGenerator(new Md5FileNameGenerator())//设置磁盘文件名的命名模式,默认哈希。HashCodeFileNameGenerator表示采用哈希算法,Md5FileNameGenerator表示采用MD5算法.diskCacheExtraOptions(480, 800, new BitmapProcessor() {@Overridepublic Bitmap process(Bitmap bitmap) {ByteArrayOutputStream baos = new ByteArrayOutputStream();bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());return BitmapFactory.decodeStream(bais, null, null);}} ) //设置磁盘中图片的长宽,需自定义压缩倍率.defaultDisplayImageOptions(DisplayImageOptions.createSimple()) //显示图片的选项,默认createSimple.imageDownloader(new BaseImageDownloader(this, 5 * 1000, 30 * 1000)) //下载图片的超时设置,默认连接超时5秒,读取超时20秒。第二个参数表示连接超时,第三个参数表示读取超时.imageDecoder(new BaseImageDecoder(false)) //对图片解码,如需缩放或旋转可在此处理.writeDebugLogs() //打印调试日志。上线时需要去掉该方法.build(); //开始构建配置

DisplayImageOptions

显示信息主要指定显示模式与占位图片,可用于ImageLoader的displayImage和loadImage方法,以及ImageLoaderConfiguration的defaultDisplayImageOptions方法。详细的方法使用举例如下:

     DisplayImageOptions options = new DisplayImageOptions.Builder().cacheInMemory(true) //设置是否在内存中缓存,默认为false.cacheOnDisk(true) //设置是否在磁盘中缓存,默认为false.resetViewBeforeLoading(false) //设置是否在加载前重置视图,默认为false.displayer(new FadeInBitmapDisplayer(3000))  //设置淡入淡出的时间间隔.imageScaleType(ImageScaleType.EXACTLY)  //设置缩放类型.bitmapConfig(Bitmap.Config.ARGB_8888) //设置图像的色彩模式.showImageOnLoading(R.drawable.bliss) //设置图片在下载期间显示的图片.showImageForEmptyUri(R.drawable.error)//设置图片Uri为空或是错误的时候显示的图片  .showImageOnFail(R.drawable.error)  //设置图片加载/解码过程中错误时候显示的图片.build(); //开始构建配置

加载资源图片

除了加载网络图片,Universal也支持加载资源类图片,包括ContentProvider、assets和drawable三种资源图片。具体方法如下:
1、加载ContentProvider图片

String contentUrl = "content://media/external/audio/albumart/13";

2、加载assets图片

String assetsUrl = Scheme.ASSETS.wrap("image.png");  

3、加载drawable图片

String drawableUrl = Scheme.DRAWABLE.wrap(""+R.drawable.image);

特别注意drawable的加载方式,网上很多人转的都是Scheme.DRAWABLE.wrap("R.drawable.image"),但这种写法是有问题的,运行的时候会报错“java.lang.NumberFormatException: Invalid int: "R.drawable.image"”。看来学习光看是不行的,人云亦云最容易犯错,还是自己动手跑跑看,才知道这样做行不行。

代码示例

下面是Universal-Image-Loader几个常用场景下的代码例子:

         //简单加载ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this).build();mLoader.init(config);DisplayImageOptions options = new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(3000))  //设置淡入淡出的时间间隔.build();mLoader.displayImage(url, iv_hello, options);//带监听器的加载ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this).build();mLoader.init(config);ImageSize size = new ImageSize(512, 384);mLoader.loadImage(url, size, new SimpleImageLoadingListener(){@Overridepublic void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {super.onLoadingComplete(imageUri, view, loadedImage);iv_hello.setImageBitmap(loadedImage);}});

点击下载本文用到的图片缓存算法的工程代码

点此查看Android开发笔记的完整目录

Android开发笔记(七十七)图片缓存算法相关推荐

  1. Android开发笔记(十七)GIF动画的实现GifAnimation

    GIF在Windows上是常见的图片格式,主要用来播放短小的动画.但在手机上由于系统资源紧张,所以Android并没有直接支持GIF格式,如果在ImageView中放入一张gif文件,你会发现显示出来 ...

  2. Android开发笔记(序)写在前面的目录

    知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经验教训,与网友互相切磋,从而去芜存菁进一步提升自己的水平.因此博主就想,入门的东西咱就不写了,人不能老停留在入 ...

  3. Android开发笔记(序)

    本开发笔记,借鉴与其他开发者整理的文章范例与心得体会.在这里作为开发过程中的一个总结与笔记式记录. 如有侵犯作者权益,请及时联系告知删除.俗话说:集百家成一言,去粕成金. ************** ...

  4. Android开发笔记(序)写在前面的目录大全

    转自  湖前琴亭 的博客https://blog.csdn.net/aqi00/article/details/50012511 知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面 ...

  5. Andriod开发之二十:Android开发笔记(序)写在前面的目录

    https://blog.csdn.net/aqi00/article/details/50038385 知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经验教 ...

  6. Android开发笔记(九十七)图片的特效处理

    图片特效用到的函数 本文讲述的图片特效处理包括:怀旧.光照.光晕.底片.浮雕.模糊.锐化.黑白.冰冻.素描,所有这些特效都是基于一定的算法,对图像每个点的RGB值进行计算,并汇总所有点的计算结果生成新 ...

  7. Android开发笔记(一百七十七)借助FileProvider安装应用

    除了发送彩信需要文件提供器,安装应用也需要FileProvider.不单单彩信的附件图片能到媒体库中查询,应用的APK安装包也可在媒体库找到.查找安装包依然借助于内容解析器,具体的实现过程和查询图片类 ...

  8. Android开发笔记(一百一十七)app省电方略

    电源管理PowerManager PowerManager是Android的电源管理类,用于管理电源操作如睡眠.唤醒.重启以及调节屏幕亮度等等. PowerManager的对象从系统服务POWER_S ...

  9. Android开发笔记(一百七十一)使用Glide加载网络图片

    如何方便而又快速地显示网络图片,一直是安卓网络编程的热门课题,前些年图片缓存框架Picasso.Fresco等等大行其道,以至于谷歌按捺不住也开发了自己的Glide开源库.由于Android本身就是谷 ...

最新文章

  1. CentOS系统安装配置JDK
  2. 常考数据结构与算法:最长回文子串
  3. 使用参数化SQL语句进行模糊查找
  4. CSS3的background-size:设置背景图片大小
  5. IT运维服务管理问题总结 #F#
  6. epic堡垒之夜显示服务器离线,堡垒之夜epic服务器进不去 | 手游网游页游攻略大全...
  7. React实现图片自适应
  8. 【计算机组成原理】I/O设备
  9. phpcmsV9:后台无法选择模板
  10. 常用数据类型使用转换详解
  11. kettle 内存设置_Kettle大数据量转换报错ora-04030: 在尝试分配484字节时进程内存不足...
  12. SAP 修改字段长度
  13. arcpy判断图层是否存在的方法
  14. 杜比服务器系统安装,小编教你给Win10系统安装杜比音效驱动的方法
  15. 【推荐】2022年AIOT人工智能物联网行业研究报告市场应用分析白皮书(附件中为网盘地址,报告持续更新)
  16. 手机wifi java_Android中使用WIFI来连接ADB
  17. 2022-2028全球与中国三维扫描仪市场现状及未来发展趋势
  18. 基于C语言实现的汽车牌照的快速查询
  19. php 算生存曲线,生存曲线(三):统计分析方法这么多,到底选哪个?
  20. (投影:Projector)Unity Projector 投影器原理以及优化

热门文章

  1. Leetcode每日一题:剑指offer22.lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof(链表中倒数第k个节点)
  2. ROS入门-12.服务端Server的编程实现
  3. 吴恩达机器学习之多变量线性回归实现部分
  4. 动态规划之袋鼠过河问题
  5. LeetCode刷题(50)--Word Search
  6. cdo收取邮件_C#使用CDO发送邮件的方法
  7. wps 选择 高亮_简单实用:一组WPS表格技巧
  8. EMS --Web Development Kit开发
  9. 在浏览器中实现复制内容到剪切板中
  10. [Axis2与Eclipse整合开发Web Service系列之一] 生成Web Service Client(将WSDl 转化成 Java代码)