三級緩存的概念:即,網絡,本地,內存,在安卓中,加載網絡資源(特別是圖片)是一件很消耗資源的資源的,因此我們使用三級緩存的形式,可以大大減少APP資源的消耗,增加開發效率,下面是三級緩存的流程圖
由流程圖可以知道,三級緩存最先訪問的是APP的內存,其次是本地緩存,最後纔是直接從網絡獲取圖片。下面我們開始將這三個部分一起來實現下
在開始之前呢我們先來設計下項目的架構和思路,項目的架構對以後的維護是相當重要的,我們定義幾個類,每個類分別實現以下功能
ImagLoader類:該類負責管理ImageView 顯示圖像的邏輯處理(只要負責圖像的顯示)
ImageCache 接口:該接口用於定義圖像緩存的方法
MemoryCache 類:該類實現了ImageCache 接口接口,用來將圖像緩存到APP內存和將圖像從APP內存中取出來
DiskCache 類:該類實現了ImageCache 接口接口,用來將圖像緩存到本地緩存和將圖像從本地緩存中取出來
DoubleCache 類:該類用來處理MemoryCache和DiskCache之間的邏輯關係,也就是先從APP內存獲取,再從本地緩存獲取的邏輯關係
1 首先我們先來定義ImageCache接口
public interface ImageCache {
public Bitmap get(String url);
public void set(String url,Bitmap bitmap);
}
該類中定義了兩個方法,get方法是從緩存裏面(包括本地緩存和內存緩存)獲取bitmap,set方法是把bitmap設置到緩存裏面
2 其次,我們來實現MemoryCache類
public class MemoryCache implements ImageCache {
private LruCache<String ,Bitmap>mMemoryCache;
public MemoryCache(){
final int max=(int) (Runtime.getRuntime().maxMemory()/1024);
mMemoryCache=new LruCache<String,Bitmap>(max/4){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes()*bitmap.getHeight()/1024;
}
};
}
@Override
public Bitmap get(String url) {
Bitmap bitmap=mMemoryCache.get(url);
if (bitmap==null){
Log.e("TAG","讀取內存緩存失敗");
}else {
Log.e("TAG","讀取內存緩存成功");
}
return bitmap;
}
@Override
public void set(String url, Bitmap bitmap) {
mMemoryCache.put(url,bitmap);
}
}
在該類中,我們使用了安卓提供的LruCache來實現內存緩存,通過Runtime.getRuntime().maxMemory()獲取APP當前可用內存的大小來定義使用內存緩存的閥值,避免出現OOM這種導致APP崩潰的尷尬事件,值得注意的是,使用LruCache的時候一定要注意key的值是否正確,否則會出現返回值爲空的現象。
關於LruCache的更多資料可用查看官網API介紹,本文不再做過多介紹
LruCache官方API地址
3 接下來我們來實現DiskCache類
public class DiskCache implements ImageCache {
// private String filepath="sdcard/cache/";
private String filepath= Environment.getExternalStorageDirectory().toString()+"/Imageloder/";
@Override
public Bitmap get(String url) {
Bitmap bitmap=BitmapFactory.decodeFile(filepath+url);
if (bitmap==null){
Log.e("TAG","讀取本地緩存失敗");
return null;
}else{
Log.e("TAG","讀取本地緩存成功");
return bitmap;
}
}
@Override
public void set(String url, Bitmap bitmap) {
File appDir = new File(Environment.getExternalStorageDirectory(), "Imageloder");
if (!appDir.exists()) {
appDir.mkdir();
Log.e("TAG","創建本地文件夾");
}
String fileName = url;
File file = new File(appDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
該類需要使用到讀取文件的權限,請在XML中註冊,在Android6.0以上的設備需要使用動態權限設置,本文直接指定使用5.1的SDK
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
在
4 在實現完本地緩存之後,我們開始來實現處理本地和內存的邏輯關係類
DoubleCache ,該類也是實現ImageCache接口,但是在get方法和put的方法中做了不同的處理:
public class DoubleCache implements ImageCache {
private ImageCache mMemoryCache;
private ImageCache mDiskCache;
public DoubleCache (){
mMemoryCache= new MemoryCache();
mDiskCache=new DiskCache();
}
@Override
public Bitmap get(String url) {
Bitmap bitmap=mMemoryCache.get(url);
if (bitmap==null){
bitmap=mDiskCache.get(url);
}
return bitmap;
}
@Override
public void set(String url, Bitmap bitmap) {
mMemoryCache.set(url,bitmap);
mDiskCache.set(url,bitmap);
}
}
在該類的get方法中,首先我們先通過mMemoryCache.get(url)來獲取內存中的bitmap對象,當內存中沒有bitmap返回時,我們將從本地緩存中
(mDiskCache.get(url))獲取bitmap對象,最後再返回bitmap對象
5 最後我們的在來實現ImagLoader
public class ImageLoader {
ImageCache imageCache=new MemoryCache();
ExecutorService mExecutorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void display(String url, ImageView imageView,String key){
Bitmap bitmap=imageCache.get(key);
if (bitmap!=null){
imageView.setImageBitmap(bitmap);
return;
}
submitLoadRequest(url,imageView,key);
}
public void setImageCache(ImageCache imageCache) {
this.imageCache = imageCache;
}
private void submitLoadRequest(final String url, final ImageView imageView,final String key){
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap=downloadImag(url);
if (bitmap==null){
Log.e("TAG","圖片下載失敗");
return;
}else {
Log.e("TAG","圖片下載成功");
imageCache.set(key,bitmap);
if (imageView.getTag().equals(url)){
imageView.setImageBitmap(bitmap);
}
}
}
});
}
private Bitmap downloadImag(String url){
Bitmap bitmap=null;
try {
URL url1=new URL(url);
final HttpURLConnection connection=(HttpURLConnection) url1.openConnection();
bitmap= BitmapFactory.decodeStream(connection.getInputStream());
connection.disconnect();
}catch (Exception e){
e.printStackTrace();
}
return bitmap;
}
}
在該類中,我們定義的一個公開的方法display給外部調用,display中有三個參數,url是指當前要顯示的圖片的網絡url地址,Imagview是當前要顯示圖片的控件,key是指圖片緩存到APP內存時的索引或者是圖片緩存到本地的文件名稱,可能有的人會問,名稱和索引直接用url不行?爲什麼要多定義一個看起來沒有用的key?在很多網絡圖片中,url是相當長的,而且可能會有一些字符是我們Android的文件系統無法正常識別的,這時候我們就不能直接用url做文件名,值得注意的是,在該類中,我們靈活定義了setImageCache方法,該方法的存在使得用戶可以很方便的定義自己使用哪種緩存方式(注:在三級緩存裏面不一定是三種緩存都要使用的,可以只使用本地,也可以只使用內存,也可以本地和內存混合使用)
6最後,我們在Activity中使用ImageLoader就可以了,本文的例子:
String url="http://img1.gamedog.cn/2014/04/24/119- 1404240UG30.jpg";
ImageLoader imageLoader;
ImageCache imageCache;
imageLoader=new ImageLoader();
imageCache=new DoubleCache();
imageLoader.setImageCache(imageCache);
imageLoader.display(url,imageView,"01.jpg");
運行結果: