Android开发中图片的三级缓存策略

一、简介

现在的Android应用程序中,不可避免的都会使用到图片,如果每次加载图片的时候都要从网络重新拉取,这样不但很耗费用户的流量,而且图片加载的也会很慢,用户体验很不好。所以一个应用的图片缓存策略是很重要的。通常情况下,Android应用程序中图片的缓存策略采用“内存-本地-网络”三级缓存策略,首先应用程序访问网络拉取图片,分别将加载的图片保存在本地SD卡中和内存中,当程序再一次需要加载图片的时候,先判断内存中是否有缓存,有则直接从内存中拉取,否则查看本地SD卡中是否有缓存,SD卡中如果存在缓存,则图片从SD卡中拉取,否则从网络加载图片。依据这三级缓存机制,可以让我们的应用程序在加载图片的时候做到游刃有余,有效的避免内存溢出。

PS:当然现在处理网络图片的时候,一般人都会选择XUtils中的BitmapUtil,它已经将网络缓存处理的相当好了,使用起来非常方便--本人就一直在用。仿照BitMapUtil的实现思路,定制一个自己的图片加载工具,来理解一下三级缓存的策略,希望对自己会有一个提升。

二、网络缓存

网络拉取图片严格来讲不能称之为缓存,实质上就是下载url对应的图片,我们这里姑且把它看作是缓存的一种。仿照BitmapUtil中的display方法,我自己定制的CustomBitmapUtils也定义这个方法,根据传入的url,将图片设置到ivPic控件上。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public void display(ImageView ivPic, String url) {  
  2.   
  3. }  
定义网络缓存的工具类,在访问网络的时候,我使用了AsyncTask来实现,在AsyncTask的doInBackGround方法里下载图片,然后将 图片设置给ivPic控件,AsyncTask有三个泛型,其中第一个泛型是执行异步任务的时候,通过execute传过来的参数,第二个泛型是更新的进度,第三个泛型是异步任务执行完成之后,返回来的结果,我们这里返回一个Bitmap。具体的下载实现代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <pre name="code" class="java">/** 
  2.  * 网络缓存的工具类 
  3.  *  
  4.  * @author ZHY 
  5.  *  
  6.  */  
  7. public class NetCacheUtils {  
  8.   
  9.     private LocalCacheUtils localCacheUtils;  
  10.     private MemoryCacheUtils memoryCacheUtils;  
  11.   
  12.     public NetCacheUtils() {  
  13.         localCacheUtils = new LocalCacheUtils();  
  14.         memoryCacheUtils = new MemoryCacheUtils();  
  15.     }  
  16.   
  17.     /** 
  18.      * 从网络下载图片 
  19.      *  
  20.      * @param ivPic 
  21.      * @param url 
  22.      */  
  23.     public void getBitmapFromNet(ImageView ivPic, String url) {  
  24.         // 访问网络的操作一定要在子线程中进行,采用异步任务实现  
  25.         MyAsyncTask task = new MyAsyncTask();  
  26.         task.execute(ivPic, url);  
  27.   
  28.     }  
  29.   
  30.     /** 
  31.      * 第一个泛型--异步任务执行的时候,通过execute传过来的参数; 第二个泛型--更新进度; 第三个泛型--异步任务执行以后返回的结果 
  32.      *  
  33.      * @author ZHY 
  34.      *  
  35.      */  
  36.     private class MyAsyncTask extends AsyncTask<Object, Void, Bitmap> {  
  37.   
  38.         private ImageView ivPic;  
  39.         private String url;  
  40.   
  41.         // 耗时任务执行之前 --主线程  
  42.         @Override  
  43.         protected void onPreExecute() {  
  44.             super.onPreExecute();  
  45.         }  
  46.   
  47.         // 后台执行的任务  
  48.         @Override  
  49.         protected Bitmap doInBackground(Object... params) {  
  50.             // 执行异步任务的时候,将URL传过来  
  51.             ivPic = (ImageView) params[0];  
  52.             url = (String) params[1];  
  53.             Bitmap bitmap = downloadBitmap(url);  
  54.             // 为了保证ImageView控件和URL一一对应,给ImageView设定一个标记  
  55.             ivPic.setTag(url);// 关联ivPic和URL  
  56.   
  57.             return bitmap;  
  58.         }  
  59.   
  60.         // 更新进度 --主线程  
  61.         @Override  
  62.         protected void onProgressUpdate(Void... values) {  
  63.             super.onProgressUpdate(values);  
  64.         }  
  65.   
  66.         // 耗时任务执行之后--主线程  
  67.         @Override  
  68.         protected void onPostExecute(Bitmap result) {  
  69.             String mCurrentUrl = (String) ivPic.getTag();  
  70.             if (url.equals(mCurrentUrl)) {  
  71.                 ivPic.setImageBitmap(result);  
  72.                 System.out.println("从网络获取图片");  
  73.                 // 从网络加载完之后,将图片保存到本地SD卡一份,保存到内存中一份  
  74.                 localCacheUtils.setBitmap2Local(url, result);  
  75.                 // 从网络加载完之后,将图片保存到本地SD卡一份,保存到内存中一份  
  76.                 memoryCacheUtils.setBitmap2Memory(url, result);  
  77.   
  78.             }  
  79.         }  
  80.     }  
  81.   
  82.     /** 
  83.      * 下载网络图片 
  84.      *  
  85.      * @param url 
  86.      * @return 
  87.      */  
  88.     private Bitmap downloadBitmap(String url) {  
  89.         HttpURLConnection conn = null;  
  90.         try {  
  91.             URL mURL = new URL(url);  
  92.             // 打开HttpURLConnection连接  
  93.             conn = (HttpURLConnection) mURL.openConnection();  
  94.             // 设置参数  
  95.             conn.setConnectTimeout(5000);  
  96.             conn.setReadTimeout(5000);  
  97.             conn.setRequestMethod("GET");  
  98.             // 开启连接  
  99.             conn.connect();  
  100.   
  101.             // 获得响应码  
  102.             int code = conn.getResponseCode();  
  103.             if (code == 200) {  
  104.                 // 相应成功,获得网络返回来的输入流  
  105.                 InputStream is = conn.getInputStream();  
  106.   
  107.                 // 图片的输入流获取成功之后,设置图片的压缩参数,将图片进行压缩  
  108.                 BitmapFactory.Options options = new BitmapFactory.Options();  
  109.                 options.inSampleSize = 2;// 将图片的宽高都压缩为原来的一半,在开发中此参数需要根据图片展示的大小来确定,否则可能展示的不正常  
  110.                 options.inPreferredConfig = Bitmap.Config.RGB_565;// 这个压缩的最小  
  111.   
  112.                 // Bitmap bitmap = BitmapFactory.decodeStream(is);  
  113.                 Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);// 经过压缩的图片  
  114.   
  115.                 return bitmap;  
  116.             }  
  117.   
  118.         } catch (Exception e) {  
  119.             e.printStackTrace();  
  120.         } finally {  
  121.             // 断开连接  
  122.             conn.disconnect();  
  123.         }  
  124.   
  125.         return null;  
  126.     }  
  127. }  
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. </pre><p></p><pre>  

三、本地缓存

从网络加载完图片之后,将图片保存到本地SD卡中。在加载图片的时候,判断一下SD卡中是否有图片缓存,如果有,就直接从SD卡加载图片。本地缓存的工具类中有两个公共的方法,分别是向本地SD卡设置网络图片,获取SD卡中的图片。设置图片的时候采用键值对的形式进行存储,将图片的url作为键,作为文件的名字,图片的Bitmap作位值来保存。由于url含有特殊字符,不能直接作为图片的名字来存储,故采用url的MD5值作为文件的名字。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 本地缓存 
  3.  *  
  4.  * @author ZHY 
  5.  *  
  6.  */  
  7. public class LocalCacheUtils {  
  8.     /** 
  9.      * 文件保存的路径 
  10.      */  
  11.     public static final String FILE_PATH = Environment  
  12.             .getExternalStorageDirectory().getAbsolutePath() + "/cache/pics";  
  13.   
  14.     /** 
  15.      * 从本地SD卡获取网络图片,key是url的MD5值 
  16.      *  
  17.      * @param url 
  18.      * @return 
  19.      */  
  20.     public Bitmap getBitmapFromLocal(String url) {  
  21.         try {  
  22.             String fileName = MD5Encoder.encode(url);  
  23.             File file = new File(FILE_PATH, fileName);  
  24.             if (file.exists()) {  
  25.                 Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(  
  26.                         file));  
  27.                 return bitmap;  
  28.             }  
  29.   
  30.         } catch (Exception e) {  
  31.             e.printStackTrace();  
  32.         }  
  33.   
  34.         return null;  
  35.   
  36.     }  
  37.   
  38.     /** 
  39.      * 向本地SD卡写网络图片 
  40.      *  
  41.      * @param url 
  42.      * @param bitmap 
  43.      */  
  44.     public void setBitmap2Local(String url, Bitmap bitmap) {  
  45.         try {  
  46.             // 文件的名字  
  47.             String fileName = MD5Encoder.encode(url);  
  48.             // 创建文件流,指向该路径,文件名叫做fileName  
  49.             File file = new File(FILE_PATH, fileName);  
  50.             // file其实是图片,它的父级File是文件夹,判断一下文件夹是否存在,如果不存在,创建文件夹  
  51.             File fileParent = file.getParentFile();  
  52.             if (!fileParent.exists()) {  
  53.                 // 文件夹不存在  
  54.                 fileParent.mkdirs();// 创建文件夹  
  55.             }  
  56.             // 将图片保存到本地  
  57.             bitmap.compress(CompressFormat.JPEG, 100,  
  58.                     new FileOutputStream(file));  
  59.   
  60.         } catch (Exception e) {  
  61.             e.printStackTrace();  
  62.         }  
  63.     }  
  64.   
  65. }  

四、内存缓存

内存缓存说白了就是在内存中保存一份图片集合,首先会想到HashMap这种键值对的形式来进行保存,以url作为key,bitmap作为value。但是在Java中这种默认的new对象的方式是强引用,JVM在进行垃圾回收的时候是不会回收强引用的,所以如果加载的图片过多的话,map会越来越大,很容易出现OOM异常。在Android2.3之前,还可以通过软引用或者弱引用来解决,但是Android2.3之后,Google官方便不再推荐软引用了,Google推荐我们使用LruCache。

在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。

为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:

  • 你的设备可以为每个应用程序分配多大的内存?Android默认是16M。
  • 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
  • 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
  • 图片的尺寸和大小,还有每张图片会占据多少内存空间。
  • 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
  • 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。
以上是Google对LruCache的描述,其实LruCache的使用非常简单,跟Map非常相近,只是在创建LruCache对象的时候需要指定它的最大允许内存,一般设置为当前应用程序的最大运行内存的八分之一即可。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 内存缓存 
  3.  *  
  4.  * @author ZHY 
  5.  *  
  6.  */  
  7. public class MemoryCacheUtils {  
  8.     /* 
  9.      * 由于map默认是强引用,所有在JVM进行垃圾回收的时候不会回收map的引用 
  10.      */  
  11.     // private HashMap<String, Bitmap> map = new HashMap<String, Bitmap>();  
  12.     // 软引用的实例,在内存不够时,垃圾回收器会优先考虑回收  
  13.     // private HashMap<String, SoftReference<Bitmap>> mSoftReferenceMap = new  
  14.     // HashMap<String, SoftReference<Bitmap>>();  
  15.     // LruCache  
  16.     private LruCache<String, Bitmap> lruCache;  
  17.   
  18.     public MemoryCacheUtils() {  
  19.         // lruCache最大允许内存一般为Android系统分给每个应用程序内存大小(默认Android系统给每个应用程序分配16兆内存)的八分之一(推荐)  
  20.         // 获得当前应用程序运行的内存大小  
  21.         long mCurrentMemory = Runtime.getRuntime().maxMemory();  
  22.         int maxSize = (int) (mCurrentMemory / 8);  
  23.         // 给LruCache设置最大的内存  
  24.         lruCache = new LruCache<String, Bitmap>(maxSize) {  
  25.             @Override  
  26.             protected int sizeOf(String key, Bitmap value) {  
  27.                 // 获取每张图片所占内存的大小  
  28.                 // 计算方法是:图片显示的宽度的像素点乘以高度的像素点  
  29.                 int byteCount = value.getRowBytes() * value.getHeight();// 获取图片占用内存大小  
  30.                 return byteCount;  
  31.             }  
  32.         };  
  33.     }  
  34.   
  35.     /** 
  36.      * 从内存中读取Bitmap 
  37.      *  
  38.      * @param url 
  39.      * @return 
  40.      */  
  41.     public Bitmap getBitmapFromMemory(String url) {  
  42.   
  43.         // Bitmap bitmap = map.get(url);  
  44.         // SoftReference<Bitmap> softReference = mSoftReferenceMap.get(url);  
  45.         // Bitmap bitmap = softReference.get();  
  46.         // 软引用在Android2.3以后就不推荐使用了,Google推荐使用lruCache  
  47.         // LRU--least recently use  
  48.         // 最近最少使用,将内存控制在一定的大小内,超过这个内存大小,就会优先释放最近最少使用的那些东东  
  49.         Bitmap bitmap = lruCache.get(url);  
  50.         return bitmap;  
  51.   
  52.     }  
  53.   
  54.     /** 
  55.      * 将图片保存到内存中 
  56.      *  
  57.      * @param url 
  58.      * @param bitmap 
  59.      */  
  60.     public void setBitmap2Memory(String url, Bitmap bitmap) {  
  61.         // 向内存中设置,key,value的形式,首先想到HashMap  
  62.         // map.put(url, bitmap);  
  63.         // 保存软引用到map中  
  64.         // SoftReference<Bitmap> mSoftReference = new  
  65.         // SoftReference<Bitmap>(bitmap);  
  66.         // mSoftReferenceMap.put(url, mSoftReference);  
  67.         lruCache.put(url, bitmap);  
  68.     }  
  69.   
  70. }  
好了。现在三级缓存策略封装完毕,接下来定制我们自己的BitmapUtils

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 自定义的加载图片的工具类,类似于Xutils中的BitmapUtil,在实际使用中,一般使用BitmapUtil,为了理解三级缓存, 
  3.  * 这里模拟BitmapUtil自定义了CustomBitmapUtil 
  4.  *  
  5.  * @author ZHY 
  6.  *  
  7.  */  
  8. public class CustomBitmapUtils {  
  9.   
  10.     private Bitmap bitmap;  
  11.   
  12.     private NetCacheUtils netCacheUtils;  
  13.     private LocalCacheUtils localCacheUtils;  
  14.     private MemoryCacheUtils memoryCacheUtils;  
  15.   
  16.     public CustomBitmapUtils() {  
  17.         netCacheUtils = new NetCacheUtils();  
  18.         localCacheUtils = new LocalCacheUtils();  
  19.         memoryCacheUtils = new MemoryCacheUtils();  
  20.     }  
  21.   
  22.     /** 
  23.      * 加载图片,将当前URL对应的图片显示到ivPic的控件上 
  24.      *  
  25.      * @param ivPic 
  26.      *            ImageView控件 
  27.      * @param url 
  28.      *            图片的地址 
  29.      */  
  30.     public void display(ImageView ivPic, String url) {  
  31.         // 设置默认显示的图片  
  32.         ivPic.setImageResource(R.drawable.ic_launcher);  
  33.   
  34.         // 1、内存缓存  
  35.         bitmap = memoryCacheUtils.getBitmapFromMemory(url);  
  36.         if (bitmap != null) {  
  37.             ivPic.setImageBitmap(bitmap);  
  38.             System.out.println("从内存缓存中加载图片");  
  39.             return;  
  40.         }  
  41.         // 2、本地磁盘缓存  
  42.         bitmap = localCacheUtils.getBitmapFromLocal(url);  
  43.         if (bitmap != null) {  
  44.             ivPic.setImageBitmap(bitmap);  
  45.             System.out.println("从本地SD卡加载的图片");  
  46.             memoryCacheUtils.setBitmap2Memory(url, bitmap);// 将图片保存到内存  
  47.             return;  
  48.         }  
  49.         // 3、网络缓存  
  50.         netCacheUtils.getBitmapFromNet(ivPic, url);  
  51.         /* 
  52.          * 从网络获取图片之后,将图片保存到手机SD卡中,在进行图片展示的时候,优先从SD卡中读取缓存,key是图片的URL的MD5值, 
  53.          * value是保存的图片bitmap 
  54.          */  
  55.     }  
  56.   
  57. }  
在mainActivity中使用ListView加载网络图片

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * Android中三级缓存--网络缓存-本地缓存-内存缓存 
  3.  *  
  4.  * @author ZHY 
  5.  *  
  6.  */  
  7. public class MainActivity extends Activity {  
  8.   
  9.     private ListView list;  
  10.     private Button btn;  
  11.     private CustomBitmapUtils utils;  
  12.   
  13.     private static final String BASE_URL = "http://192.168.0.148:8080/pics";  
  14.     // 初始化一些网络图片  
  15.     String[] urls = { BASE_URL + "/1.jpg", BASE_URL + "/2.jpg",  
  16.             BASE_URL + "/3.jpg", BASE_URL + "/4.jpg", BASE_URL + "/5.jpg",  
  17.             BASE_URL + "/6.jpg", BASE_URL + "/7.jpg", BASE_URL + "/8.jpg",  
  18.             BASE_URL + "/9.jpg", BASE_URL + "/10.jpg", BASE_URL + "/11.jpg",  
  19.             BASE_URL + "/12.jpg", BASE_URL + "/13.jpg", BASE_URL + "/14.jpg",  
  20.             BASE_URL + "/15.jpg", BASE_URL + "/16.jpg", BASE_URL + "/17.jpg",  
  21.             BASE_URL + "/18.jpg", BASE_URL + "/19.jpg", BASE_URL + "/20.jpg",  
  22.             BASE_URL + "/21.jpg", BASE_URL + "/22.jpg", BASE_URL + "/23.jpg",  
  23.             BASE_URL + "/24.jpg", BASE_URL + "/25.jpg", BASE_URL + "/26.jpg",  
  24.             BASE_URL + "/27.jpg", BASE_URL + "/28.jpg", BASE_URL + "/29.jpg",  
  25.             BASE_URL + "/30.jpg" };  
  26.   
  27.     @Override  
  28.     protected void onCreate(Bundle savedInstanceState) {  
  29.         super.onCreate(savedInstanceState);  
  30.         setContentView(R.layout.activity_main);  
  31.         list = (ListView) findViewById(R.id.list);  
  32.         btn = (Button) findViewById(R.id.btn_load);  
  33.         utils = new CustomBitmapUtils();  
  34.   
  35.         // 加载网络图片  
  36.         btn.setOnClickListener(new OnClickListener() {  
  37.             @Override  
  38.             public void onClick(View v) {  
  39.                 MyAdapter adapter = new MyAdapter();  
  40.                 list.setAdapter(adapter);  
  41.             }  
  42.         });  
  43.     }  
  44.   
  45.     class MyAdapter extends BaseAdapter {  
  46.   
  47.         @Override  
  48.         public int getCount() {  
  49.             return urls.length;  
  50.         }  
  51.   
  52.         @Override  
  53.         public String getItem(int position) {  
  54.             return urls[position];  
  55.         }  
  56.   
  57.         @Override  
  58.         public long getItemId(int position) {  
  59.             return position;  
  60.         }  
  61.   
  62.         @Override  
  63.         public View getView(int position, View convertView, ViewGroup parent) {  
  64.             ViewHolder holder;  
  65.             if (convertView == null) {  
  66.                 convertView = View.inflate(MainActivity.this,  
  67.                         R.layout.item_list, null);  
  68.                 holder = new ViewHolder();  
  69.                 holder.ivPic = (ImageView) convertView.findViewById(R.id.iv);  
  70.                 convertView.setTag(holder);  
  71.             } else {  
  72.                 holder = (ViewHolder) convertView.getTag();  
  73.             }  
  74.             utils.display(holder.ivPic, urls[position]);  
  75.             return convertView;  
  76.         }  
  77.   
  78.         class ViewHolder {  
  79.             ImageView ivPic;  
  80.         }  
  81.     }  
  82. }  
运行的结果如下:

程序第一次运行,日志打印如下



之后将图片缓存在SD卡中,从本地加载图片


然后将图片缓存到内存,从内存加载图片


OK,到目前为止,Android中图片的三级缓存原理就都介绍完了,我自己本人受益匪浅,希望能够帮助到需要的朋友。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值