Android网络图片加载详解之"三级缓存"
来源:互联网 发布:mac下最好的输入法 编辑:程序博客网 时间:2024/06/06 03:00
三级缓存概览引入
我最近做的一个新闻类的项目中,用到了Picasso框架来加载网络图片. 优点是不但可以节约用户的使用流量,而且减少了内存的使用.
Picasso是如何对图片进行维护的呢?
其实Picasso就是对图片做了个缓存,而且是三级缓存.原理是: 当从网络中加载图片之后,本地一般会缓存一份, 下次再加载同一个图片时,直接从本地缓存中取出就可以了.
三级缓存概念图
原理详解
一级缓存:内存缓存
当加载网络图片时,首先会从内存缓存中,查找图片:
有:直接显示图片
没有:从二级缓存中查找图片
二级缓存:磁盘缓存
当一级缓存中没有目标图片时,从二级缓存中查找图片 :
有:先保存内存缓存中,然后显示图片.
没有:从三级缓存中获取图片.
三级缓存:网络缓存
当二级缓存中也没有目标图片时,从网络缓存中查找图片 :
有,先变为磁盘缓存,再变为内存缓存, 最后显示图片.
没有,先网络获取图片,然后磁盘缓存,内存缓存,显示图片
三级缓存的原理就是这么个套路,接下来我来通过代码实现一个相同功能的缓存框架, 以便于理解
三级缓存的实现
1) 网络缓存
要实现网络缓存,这里面一定涉及到异步加载网络图片。
异步加载网络图片常见的有两种形式:
第一种形式就是Thread + handler机制,来完成,
第二种形式就是android的SDK提供的异步工具类,AsyncTask来实现。这里我们选择AsyncTask来异步加载图片,因为它内部维护了线程池,可以实现并发的加载图片.
- 创建【NetCacheUtils】类, 具体代码如下:
package com.yashiro.demo.utils.bitmap;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.widget.ImageView;import com.yashiro.demo.utils.MyLogger;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URL;/** * Created by YEXU on 2017/2/12. * 三级缓存之: 网络缓存 */public class NetCacheUtils { private static final String TAG = "NetCacheUtils"; private Context context; //从网络获取图片 public void getBitmapFromNet(Context context, ImageView iv, String url){ this.context = context; //让ImageView和url关联起来: 解决图片错位的bug iv.setTag(url); // 创建内部类 BitmapTask 去实现异步加载网络图片 new BitmapTask().execute(iv,url); } //异步任务 class BitmapTask extends AsyncTask<Object,Void,Bitmap> { // 定义放图片的容器和网络链接 private ImageView iv; private String url; @Override protected Bitmap doInBackground(Object... params) { //获取参数 iv = (ImageView) params[0]; url = (String) params[1]; //下载图片: 抽取下载图片的方法downloadBitmap() Bitmap bitmap = downloadBitmap(url); // MyLogger是一个自定义的日志工具类(附件会提供) MyLogger.i(TAG,"从网络上加载了图片"); //将加载的网络图片缓存到磁盘和内存中: // 先缓存到磁盘(LocalCacheUtils后面会实现) LocalCacheUtils.saveCache(context,bitmap,url); // 再缓存到内存中(MemoryCacheUtils后面会实现) MemoryCacheUtils.saveCache(bitmap,url); return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); //获取ImageView对应的url String url = (String) iv.getTag(); // 只有是同一张图片时才显示,防止图片错位 if(bitmap != null && this.url.equals(url)){ iv.setImageBitmap(bitmap); } } } //下载图片的方法(利用IO流) private Bitmap downloadBitmap(String url) { Bitmap bitmap = null; HttpURLConnection conn = null; try { conn = (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(3000); conn.setReadTimeout(6000); conn.connect(); int responseCode = conn.getResponseCode(); if(responseCode == 200){ InputStream inputStream = conn.getInputStream(); //把流转换成Bitmap对象 bitmap = BitmapFactory.decodeStream(inputStream); return bitmap; } } catch (IOException e) { e.printStackTrace(); }finally { if(conn != null){ conn.disconnect(); } } return bitmap; }}
小结:
定义异步任务,并实现两个方法:
doInBackground:执行在子线程中的方法,作用:网络加载图片
onPostExecute:执行在UI线程中的方法,作用:更新UI,显示图片
定义网络加载图片:该逻辑执行在doInBackground方法中。
绑定UI,显示图片:该逻辑执行在onPostExecute方法中。
备注:
AsyncTask 定义的三种泛型类型 参数:Params,Progress 和 Result的解释
Params: 启动任务执行的输入参数, 比如HTTP 请求的 URL。
Progress: 后台任务执行的百分比。
Result: 后台执行任务最终返回的结果,比如String。
2) 磁盘缓存
接下来实现磁盘缓存:
* 磁盘缓存包含两部内容:
1)写磁盘:
如何将一个bitmap写入到磁盘中?
可以通过bitmap.compress(Bitmap.CompressFormat.JPEG,100,stream);
2)读磁盘
可以通过BitmapFactory.decodeFile(file.getAbsolutePath());读数据
- 创建【LocalCacheUtils.java】工具类,具体代码如下:
package com.yashiro.demo.utils.bitmap;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import com.yashiro.demo.utils.Md5Utils;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;/** * Created by YEXU on 2017/2/12. * 磁盘缓存(数据存放在本应用程序的cache目录下的Local_cache目录) */public class LocalCacheUtils { //写缓存 public static void saveCache(Context context, Bitmap bitmap, String url){ //缓存目录 File dir = new File(context.getCacheDir(),"local_cache"); if(!dir.exists()){ dir.mkdirs(); } //把图片缓存在缓存目录 // 此处的Md5Utils是一个生成md5文件的工具类(附件会提供) File file = new File(dir, Md5Utils.encode(url)); FileOutputStream stream = null; try { stream = new FileOutputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); } // 核心代码 bitmap.compress(Bitmap.CompressFormat.JPEG,100,stream); } //读缓存 public static Bitmap readCache(Context context,String url){ File dir = new File(context.getCacheDir(),"local_cache"); if(!dir.exists()){ return null; } File file = new File(dir, Md5Utils.encode(url)); if(!file.exists()){ return null; } // 核心代码 Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); //重要操作: 把数据缓存在内存中 // MemoryCacheUtils马上讲到 MemoryCacheUtils.saveCache(bitmap,url); return bitmap; }}
小结 :
- 借助md5工具类将图片url转化为图片名称,通过IO流将数据写到磁盘中.
- 通过url获取图片名称,从磁盘中读取数据使用
3) 内存缓存
接下来我们实现内存缓存, 内存缓存同样也包含两部分内容:
1)写入内存:
可以通过HashMap,把对应的数据进行保存
Key:对应的url
Value:对应的bitmap
2) 从内存读
从HashMap中取,
通过key取出对应bitmap
- 创建【MemoryCacheUtils】工具类 , 具体代码如下:
package com.yashiro.demo.utils.bitmap;import android.graphics.Bitmap;import android.util.LruCache;/** * Created by YEXU on 2017/2/12. * 内存缓存:通过HashMap来进行数据的存储 */public class MemoryCacheUtils { static { //caches = new HashMap<>(); //Android虚拟机的内存只有16M,易产生OOM异常(内存溢出) //java语言提供了另外一种机制:软引用、弱引用、虚引用 //软引用:当虚拟机内存不足的时候,回收软引用的对象 //弱引用:当对象没有应用的时候,马上回收 //虚引用 :任何情况下都可能回收 //java默认的数据类型是强引用类型 //caches = new HashMap<>(); //因为从 Android 2.3 (API Level 9)开始, //垃圾回收器会更倾向于回收持有软引用或弱引用的对象, //这让软引用和弱引用变得不再可靠。 //google官方推荐我们使用这样一种缓存机制:LruCache. //LruCache lru:least recently used 最近最少使用的算法 //A //B //C(最近使用的最少 优先被回收) //B //A long maxMemory = Runtime.getRuntime().maxMemory();//获取Dalvik 虚拟机最大的内存大小:16 // 2. 指定内存缓存集合的大小 lruCache = new LruCache<String,Bitmap>((int) (maxMemory/8)){ //3.获取每张图片的大小 @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes()*value.getHeight(); } }; } // 1. 创建LruCache对象 private static LruCache<String, Bitmap> lruCache; //private static HashMap<String, SoftReference<Bitmap>> caches; //private static HashMap<String, Bitmap> caches; //写缓存 public static void saveCache(Bitmap bitmap,String url){ //caches.put(url,bitmap); //SoftReference<Bitmap> soft = new SoftReference<Bitmap>(bitmap); //caches.put(url,soft); // 4. 使用put()方法缓存对象 lruCache.put(url,bitmap); } //读缓存 public static Bitmap readCache(String url){ //Bitmap bitmap = caches.get(url); //SoftReference<Bitmap> soft = caches.get(url); //if(soft != null){ //Bitmap bitmap = soft.get(); //return bitmap; //} // 5. 使用get()方法取出对象 return lruCache.get(url); }}
小结 :
1) 刚开始我是通过一个HashMap来缓存数据的,当缓存大量的图片时,就会导致应用程序出现内存溢出OOM的情况。属于强引用状态.
2) 为了解决这样的问题,我将缓存的bitmap对象,由原来的强引用状态变为软引用状态。当内存不足时,回收软用的bitmap对象。
3) 我们通过软引用方式,来解决内存溢出的情况,是可以的!但是代码中我有提到,随着android系统的升级和更新,垃圾回收器会更倾向于回收持有软引用或弱引用的对象. 因此,持有软引用对象变得不太可靠了。
4) 最后确定的是google官方推荐我们使用这样一种缓存机制:LruCache
什么是LruCache??
- 使用最近最少算法机制。来缓存每个对象的。
- 把近期最少使用的数据从缓存中移除,保留使用最频繁的数据
- LruCache内存维护一个LinkedHashMap,来维护每个缓存对象
- 从LruCache取出对象,它会把当前使用的对象进行移动到LinkedHashMap尾端
- 如果添加一个缓存对象,LruCache会把当前对象也放在LinkedHashMap尾端
- 在LinkedHashMap的顶端就是最近最少使用的缓存对象。也就是被移除的对象了。
LruCache使用:
* 构建LruCache对象,同时指定内存缓存大小
* 重写内部的sizeOf方法,计算每个图片的大小
* 使用LruCache的put方法缓存对象
* 使用LruCache的get方法取出对象
最终用于使用的 三级缓存框架 搭建
结合上面创建的三个工具类, 实现类似Picasso功能的框架:
- 创建【BitmapUtils】类, 具体代码如下:
package com.yashiro.demo.utils.bitmap;import android.content.Context;import android.graphics.Bitmap;import android.widget.ImageView;import com.yashiro.demo.utils.MyLogger;/** * Created by Administrator on 2017/2/9. * 图片三级缓存框架 */public class BitmapUtils { private static final String TAG = "BitmapUtils"; static{ netCacheUtils = new NetCacheUtils(); localCacheUtils = new LocalCacheUtils(); memoryCacheUtils = new MemoryCacheUtils(); } private static NetCacheUtils netCacheUtils; private static LocalCacheUtils localCacheUtils; private static MemoryCacheUtils memoryCacheUtils; //显示图片 public static void display(Context context, ImageView iv, String url){ Bitmap bitmap = null; //内存缓存 bitmap = memoryCacheUtils.readCache(url); if(bitmap != null){ iv.setImageBitmap(bitmap); MyLogger.i(TAG,"从内存获取了图片"); return; } //磁盘缓存 bitmap = localCacheUtils.readCache(context, url); if(bitmap != null){ iv.setImageBitmap(bitmap); MyLogger.i(TAG,"从磁盘获取了图片"); return; } //网络缓存 netCacheUtils.getBitmapFromNet(context,iv,url); }}
小结 :
此处只提供了一个显示网络图片的方法 display(), 只需要传入一下三个参数即可:
Context context : 上下文
ImageView iv: 存放图片的容器
String url: 图片的网络链接
附件:
文中用到的几个自定义工具类分享:
1) MyLogger 日志工具类:
package com.yashiro.demo.utils;import android.util.Log;/** * 日志工具类 * @author YEXU * */public final class MyLogger { //开关 //true 测试 false 上线 private final static boolean flag = true; public static void v(String tag,String msg){ if(flag){ Log.v(tag, msg); } } public static void d(String tag,String msg){ if(flag){ Log.d(tag, msg); } } public static void i(String tag,String msg){ if(flag){ Log.i(tag, msg); } } public static void w(String tag,String msg){ if(flag){ Log.w(tag, msg); } } public static void e(String tag,String msg){ if(flag){ Log.e(tag, msg); } }}
2) Md5Utils 生成md5文件工具类
package com.yashiro.demo.utils;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;public class Md5Utils { public static String encode(String password){ try { MessageDigest digest = MessageDigest.getInstance("MD5"); byte[] result = digest.digest(password.getBytes()); StringBuffer sb = new StringBuffer(); for(byte b : result){ int number = (int)(b & 0xff) ; String str = Integer.toHexString(number); if(str.length()==1){ sb.append("0"); } sb.append(str); } return sb.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); //can't reach return ""; } }}
完~~~
- Android网络图片加载详解之"三级缓存"
- Android网络图片加载三级缓存
- Android网络图片三级缓存
- android加载图片优化(三级缓存)
- Android网络图片三级缓存策略
- Android网络图片的三级缓存
- android三级缓存访问网络图片
- Android中图片的三级缓存详解
- Android中图片的三级缓存详解
- 网络图片加载,实现了简单的三级缓存
- Android图片的三级缓存机制之从网络中获取图片
- Android图片的三级缓存机制之从网络中获取图片
- Android 图片三级缓存
- Android 图片三级缓存
- Android 图片三级缓存
- android图片三级缓存
- Android三级图片缓存
- Android图片三级缓存
- 掌握技能j
- Docker学习笔记一:理论
- Ubuntu16.04+Theano环境
- TensorFlow学习笔记--1.0版本下的可视化
- No module named 'Tkinter'
- Android网络图片加载详解之"三级缓存"
- Solr删除查询示例
- 一个vue2.0+vuex+vue-router搭建的单页潮流购物网站
- 欢迎使用CSDN-markdown编辑器
- Java常见问题分析(内存溢出、内存泄露、线程阻塞等)
- 基于HTTP长轮询实现简单推送
- 用 AsyncDisplayKit 開發響應式 iOS App
- Centos7安装时提示,没有可用的网络设备
- python特性(一):序列与xrange对象