Android圖片三級緩存策略(網(wǎng)絡、本地、內存緩存)
一、簡介
現(xiàn)在的Android應用程序中,不可避免的都會使用到圖片,如果每次加載圖片的時候都要從網(wǎng)絡重新拉取,這樣不但很耗費用戶的流量,而且圖片加載的也會很慢,用戶體驗很不好。所以一個應用的圖片緩存策略是很重要的。通常情況下,Android應用程序中圖片的緩存策略采用“內存-本地-網(wǎng)絡”三級緩存策略,首先應用程序訪問網(wǎng)絡拉取圖片,分別將加載的圖片保存在本地SD卡中和內存中,當程序再一次需要加載圖片的時候,先判斷內存中是否有緩存,有則直接從內存中拉取,否則查看本地SD卡中是否有緩存,SD卡中如果存在緩存,則圖片從SD卡中拉取,否則從網(wǎng)絡加載圖片。依據(jù)這三級緩存機制,可以讓我們的應用程序在加載圖片的時候做到游刃有余,有效的避免內存溢出。
PS:當然現(xiàn)在處理網(wǎng)絡圖片的時候,一般人都會選擇XUtils中的BitmapUtil,它已經(jīng)將網(wǎng)絡緩存處理的相當好了,使用起來非常方便--本人就一直在用。仿照BitMapUtil的實現(xiàn)思路,定制一個自己的圖片加載工具,來理解一下三級緩存的策略,希望對自己會有一個提升。
二、網(wǎng)絡緩存
網(wǎng)絡拉取圖片嚴格來講不能稱之為緩存,實質上就是下載url對應的圖片,我們這里姑且把它看作是緩存的一種。仿照BitmapUtil中的display方法,我自己定制的CustomBitmapUtils也定義這個方法,根據(jù)傳入的url,將圖片設置到ivPic控件上。
public void display(ImageView ivPic, String url) {
}
定義網(wǎng)絡緩存的工具類,在訪問網(wǎng)絡的時候,我使用了AsyncTask來實現(xiàn),在AsyncTask的doInBackGround方法里下載圖片,然后將 圖片設置給ivPic控件,AsyncTask有三個泛型,其中第一個泛型是執(zhí)行異步任務的時候,通過execute傳過來的參數(shù),第二個泛型是更新的進度,第三個泛型是異步任務執(zhí)行完成之后,返回來的結果,我們這里返回一個Bitmap。具體的下載實現(xiàn)代碼如下:
/** * 網(wǎng)絡緩存的工具類 * * @author ZHY * */ public class NetCacheUtils
{ private LocalCacheUtils localCacheUtils;
private MemoryCacheUtils memoryCacheUtils;
public NetCacheUtils()
{
localCacheUtils = new LocalCacheUtils();
memoryCacheUtils = new MemoryCacheUtils();
}
/** * 從網(wǎng)絡下載圖片 *
* @param ivPic *
@param url */
public void getBitmapFromNet(ImageView ivPic, String url)
{
// 訪問網(wǎng)絡的操作一定要在子線程中進行,采用異步任務實現(xiàn) MyAsyncTask task = new MyAsyncTask(); task.execute(ivPic, url); }
/** * 第一個泛型--異步任務執(zhí)行的時候,通過execute傳過來的參數(shù);
第二個泛型--更新進度; 第三個泛型--異步任務執(zhí)行以后返回的結果
* * @author ZHY * */
private class MyAsyncTask extends AsyncTask
{ private ImageView ivPic; private String url; // 耗時任務執(zhí)行之前
--主線程
@Override protected void onPreExecute()
{ super.onPreExecute();
}
// 后臺執(zhí)行的任務
@Override protected Bitmap doInBackground(Object... params)
{
// 執(zhí)行異步任務的時候,將URL傳過來 ivPic = (ImageView) params[0]; url = (String) params[1]; Bitmap bitmap = downloadBitmap(url);
// 為了保證ImageView控件和URL一一對應,給ImageView設定一個標記 ivPic.setTag(url);
// 關聯(lián)ivPic和URL return bitmap; }
// 更新進度 --主線程
@Override protected void onProgressUpdate(Void... values)
{ super.onProgressUpdate(values);
}
// 耗時任務執(zhí)行之后--主線程
@Override protected void onPostExecute(Bitmap result)
{ String mCurrentUrl = (String) ivPic.getTag();
if (url.equals(mCurrentUrl))
{ ivPic.setImageBitmap(result);
System.out.println("從網(wǎng)絡獲取圖片");
// 從網(wǎng)絡加載完之后,將圖片保存到本地SD卡一份,保存到內存中一份
localCacheUtils.setBitmap2Local(url, result);
// 從網(wǎng)絡加載完之后,將圖片保存到本地SD卡一份,保存到內存中一份 memoryCacheUtils.setBitmap2Memory(url, result);
} } }
/** * 下載網(wǎng)絡圖片 *
* @param url * @return */
private Bitmap downloadBitmap(String url)
{
HttpURLConnection conn = null;
try
{
URL mURL = new URL(url);
// 打開HttpURLConnection連接
conn = (HttpURLConnection) mURL.openConnection();
// 設置參數(shù) conn.setConnectTimeout(5000); conn.setReadTimeout(5000); conn.setRequestMethod("GET");
// 開啟連接 conn.connect();
// 獲得響應碼 int code = conn.getResponseCode();
if (code == 200) {
// 相應成功,獲得網(wǎng)絡返回來的輸入流
InputStream is = conn.getInputStream();
// 圖片的輸入流獲取成功之后,設置圖片的壓縮參數(shù),將圖片進行壓縮 BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
// 將圖片的寬高都壓縮為原來的一半,在開發(fā)中此參數(shù)需要根據(jù)圖片展示的大小來確定,否則可能展示的不正常
options.inPreferredConfig = Bitmap.Config.RGB_565;
// 這個壓縮的最小
// Bitmap bitmap = BitmapFactory.decodeStream(is); Bitmap bitmap = BitmapFactory.decodeStream(is, null, options)
;// 經(jīng)過壓縮的圖片 return bitmap;
} }
catch (Exception e)
{ e.printStackTrace();
}
finally {
// 斷開連接 conn.disconnect();
}
return null;
} }
三、本地緩存
從網(wǎng)絡加載完圖片之后,將圖片保存到本地SD卡中。在加載圖片的時候,判斷一下SD卡中是否有圖片緩存,如果有,就直接從SD卡加載圖片。本地緩存的工具類中有兩個公共的方法,分別是向本地SD卡設置網(wǎng)絡圖片,獲取SD卡中的圖片。設置圖片的時候采用鍵值對的形式進行存儲,將圖片的url作為鍵,作為文件的名字,圖片的Bitmap作位值來保存。由于url含有特殊字符,不能直接作為圖片的名字來存儲,故采用url的MD5值作為文件的名字。
/**
* 本地緩存
*
* @author ZHY
*
*/
public class LocalCacheUtils {
/**
* 文件保存的路徑
*/
public static final String FILE_PATH = Environment
.getExternalStorageDirectory().getAbsolutePath() + "/cache/pics";
/**
* 從本地SD卡獲取網(wǎng)絡圖片,key是url的MD5值
*
* @param url
* @return
*/
public Bitmap getBitmapFromLocal(String url) {
try {
String fileName = MD5Encoder.encode(url);
File file = new File(FILE_PATH, fileName);
if (file.exists()) {
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(
file));
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 向本地SD卡寫網(wǎng)絡圖片
*
* @param url
* @param bitmap
*/
public void setBitmap2Local(String url, Bitmap bitmap) {
try {
// 文件的名字
String fileName = MD5Encoder.encode(url);
// 創(chuàng)建文件流,指向該路徑,文件名叫做fileName
File file = new File(FILE_PATH, fileName);
// file其實是圖片,它的父級File是文件夾,判斷一下文件夾是否存在,如果不存在,創(chuàng)建文件夾
File fileParent = file.getParentFile();
if (!fileParent.exists()) {
// 文件夾不存在
fileParent.mkdirs();// 創(chuàng)建文件夾
}
// 將圖片保存到本地
bitmap.compress(CompressFormat.JPEG, 100,
new FileOutputStream(file));
} catch (Exception e) {
e.printStackTrace();
}
}
}
四、內存緩存
內存緩存說白了就是在內存中保存一份圖片集合,首先會想到HashMap這種鍵值對的形式來進行保存,以url作為key,bitmap作為value。但是在Java中這種默認的new對象的方式是強引用,JVM在進行垃圾回收的時候是不會回收強引用的,所以如果加載的圖片過多的話,map會越來越大,很容易出現(xiàn)OOM異常。在Android2.3之前,還可以通過軟引用或者弱引用來解決,但是Android2.3之后,Google官方便不再推薦軟引用了,Google推薦我們使用LruCache。
在過去,我們經(jīng)常會使用一種非常流行的內存緩存技術的實現(xiàn),即軟引用或弱引用 (SoftReference or WeakReference)。但是現(xiàn)在已經(jīng)不再推薦使用這種方式了,因為從 Android 2.3 (API Level 9)開始,垃圾回收器會更傾向于回收持有軟引用或弱引用的對象,這讓軟引用和弱引用變得不再可靠。另外,Android 3.0 (API Level 11)中,圖片的數(shù)據(jù)會存儲在本地的內存當中,因而無法用一種可預見的方式將其釋放,這就有潛在的風險造成應用程序的內存溢出并崩潰。
為了能夠選擇一個合適的緩存大小給LruCache, 有以下多個因素應該放入考慮范圍內,例如:
你的設備可以為每個應用程序分配多大的內存?Android默認是16M。 設備屏幕上一次最多能顯示多少張圖片?有多少圖片需要進行預加載,因為有可能很快也會顯示在屏幕上? 你的設備的屏幕大小和分辨率分別是多少?一個超高分辨率的設備(例如 Galaxy Nexus) 比起一個較低分辨率的設備(例如 Nexus S),在持有相同數(shù)量圖片的時候,需要更大的緩存空間。 圖片的尺寸和大小,還有每張圖片會占據(jù)多少內存空間。 圖片被訪問的頻率有多高?會不會有一些圖片的訪問頻率比其它圖片要高?如果有的話,你也許應該讓一些圖片常駐在內存當中,或者使用多個LruCache 對象來區(qū)分不同組的圖片。 你能維持好數(shù)量和質量之間的平衡嗎?有些時候,存儲多個低像素的圖片,而在后臺去開線程加載高像素的圖片會更加的有效。 以上是Google對LruCache的描述,其實LruCache的使用非常簡單,跟Map非常相近,只是在創(chuàng)建LruCache對象的時候需要指定它的最大允許內存,一般設置為當前應用程序的最大運行內存的八分之一即可。
/**
* 內存緩存
*
* @author ZHY
*
*/
public class MemoryCacheUtils {
/*
* 由于map默認是強引用,所有在JVM進行垃圾回收的時候不會回收map的引用
*/
// private HashMap<string, bitmap=""> map = new HashMap<string, bitmap="">();
// 軟引用的實例,在內存不夠時,垃圾回收器會優(yōu)先考慮回收
// private HashMap<string, bitmap="">> mSoftReferenceMap = new
// HashMap<string, bitmap="">>();
// LruCache
private LruCache<string, bitmap=""> lruCache;
public MemoryCacheUtils() {
// lruCache最大允許內存一般為Android系統(tǒng)分給每個應用程序內存大?。JAndroid系統(tǒng)給每個應用程序分配16兆內存)的八分之一(推薦)
// 獲得當前應用程序運行的內存大小
long mCurrentMemory = Runtime.getRuntime().maxMemory();
int maxSize = (int) (mCurrentMemory / 8);
// 給LruCache設置最大的內存
lruCache = new LruCache<string, bitmap="">(maxSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
// 獲取每張圖片所占內存的大小
// 計算方法是:圖片顯示的寬度的像素點乘以高度的像素點
int byteCount = value.getRowBytes() * value.getHeight();// 獲取圖片占用內存大小
return byteCount;
}
};
}
/**
* 從內存中讀取Bitmap
*
* @param url
* @return
*/
public Bitmap getBitmapFromMemory(String url) {
// Bitmap bitmap = map.get(url);
// SoftReference<bitmap> softReference = mSoftReferenceMap.get(url);
// Bitmap bitmap = softReference.get();
// 軟引用在Android2.3以后就不推薦使用了,Google推薦使用lruCache
// LRU--least recently use
// 最近最少使用,將內存控制在一定的大小內,超過這個內存大小,就會優(yōu)先釋放最近最少使用的那些東東
Bitmap bitmap = lruCache.get(url);
return bitmap;
}
/**
* 將圖片保存到內存中
*
* @param url
* @param bitmap
*/
public void setBitmap2Memory(String url, Bitmap bitmap) {
// 向內存中設置,key,value的形式,首先想到HashMap
// map.put(url, bitmap);
// 保存軟引用到map中
// SoftReference<bitmap> mSoftReference = new
// SoftReference<bitmap>(bitmap);
// mSoftReferenceMap.put(url, mSoftReference);
lruCache.put(url, bitmap);
}
}</bitmap></bitmap></bitmap></string,></string,></string,></string,></string,></string,>
好了?,F(xiàn)在三級緩存策略封裝完畢,接下來定制我們自己的BitmapUtils
/**
* 自定義的加載圖片的工具類,類似于Xutils中的BitmapUtil,在實際使用中,一般使用BitmapUtil,為了理解三級緩存,
* 這里模擬BitmapUtil自定義了CustomBitmapUtil
*
* @author ZHY
*
*/
public class CustomBitmapUtils {
private Bitmap bitmap;
private NetCacheUtils netCacheUtils;
private LocalCacheUtils localCacheUtils;
private MemoryCacheUtils memoryCacheUtils;
public CustomBitmapUtils() {
netCacheUtils = new NetCacheUtils();
localCacheUtils = new LocalCacheUtils();
memoryCacheUtils = new MemoryCacheUtils();
}
/**
* 加載圖片,將當前URL對應的圖片顯示到ivPic的控件上
*
* @param ivPic
* ImageView控件
* @param url
* 圖片的地址
*/
public void display(ImageView ivPic, String url) {
// 設置默認顯示的圖片
ivPic.setImageResource(R.drawable.ic_launcher);
// 1、內存緩存
bitmap = memoryCacheUtils.getBitmapFromMemory(url);
if (bitmap != null) {
ivPic.setImageBitmap(bitmap);
System.out.println("從內存緩存中加載圖片");
return;
}
// 2、本地磁盤緩存
bitmap = localCacheUtils.getBitmapFromLocal(url);
if (bitmap != null) {
ivPic.setImageBitmap(bitmap);
System.out.println("從本地SD卡加載的圖片");
memoryCacheUtils.setBitmap2Memory(url, bitmap);// 將圖片保存到內存
return;
}
// 3、網(wǎng)絡緩存
netCacheUtils.getBitmapFromNet(ivPic, url);
/*
* 從網(wǎng)絡獲取圖片之后,將圖片保存到手機SD卡中,在進行圖片展示的時候,優(yōu)先從SD卡中讀取緩存,key是圖片的URL的MD5值,
* value是保存的圖片bitmap
*/
}
}
在mainActivity中使用ListView加載網(wǎng)絡圖片
/**
* Android中三級緩存--網(wǎng)絡緩存-本地緩存-內存緩存
*
* @author ZHY
*
*/
public class MainActivity extends Activity {
private ListView list;
private Button btn;
private CustomBitmapUtils utils;
private static final String BASE_URL = "http://192.168.0.148:8080/pics";
// 初始化一些網(wǎng)絡圖片
String[] urls = { BASE_URL + "/1.jpg", BASE_URL + "/2.jpg",
BASE_URL + "/3.jpg", BASE_URL + "/4.jpg", BASE_URL + "/5.jpg",
BASE_URL + "/6.jpg", BASE_URL + "/7.jpg", BASE_URL + "/8.jpg",
BASE_URL + "/9.jpg", BASE_URL + "/10.jpg", BASE_URL + "/11.jpg",
BASE_URL + "/12.jpg", BASE_URL + "/13.jpg", BASE_URL + "/14.jpg",
BASE_URL + "/15.jpg", BASE_URL + "/16.jpg", BASE_URL + "/17.jpg",
BASE_URL + "/18.jpg", BASE_URL + "/19.jpg", BASE_URL + "/20.jpg",
BASE_URL + "/21.jpg", BASE_URL + "/22.jpg", BASE_URL + "/23.jpg",
BASE_URL + "/24.jpg", BASE_URL + "/25.jpg", BASE_URL + "/26.jpg",
BASE_URL + "/27.jpg", BASE_URL + "/28.jpg", BASE_URL + "/29.jpg",
BASE_URL + "/30.jpg" };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
list = (ListView) findViewById(R.id.list);
btn = (Button) findViewById(R.id.btn_load);
utils = new CustomBitmapUtils();
// 加載網(wǎng)絡圖片
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
MyAdapter adapter = new MyAdapter();
list.setAdapter(adapter);
}
});
}
class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return urls.length;
}
@Override
public String getItem(int position) {
return urls[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = View.inflate(MainActivity.this,
R.layout.item_list, null);
holder = new ViewHolder();
holder.ivPic = (ImageView) convertView.findViewById(R.id.iv);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
utils.display(holder.ivPic, urls[position]);
return convertView;
}
class ViewHolder {
ImageView ivPic;
}
}
}
運行的結果如下:
程序第一次運行,日志打印如下

之后將圖片緩存在SD卡中,從本地加載圖片

然后將圖片緩存到內存,從內存加載圖片

OK,到目前為止,Android中圖片的三級緩存原理就都介紹完了,我自己本人受益匪淺,希望能夠幫助到需要的朋友。需要源碼的請點擊如下鏈接進行下載。
相關文章
詳解Android開啟OTG功能/USB?Host?API功能
這篇文章主要介紹了Android開啟OTG功能/USB?Host?API功能,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
Android 監(jiān)聽Notification 被清除實例代碼
本文主要介紹Android 監(jiān)聽Notification 事件,這里給大家提供實例代碼進行參考,有需要的小伙伴可以參考下2016-07-07
Android onMeasure與onDraw及自定義屬性使用示例
這篇文章主要介紹了Android onMeasure與onDraw及自定義屬性使用示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-02-02

