圖片的三級緩存
一、概述
一開始在學習Android的時候,處理圖片的時候,每次獲取圖片都是直接從網絡上面加載圖片,
但是在開發項目的過程中,每次點擊進入app裏面,圖片都要慢慢的再一次從網絡上面加載。
給用戶的體驗非常不好,第一個等待的時間非常令人dan 疼
第二個給用戶的流量造成了不必要的浪費
因此提出圖片的三級緩存策略,
所謂的三級緩存:就是在手機加載圖片的時候,
1、首先從內存中加載,
2、如果內存中沒有的話,從sd卡上獲取,讀取到之後將圖片寫入到內存中
3、如果sd卡上沒有的話,從網絡上獲取,從網絡上獲取之後,寫入到sd卡上,還有內存中
總體效果圖如下:(比較模糊了點將就看吧)
想要寫一個網絡加載的框架,首先來寫一個內存緩存的類:
package com.jishihuitong.bitmaputil;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* Created by hss on 2016/8/4.
* <p>圖片內存緩存工具類
*/
public class MemoryCache {
/**
* 內存緩存對象
*/
private LruCache<String, Bitmap> mLruCache;
private static final String TAG = "MemoryCache";
/**
* 構造方法,初始化LruCache
*/
public MemoryCache() {
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheMemory = maxMemory/8;
mLruCache = new LruCache<String,Bitmap>(cacheMemory){
@Override
protected int sizeOf(String key, Bitmap value) {
LogUtil.d(TAG,"創建了lruCache實例");
return value.getByteCount();
}
};
}
/**
* 從內存中獲取Bitmap對象
* @param url key
* @return bitmap對象
*/
public Bitmap getBitmap(String url){
LogUtil.d(TAG,"從內存中獲取圖片"+url);
return mLruCache.get(url);
}
/**
* 將圖片對象按照鍵值對保存在內存中
* @param url key
* @param bitmap value
*/
public void putBitmap(String url,Bitmap bitmap){
LogUtil.d(TAG,"將圖片保存到內存中"+url);
mLruCache.put(url,bitmap);
}
}
然後來寫 本地緩存 實例,本地一般是保存在了sd卡上面,
但是有時候需要判斷一下手機有沒有sd卡,要是沒有的話我們就把他存在緩存目錄裏面,
將文件寫入到本地的話,爲了安全操作,我們需要對文件名進行一次MD5加密,
這裏是MD5的代碼
package com.jishihuitong.bitmaputil;
import java.security.MessageDigest;
/**
* Created by hss on 2016/8/4.
*/
public class MD5Encoder {
public static String encode(String string) throws Exception {
byte[] hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if ((b & 0xFF) < 0x10) {
hex.append("0");
}
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString();
}
}
將圖片保存到本地的代碼
package com.jishihuitong.bitmaputil;
import android.content.Context;
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 hss on 2016/8/4.
*/
public class SDCardCache {
private static final String TAG = "SDCardCache";
private static String sdcardCache;
/**
* 從sd卡緩存中獲取圖片對象
*
* @param url 圖片URL
* @return bitmap
*/
public Bitmap getSDCardBitmap(Context context,String url) {
Bitmap bitmap;
String filename = null;
try {
filename = MD5Encoder.encode(url);
LogUtil.d(TAG,"獲取到文件的filename = "+filename);
bitmap = BitmapFactory.decodeStream(new FileInputStream(new File(getSDCache(context), filename)));
if (bitmap != null) {
LogUtil.d(TAG,"從SD卡中獲取到圖片"+url);
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
/**
* 將圖片緩存到本sd卡
* @param context 上下文對象
* @param url 圖片URL
* @param bitmap 圖片對象
*/
public void saveBitmapToSDCard(Context context,String url, Bitmap bitmap) {
String filename = null;
try {
filename = MD5Encoder.encode(url);
LogUtil.d(TAG,"保存的圖片的filename = "+filename);
} catch (Exception e) {
e.printStackTrace();
}
File file = new File(getSDCache(context), filename);
File parentFile = file.getParentFile();
if(!parentFile.exists()){
LogUtil.d(TAG,"父目錄不存在,創建");
parentFile.mkdirs();
}
//將圖片保存到本地
try {
LogUtil.d(TAG,"文件保存在了本地");
//將圖片保存到本地的方法,第一個參數 圖片的格式,第二個參數圖片的質量100表示最高質量
//第三個參數 圖片的流
bitmap.compress(Bitmap.CompressFormat.PNG, 100,new FileOutputStream(file));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 獲取到圖片在本地緩存的目錄
* @param context 上下文對象
* @return 緩存目錄的字符串
*/
public String getSDCache(Context context){
String state = Environment.getExternalStorageState();
if(state==Environment.MEDIA_MOUNTED){
LogUtil.d(TAG,"SD卡掛載,獲取到sd卡目錄");
sdcardCache = Environment.getExternalStorageDirectory() + "cache/";
}else{
LogUtil.d(TAG,"SD卡不在,獲取到cache目錄");
sdcardCache = context.getCacheDir().getAbsolutePath();
LogUtil.d(TAG,"sdcardCache = "+sdcardCache);
}
return sdcardCache;
}
}
然後說從網絡上獲取圖片的操作,這個操作涉及到訪問網絡,這個例子裏面使用的是asyncTask
在asyncTask裏面,需要有在UI線程的回調,將圖片對象設置到控件中,所以也需要一個ImageView對象的引用
把圖片下載下來之後,還要將圖片保存到本地 還有內存中,
所以該網絡緩存的工具類中,需要有ImageView的引用,還有本地和內存的緩存對象
我們將他們在構造方法中進行實現
訪問網絡的工具類:
package com.jishihuitong.bitmaputil;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Created by hss on 2016/8/4.
*/
public class NetCacheUtil {
/**
* 內存緩存對象
*/
private MemoryCache mMemoryCache;
/**
* 本地緩存對象
*/
private SDCardCache mSDCardCache;
/**
* ImageView對象的引用
*/
public ImageView mImageView;
/**
* 全局使用的URL
*/
public String mUrl;
/**
* 上下文對象
*/
public Context context;
public Handler handler = new Handler();
public static final String TAG = "NetCacheUtil";
public NetCacheUtil(Context context,MemoryCache memoryCache, SDCardCache sdcardCache) {
this.mMemoryCache = memoryCache;
this.mSDCardCache = sdcardCache;
this.context = context;
}
public void getBitmapFromNet(ImageView view,String url){
//開啓一個任務去下載圖片,並設置到view上
new BitmapAsyncTask().execute(view,url);
}
/**
* 下載圖片的多線程任務
* <p/>
* 第一個泛型 : 參數類型 對應doInBackground()
* 第二個泛型 : 更新進度 對應onProgressUpdate()
* 第三個泛型 : 返回結果result 對應onPostExecute
*/
class BitmapAsyncTask extends AsyncTask<Object,Void,Bitmap>{
@Override
protected Bitmap doInBackground(Object... params) {
//獲取到參數
mImageView = (ImageView) params[0];
mUrl = (String) params[1];
//在主線程執行,讓ImageView和URL進行綁定,防止圖片錯亂
handler.post(new Runnable() {
@Override
public void run() {
mImageView.setTag(mUrl);
}
});
//執行下載圖片的任務
Bitmap bitmap = downloadBitmap(mUrl);
return bitmap;
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
//該方法將在DoInBackground之後執行,
if(bitmap!=null){
String url = (String) mImageView.getTag();
//防止圖片錯亂,對剛纔的圖片URL和現在獲取到的URL進行比對
if(url.equals(mUrl)){
mImageView.setImageBitmap(bitmap);
LogUtil.d(TAG,"將圖片保存在本地和內存中" +context +" "+mUrl+" "+bitmap );
mSDCardCache.saveBitmapToSDCard(context, mUrl, bitmap);
mMemoryCache.putBitmap(mUrl, bitmap);
}
}
}
}
/**
* 鏈接網絡下載圖片,下載下來之後將圖片保存在本地和內存中
* @param mUrl
* @return
*/
private Bitmap downloadBitmap(String mUrl) {
HttpURLConnection conn =null;
try {
conn = (HttpURLConnection) new URL(mUrl).openConnection();
conn.setReadTimeout(5 * 1000);
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
conn.connect();
int code = conn.getResponseCode();
if(code == 200){
InputStream inputStream = conn.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(conn!=null){
conn.disconnect();
conn = null;
}
}
return null;
}
}
三個階段的緩存工具類都準備好了,接下來定義一個類,一個方法,可以讓他們一次性執行,那麼這個方法就是display方法
package com.jishihuitong.bitmaputil;
import android.content.Context;
import android.graphics.Bitmap;
import android.widget.ImageView;
/**
* Created by hss on 2016/8/4.
* <p/>
* 綜合使用三級緩存的工具類
*/
public class BitmapUtil {
/**
* 內存緩存類
*/
private final MemoryCache memoryCache;
/**
* SD卡緩存類
*/
private final SDCardCache sdCardCache;
/**
* 網絡緩存類
*/
private final NetCacheUtil netCacheUtil;
/**
* 上下文對象
*/
private Context context;
public BitmapUtil(Context context) {
memoryCache = new MemoryCache();
sdCardCache = new SDCardCache();
netCacheUtil = new NetCacheUtil(context, memoryCache, sdCardCache);
this.context = context;
}
/**
* 調用該方法可以進入圖片三級緩存流程,將圖片展示在UI界面上
* @param view ImageView對象
* @param url 圖片訪問路徑
*/
public void display(ImageView view,String url){
//根URL從內存中獲取,
Bitmap memoryCacheBitmap = memoryCache.getBitmap(url);
//如果獲取到的不爲空,將圖片設置到控件上,返回
if(memoryCacheBitmap!=null){
view.setImageBitmap(memoryCacheBitmap);
return;
}
//如果內存獲取爲空的話,再根據URL從內存中獲取,
Bitmap sdCardBitmap = sdCardCache.getSDCardBitmap(context, url);
//如果不爲空,將圖片設置給控件,然後將圖片保存在內存中 返回
if(sdCardBitmap!=null){
view.setImageBitmap(sdCardBitmap);
memoryCache.putBitmap(url,sdCardBitmap);
return;
}
//本地也沒有的話就只能到網絡去找了,該方法裏有直接將圖片設置給控件的方法
netCacheUtil.getBitmapFromNet(view,url);
}
}
工具類都寫好之後,我們在一個activity裏面簡單的調用一下,
首先整個佈局就是一個ListView,然後裏面的item就是一個ImageView
佈局代碼如下
main_activity.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/lv">
</ListView>
</LinearLayout>
view_listview.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
android:orientation="vertical"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="180dp"
android:id="@+id/iv"
android:src="@mipmap/empty_photo"
/>
</LinearLayout>
在MainActivity裏面調用方法 讓圖片填充ListView
package com.jishihuitong.bitmaputil;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.media.Image;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ArrayList<String> list;
private BitmapUtil bitmapUtil;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//準備數據
list = new ArrayList<String>();
list.add("http://192.168.0.11:8080/images/10001.png");
list.add("http://192.168.0.11:8080/images/10002.png");
list.add("http://192.168.0.11:8080/images/10003.png");
list.add("http://192.168.0.11:8080/images/10004.png");
list.add("http://192.168.0.11:8080/images/10005.png");
list.add("http://192.168.0.11:8080/images/10006.png");
//初始化圖片緩存工具類
bitmapUtil = new BitmapUtil(this);
ListView lv = (ListView) findViewById(R.id.lv);
lv.setAdapter(new MyAdapter(getApplication(),0,list));
}
class MyAdapter extends ArrayAdapter {
public MyAdapter(Context context, int resource, List<String> list) {
super(context, resource, list);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ImageView iv;
if (convertView == null) {
view = View.inflate(getApplicationContext(), R.layout.view_listview, null);
} else {
view = convertView;
}
iv = (ImageView) view.findViewById(R.id.iv);
iv.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.empty_photo));
//爲了防止圖片亂跳,給imageView設置標記
iv.setTag(list.get(position));
LogUtil.d(TAG, "訪問的URL爲 " + list.get(position));
//加載圖片
bitmapUtil.display(iv, list.get(position));
return view;
}
}
}
部署在tomcat上面的圖片路徑如圖所示: