资料 | 汇总Android小黑屋android开发技巧

Android 学习笔记之图片三级缓存

2017-01-15  本文已影响507人  maoqitian

最近在学习的时候接触到Android对图片的获取途径,写一篇笔记记录一发。

什么是图片三级缓存

三级缓存指的是内存缓存,本地缓存和网络缓存,而App获取图片资源可以通过这三种途径来获取,也就是指图片的三级缓存。

为什么要使用图片三级缓存

如果一个App有很多图片,而每次获取图片都要通过网络来获取,则这个应用一定会很消耗流量,所有这时候有必要将获取的图片做缓存保存在本地或者内存中。而他们获取的优先级是首先从内存获取,其次是从本地获取,前面都获取不到图片才通过网络从服务器中获取图片。从内存获取图片的速度是最快的,如果是加载很多图片,有可能会导致内存溢出(OOM).

如何做图片三级缓存

可以从最外层做起,首先从网络获取图片

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
/** 
* Created by 毛麒添 on 2017/1/14 0014.
 * 图片三级缓存之网络缓存 
* 从网络中获取图片 
* 使用异步任务来获取图片 
*/
public class NetCacheUtils {   
 private ImageView imageView;    
private LocalCacheUtils localCacheUtils;   
 private MemoryCacheUtils memoryCacheUtils;    
private String url;    
public NetCacheUtils(LocalCacheUtils localCacheUtils, MemoryCacheUtils memoryCacheUtils) {
        this.localCacheUtils=localCacheUtils;        
        this.memoryCacheUtils=memoryCacheUtils;   
 }    

public void getBitmapFromNet(ImageView imageView, String url) {    
 //开启异步任务      
 new BitmapTack().execute(imageView,url);
    
}    
class BitmapTack extends AsyncTask<Object,Integer,Bitmap>{       
 //预备加载,运行在主线程        
@Override       
 protected void onPreExecute() 
{           
 super.onPreExecute();       
 }        
@Override        
protected Bitmap doInBackground(Object... params) {            
//获取外部需要设置的ImageView对象           
 imageView = (ImageView) params[0];            
url = (String) params[1];            //开始下载图片          
  //imageView.setTag(url);
给图片设置唯一标记,让其在设置bitmap时候判断url是否相同来进行设置,防止图片不一致           
 Bitmap bitmap=downLoadBitmap(url);           
 return bitmap;        }        
   
//执行完成,运行在主线程        
@Override        
protected void onPostExecute(Bitmap bitmap) 
{            
if(bitmap!=null){               
 imageView.setImageBitmap(bitmap);                
//设置本地缓存               
 localCacheUtils.setLocalBitmapCache(bitmap,url);               
 //设置内存缓存                
memoryCacheUtils.setMemoryCache(url,bitmap);           
 }           
 super.onPostExecute(bitmap);       
 }   
 }    
/**     
* 下载图片bitmap对象    
 * @param url 下载地址     
* @return 请求成功返回获取图片的bitmap对象,否则返回空     
*/    
private Bitmap downLoadBitmap(String url) {        
HttpsURLConnection conn=null;       
 try {                
conn= (HttpsURLConnection) new URL(url).openConnection();                
conn.setConnectTimeout(5000);//连接延时                
conn.setReadTimeout(5000);//读取延时                
int code = conn.getResponseCode();               
 if(code==200){                    
InputStream inputStream = conn.getInputStream();                   
 //根据网络获取的输入流生成Bitmap对象                   
 Bitmap bitmap = BitmapFactory.decodeStream(inputStream);                   
 return bitmap;            }       
 } catch (IOException e) 
{            e.printStackTrace();       
 }finally {           
 if(conn!=null){               
 conn.disconnect();           
     }        
   }       
 return  null;  
  }
}
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import java.io.File;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
/** 
* Created by 毛麒添 on 2017/1/14 0014. 
* 图片三级缓存之本地缓存 
* 设置和获取本地图片缓存的方法(缓存放在Sdcard中) 
*/
public class LocalCacheUtils {     
//存放本地缓存的地址     
private static final  String CACHE_PATH=Environment.getExternalStorageDirectory().getAbsolutePath()+"bitmap_cache";     
//设置本地图片缓存   
 public void setLocalBitmapCache(Bitmap bitmap, String url)
{        
File dir=new File(CACHE_PATH);       
 if(!dir.exists()||!dir.isDirectory()){//如果不存在或者不是一个文件夹           
//创建文件夹            
dir.mkdirs();        
}        
String filename = Md5Util.encoder(url);//给文件名称使用MD5加密       
 //创建缓存文件       
 File bitmapcachefile=new File(dir,filename);        
try {            
//图片压缩格式,压缩比例            
bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(bitmapcachefile));        
} catch (FileNotFoundException e) {            
e.printStackTrace();        
}    
}   
 //获取本地图片缓存    
public Bitmap getLocalBitmapCache(String url){       
 //根据图片路径和名称获取图片       
 File bitmapcachefile=new File(CACHE_PATH,Md5Util.encoder(url));       
 if(bitmapcachefile.exists()){           
 try {               
 Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapcachefile));               
 return bitmap;            
} catch (FileNotFoundException e) { 
       e.printStackTrace();           
 }        
}        
return null;    
}}
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Md5Util {   
/**    
* 给指定字符串按照md5算法去加密   
 * @param psd 需要加密的字符串    
* @return  返回字符串     
*/  
 public static String encoder(String psd) {     
 try {        
 //1,指定加密算法类型         
MessageDigest digest = MessageDigest.getInstance("MD5");        
 //2,将需要加密的字符串中转换成byte类型的数组,然后进行随机哈希过程        
 byte[] bs = digest.digest(psd.getBytes());
//       System.out.println(bs.length);         
//3,循环遍历bs,然后让其生成32位字符串,固定写法        
 //4,拼接字符串过程         
StringBuffer stringBuffer = new StringBuffer();        
 for (byte b : bs) {           
 int i = b & 0xff;            
//int类型的i需要转换成16进制字符            
String hexString = Integer.toHexString(i);     i
f(hexString.length()<2){              
 hexString = "0"+hexString;           
 }            
stringBuffer.append(hexString);         
}                 
return stringBuffer.toString();    
  } catch (NoSuchAlgorithmException e) {  
       e.printStackTrace();     
 }      return "";  
 }
}
public class MemoryCacheUtils {
//使用hashmap 存储图片
private HashMap<String,Bitmap> myMemoryCache=new HashMap<String,Bitmap>();

//设置内存缓存
public void setMemoryCache(String url,Bitmap bitmap){

myMemoryCache.put(url,bitmap);
}

//获取内存缓存public Bitmap getMemoryCache(String url){
 return myMemoryCache.get(url);
}
public class MemoryCacheUtils 
{
private HashMap<String,SoftReference<Bitmap>> myMemoryCache=new HashMap<String,SoftReference<Bitmap>>();
//设置内存缓存
public void setMemoryCache(String url,Bitmap bitmap)
{
  SoftReference<Bitmap> bitmapSoftReference=new SoftReference<Bitmap>(bitmap);
    myMemoryCache.put(url,bitmapSoftReference);
}
/获取内存缓存
public Bitmap getMemoryCache(String url){ 
SoftReference<Bitmap> bitmapSoftReference=myMemoryCache.get(url);
if(bitmapSoftReference!=null){
Bitmap bitmap = bitmapSoftReference.get();    
  return bitmap;
}
return null;
}

经过这一番改造,垃圾回收器确实是可以起作用了,但是通过一些前辈们的研究,谷歌官方还是不推荐我们使用软引用,官方文档是这样说的:

官方文档截图.png
抠脚的英语翻译:
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
官方文档链接地址:
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

而在文档中则是推荐我们使用LruCache(使用v4包中的)这个类来做内存缓存,其实他的本质也是对HashMap的封装。好吧,继续改造

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class MemoryCacheUtils 
{
private LruCache<String,Bitmap> myMemoryCache;

public MemoryCacheUtils(){    
long maxMemory = Runtime.getRuntime().maxMemory();//获取分配给每个App的内存大小    
myMemoryCache=new LruCache<String,Bitmap>((int) (maxMemory/8)){        
//返回每个对象的大小        
@Override        
protected int sizeOf(String key, Bitmap value) {           
 int byteCount = value.getByteCount();           
 return byteCount;        
       }    
   };
}
//设置内存缓存
public void setMemoryCache(String url,Bitmap bitmap)
{
  myMemoryCache.put(url,bitmap);
}
/获取内存缓存
public Bitmap getMemoryCache(String url){ 
   return myMemoryCache.get(url);
}
import android.graphics.Bitmap;
import android.widget.ImageView;
/** 
* Created by 毛麒添 on 2017/1/14 0014. 
* 自己创建三级缓存加载图片 
*/
public class BitmapUtils {    
//网络获取图片工具类对象    
private NetCacheUtils netCacheUtils;   
private MemoryCacheUtils memoryCacheUtils;    
private LocalCacheUtils localCacheUtils;    
private Bitmap bitmap;    

public BitmapUtils(){       
 localCacheUtils=new LocalCacheUtils();        
memoryCacheUtils=new MemoryCacheUtils();       
 netCacheUtils=new NetCacheUtils(localCacheUtils,memoryCacheUtils);    
}    
/**     
* 显示图片的方法     
* @param imageView 需要设置图片的图片对象    
* @param url 请求地址    
* 优先从内存中加载图片    
* 其次从本地中(Sdcard)加载图片     
* 最后从网络中获取图片     
*/    
public void displayBitmapImage(ImageView imageView, String url) {       
 //从缓存缓存中获取图片        
bitmap = memoryCacheUtils.getMemoryCache(url);       
 if(bitmap!=null){
//如果内存中图片缓存不为空,直接将其设置给ImageView           
 imageView.setImageBitmap(bitmap);           
 return;        
}        
//从本地缓存中获取图片       
bitmap = localCacheUtils.getLocalBitmapCache(url);        
if(bitmap !=null){//如果本地图片缓存不为空,直接将其设置给ImageView            
imageView.setImageBitmap(bitmap);           
 return;        
}       
 //从网络获取图片        
netCacheUtils.getBitmapFromNet(imageView,url);   
 }
}

终于,图片三级缓存的小框架已经搭建好,我们使用ListView或者ViewPager的控件加载图片的时候在适配器getView()方法中给ImageView设置图片就可以使用这个图片三级缓存。

最后

xutils3 源码结构.png

其实做了这么多的东西,在加载很多的图片的时候还是会出现OOM,通过看Xutils3的源码结构,其实他的图片加载也是运用三级缓存结构,但是他做的事情的远远不至于我上面所说的这么简单,学无止境,所以我们可以先把核心的东西给先弄明白了解,再继续往里深入,菜鸟通过积累,总有一天会成为大鹏。

上一篇 下一篇

猜你喜欢

热点阅读