摘要:原文首發(fā)于微信公眾號(hào),歡迎關(guān)注交流中緩存的使用比較普遍,使用相應(yīng)的緩存策略可以減少流量的消耗,也可以在一定程度上提高應(yīng)用的性能,如加載網(wǎng)絡(luò)圖片的情況,不應(yīng)該每次都從網(wǎng)絡(luò)上加載圖片,應(yīng)該將其緩存到內(nèi)存和磁盤中,下次直接從內(nèi)存或磁盤中獲取,緩
原文首發(fā)于微信公眾號(hào):jzman-blog,歡迎關(guān)注交流!
Android 中緩存的使用比較普遍,使用相應(yīng)的緩存策略可以減少流量的消耗,也可以在一定程度上提高應(yīng)用的性能,如加載網(wǎng)絡(luò)圖片的情況,不應(yīng)該每次都從網(wǎng)絡(luò)上加載圖片,應(yīng)該將其緩存到內(nèi)存和磁盤中,下次直接從內(nèi)存或磁盤中獲取,緩存策略一般使用 LRU(Least Recently Used) 算法,即最近最少使用算法,下面將從內(nèi)存緩存和磁盤緩存兩個(gè)方面以圖片為例 介紹 Android 中如何使用緩存,閱讀本文之前,請(qǐng)先閱讀上篇文章:
Bitmap之位圖采樣和內(nèi)存計(jì)算詳解
內(nèi)存緩存LruCache 是 Android 3.1 提供的一個(gè)緩存類,通過(guò)該類可以快速訪問(wèn)緩存的 Bitmap 對(duì)象,內(nèi)部采用一個(gè) LinkedHashMap 以強(qiáng)引用的方式存儲(chǔ)需要緩存的 Bitmap 對(duì)象,當(dāng)緩存超過(guò)指定的大小之前釋放最近很少使用的對(duì)象所占用的內(nèi)存。
注意:Android 3.1 之前,一個(gè)常用的內(nèi)存緩存是一個(gè) SoftReference 或 WeakReference 的位圖緩存,現(xiàn)在已經(jīng)不推薦使用了。Android 3.1 之后,垃圾回收器更加注重回收 SoftWeakference/WeakReference,這使得使用該種方式實(shí)現(xiàn)緩存很大程度上無(wú)效,使用 support-v4 兼容包中的 LruCache 可以兼容 Android 3.1 之前的版本。
LruCache 的使用初始化 LruCache
首先計(jì)算需要的緩存大小,具體如下:
//第一種方式: ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); //獲取當(dāng)前硬件條件下應(yīng)用所占的大致內(nèi)存大小,單位為M int memorySize = manager.getMemoryClass();//M int cacheSize = memorySize/ 8; //第二種方式(比較常用) int memorySize = (int) Runtime.getRuntime().maxMemory();//bytes int cacheSize = memorySize / 8;
然后,初始化 LruCache ,具體如下:
//初始化 LruCache 且設(shè)置了緩存大小 LruCachelruCache = new LruCache (cacheSize){ @Override protected int sizeOf(String key, Bitmap value) { //計(jì)算每一個(gè)緩存Bitmap的所占內(nèi)存的大小,內(nèi)存單位應(yīng)該和 cacheSize 的單位保持一致 return value.getByteCount(); } };
添加 Bitmap 對(duì)象到 LruCache 緩存中
//參數(shù)put(String key,Bitmap bitmap) lruCache.put(key,bitmap)
獲取緩存中的圖片并顯示
//參數(shù)get(String key) Bitmap bitmap = lruCache.get(key); imageView.setImageBitmap(bitmap);
下面使用 LruCache 加載一張網(wǎng)絡(luò)圖片來(lái)演示 LruCache 的簡(jiǎn)單使用。
加載網(wǎng)絡(luò)圖片創(chuàng)建一個(gè)簡(jiǎn)單的 ImageLoader,里面封裝獲取緩存 Bitmap 、添加 Bitmap 到緩存中以及從緩存中移出 Bitmap 的方法,具體如下:
//ImageLoader public class ImageLoader { private LruCachelruCache; public ImageLoader() { int memorySize = (int) Runtime.getRuntime().maxMemory() / 1024; int cacheSize = memorySize / 8; lruCache = new LruCache (cacheSize){ @Override protected int sizeOf(String key, Bitmap value) { //計(jì)算每一個(gè)緩存Bitmap的所占內(nèi)存的大小 return value.getByteCount()/1024; } }; } /** * 添加Bitmapd到LruCache中 * @param key * @param bitmap */ public void addBitmapToLruCache(String key, Bitmap bitmap){ if (getBitmapFromLruCache(key)==null){ lruCache.put(key,bitmap); } } /** * 獲取緩存的Bitmap * @param key */ public Bitmap getBitmapFromLruCache(String key){ if (key!=null){ return lruCache.get(key); } return null; } /** * 移出緩存 * @param key */ public void removeBitmapFromLruCache(String key){ if (key!=null){ lruCache.remove(key); } } }
然后創(chuàng)建一個(gè)線程類用于加載圖片,具體如下:
//加載圖片的線程 public class LoadImageThread extends Thread { private Activity mActivity; private String mImageUrl; private ImageLoader mImageLoader; private ImageView mImageView; public LoadImageThread(Activity activity,ImageLoader imageLoader, ImageView imageView,String imageUrl) { this.mActivity = activity; this.mImageLoader = imageLoader; this.mImageView = imageView; this.mImageUrl = imageUrl; } @Override public void run() { HttpURLConnection connection = null; InputStream is = null; try { URL url = new URL(mImageUrl); connection = (HttpURLConnection) url.openConnection(); is = connection.getInputStream(); if (connection.getResponseCode() == HttpURLConnection.HTTP_OK){ final Bitmap bitmap = BitmapFactory.decodeStream(is); mImageLoader.addBitmapToLruCache("bitmap",bitmap); mActivity.runOnUiThread(new Runnable() { @Override public void run() { mImageView.setImageBitmap(bitmap); } }); } } catch (IOException e) { e.printStackTrace(); } finally { if (connection!=null){ connection.disconnect(); } if (is!=null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
然后,在 MainActivity 中使用 ImageLoader 加載并緩存網(wǎng)絡(luò)圖片到內(nèi)存中, 先從內(nèi)存中獲取,如果緩存中沒(méi)有需要的 Bitmap ,則從網(wǎng)絡(luò)上獲取圖片并添加到緩存中,使用過(guò)程中一旦退出應(yīng)用,系統(tǒng)將會(huì)釋放內(nèi)存,關(guān)鍵方法如下:
//獲取圖片 private void loadImage(){ Bitmap bitmap = imageLoader.getBitmapFromLruCache("bitmap"); if (bitmap==null){ Log.i(TAG,"從網(wǎng)絡(luò)獲取圖片"); new LoadImageThread(this,imageLoader,imageView,url).start(); }else{ Log.i(TAG,"從緩存中獲取圖片"); imageView.setImageBitmap(bitmap); } } // 移出緩存 private void removeBitmapFromL(String key){ imageLoader.removeBitmapFromLruCache(key); }
然后在相應(yīng)的事件里調(diào)用上述獲取圖片、移出緩存的方法,具體如下:
@Override public void onClick(View v) { switch (v.getId()){ case R.id.btnLoadLruCache: loadImage(); break; case R.id.btnRemoveBitmapL: removeBitmapFromL("bitmap"); break; } }
下面來(lái)一張日志截圖說(shuō)明執(zhí)行情況:
磁盤緩存磁盤緩存就是指將緩存對(duì)象寫入文件系統(tǒng),使用磁盤緩存可有助于在內(nèi)存緩存不可用時(shí)縮短加載時(shí)間,從磁盤緩存中獲取圖片相較從緩存中獲取較慢,如果可以應(yīng)該在后臺(tái)線程中處理;磁盤緩存使用到一個(gè) DiskLruCache 類來(lái)實(shí)現(xiàn)磁盤緩存,DiskLruCache 收到了 Google 官方的推薦使用,DiskLruCache 不屬于 Android SDK 中的一部分,首先貼一個(gè) DiskLruCache 的源碼鏈接
DiskLruCache 源碼地址 。
DiskLruCache 的構(gòu)造方法是私有的,故不能用來(lái)創(chuàng)建 DiskLruCache,它提供一個(gè) open 方法用于創(chuàng)建自身,方法如下:
/** * 返回相應(yīng)目錄中的緩存,如果不存在則創(chuàng)建 * @param directory 緩存目錄 * @param appVersion 表示應(yīng)用的版本號(hào),一般設(shè)為1 * @param valueCount 每個(gè)Key所對(duì)應(yīng)的Value的數(shù)量,一般設(shè)為1 * @param maxSize 緩存大小 * @throws IOException if reading or writing the cache directory fails */ public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) throws IOException { ... // 創(chuàng)建DiskLruCache DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); if (cache.journalFile.exists()) { ... return cache; } //如果緩存目錄不存在,創(chuàng)建緩存目錄以及DiskLruCache directory.mkdirs(); cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); ... return cache; }
注意:緩存目錄可以選擇 SD 卡上的緩存目錄,及 /sdcard/Android/data/應(yīng)用包名/cache 目錄,也可以選擇當(dāng)前應(yīng)用程序 data 下的緩存目錄,當(dāng)然可以指定其他目錄,如果應(yīng)用卸載后希望刪除緩存文件,就選擇 SD 卡上的緩存目錄,如果希望保留數(shù)據(jù)請(qǐng)選擇其他目錄,還有一點(diǎn),如果是內(nèi)存緩存,退出應(yīng)用之后緩存將會(huì)被清除。
DiskLruCache 緩存的添加DiskLruCache 緩存的添加是通過(guò) Editor 完成的,Editor 表示一個(gè)緩存對(duì)象的編輯對(duì)象,可以通過(guò)其 edit(String key) 方法來(lái)獲取對(duì)應(yīng)的 Editor 對(duì)象,如果 Editor 正在使用 edit(String key) 方法將會(huì)返回 null,即 DiskLruCache 不允許同時(shí)操作同一個(gè)緩存對(duì)象。當(dāng)然緩存的添加都是通過(guò)唯一的 key 來(lái)進(jìn)行添加操作的,那么什么作為 key 比較方便嗎,以圖片為例,一般講 url 的 MD5 值作為 key ,計(jì)算方式如下:
//計(jì)算url的MD5值作為key private String hashKeyForDisk(String url) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(url.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(url.hashCode()); } return cacheKey; } private String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]); if (hex.length() == 1) { sb.append("0"); } sb.append(hex); } return sb.toString(); }
通過(guò) url 的 MD5 的值獲取到 key 之后,就可以通過(guò) DiskLruCache 對(duì)象的 edit(String key) 方法獲取 Editor 對(duì)象,然后通過(guò) Editor 對(duì)象的 commit 方法,大概意思就是釋放 Editir 對(duì)象,之后就可以通過(guò) key 進(jìn)行其他操作咯。
當(dāng)然,獲取到 key 之后就可以向 DiskLruCache 中添加要緩存的東西咯,要加載一個(gè)網(wǎng)絡(luò)圖片到緩存中,顯然就是的通過(guò)下載的方式將要緩存的東西寫入文件系統(tǒng)中,那么就需要一個(gè)輸出流往里面寫東西,主要有兩種處理方式:
創(chuàng)建 OutputStream 寫入要緩存的數(shù)據(jù),通過(guò) DiskLruCache 的 edit(String key) 方法獲得 Editor 對(duì)象,然后通過(guò) OutputStream 轉(zhuǎn)換為 Birmap,將該 Bitmap 寫入由 Editor 對(duì)象創(chuàng)建的 OutputStream 中,最后調(diào)用 Editor 對(duì)象的 commit 方法提交;
先獲得 Editor 對(duì)象,根據(jù) Editor 對(duì)象創(chuàng)建出 OutputStream 直接寫入要緩存的數(shù)據(jù),最后調(diào)用 Editor 對(duì)象的 commit 方法提交;
這里以第一種方式為例,將根據(jù) url 將網(wǎng)絡(luò)圖片添加到磁盤緩存中,同時(shí)也添加到內(nèi)存緩存中,具體如下:
//添加網(wǎng)絡(luò)圖片到內(nèi)存緩存和磁盤緩存 public void putCache(final String url, final CallBack callBack){ Log.i(TAG,"putCache..."); new AsyncTaskDiskLruCache 緩存的獲取(){ @Override protected Bitmap doInBackground(String... params) { String key = hashKeyForDisk(params[0]); DiskLruCache.Editor editor = null; Bitmap bitmap = null; try { URL url = new URL(params[0]); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(1000 * 30); conn.setConnectTimeout(1000 * 30); ByteArrayOutputStream baos = null; if(conn.getResponseCode()==HttpURLConnection.HTTP_OK){ BufferedInputStream bis = new BufferedInputStream(conn.getInputStream()); baos = new ByteArrayOutputStream(); byte[] bytes = new byte[1024]; int len = -1; while((len=bis.read(bytes))!=-1){ baos.write(bytes,0,len); } bis.close(); baos.close(); conn.disconnect(); } if (baos!=null){ bitmap = decodeSampledBitmapFromStream(baos.toByteArray(),300,200); addBitmapToCache(params[0],bitmap);//添加到內(nèi)存緩存 editor = diskLruCache.edit(key); //關(guān)鍵 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, editor.newOutputStream(0)); editor.commit();//提交 } } catch (IOException e) { try { editor.abort();//放棄寫入 } catch (IOException e1) { e1.printStackTrace(); } } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); callBack.response(bitmap); } }.execute(url); }
在 DiskLruCache 緩存的添加中了解了如何獲取 key,獲取到 key 之后,通過(guò) DiskLruCache 對(duì)象的 get 方法獲得 Snapshot 對(duì)象,然后根據(jù) Snapshot 對(duì)象獲得 InputStream,最后通過(guò) InputStream 就可以獲得 Bitmap ,當(dāng)然可以利用 上篇文章 中的對(duì) Bitmap 采樣的方式進(jìn)行適當(dāng)?shù)恼{(diào)整,也可以在緩存之前先壓縮再緩存,獲取 InputStream 的方法具體如下:
//獲取磁盤緩存 public InputStream getDiskCache(String url) { Log.i(TAG,"getDiskCache..."); String key = hashKeyForDisk(url); try { DiskLruCache.Snapshot snapshot = diskLruCache.get(key); if (snapshot!=null){ return snapshot.getInputStream(0); } } catch (IOException e) { e.printStackTrace(); } return null; }
DiskLruCache 的主要部分大致如上,下面實(shí)現(xiàn)一個(gè)簡(jiǎn)單的三級(jí)緩存來(lái)說(shuō)明 LruCache 和 DiskLruCache 的具體使用,MainActivity 代碼如下:
//MainActivity.java public class MainActivity extends AppCompatActivity { private static final String TAG = "cache_test"; public static String CACHE_DIR = "diskCache"; //緩存目錄 public static int CACHE_SIZE = 1024 * 1024 * 10; //緩存大小 private ImageView imageView; private LruCachelruCache; private LruCacheUtils cacheUtils; private String url = "http://img06.tooopen.com/images/20161012/tooopen_sy_181713275376.jpg"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView) findViewById(R.id.imageView); } @Override protected void onResume() { super.onResume(); cacheUtils = LruCacheUtils.getInstance(); //創(chuàng)建內(nèi)存緩存和磁盤緩存 cacheUtils.createCache(this,CACHE_DIR,CACHE_SIZE); } @Override protected void onPause() { super.onPause(); cacheUtils.flush(); } @Override protected void onStop() { super.onStop(); cacheUtils.close(); } public void loadImage(View view){ load(url,imageView); } public void removeLruCache(View view){ Log.i(TAG, "移出內(nèi)存緩存..."); cacheUtils.removeLruCache(url); } public void removeDiskLruCache(View view){ Log.i(TAG, "移出磁盤緩存..."); cacheUtils.removeDiskLruCache(url); } private void load(String url, final ImageView imageView){ //從內(nèi)存中獲取圖片 Bitmap bitmap = cacheUtils.getBitmapFromCache(url); if (bitmap == null){ //從磁盤中獲取圖片 InputStream is = cacheUtils.getDiskCache(url); if (is == null){ //從網(wǎng)絡(luò)上獲取圖片 cacheUtils.putCache(url, new LruCacheUtils.CallBack () { @Override public void response(Bitmap bitmap1) { Log.i(TAG, "從網(wǎng)絡(luò)中獲取圖片..."); Log.i(TAG, "正在從網(wǎng)絡(luò)中下載圖片..."); imageView.setImageBitmap(bitmap1); Log.i(TAG, "從網(wǎng)絡(luò)中獲取圖片成功..."); } }); }else{ Log.i(TAG, "從磁盤中獲取圖片..."); bitmap = BitmapFactory.decodeStream(is); imageView.setImageBitmap(bitmap); } }else{ Log.i(TAG, "從內(nèi)存中獲取圖片..."); imageView.setImageBitmap(bitmap); } } }
布局文件比較簡(jiǎn)單就不貼代碼了,下面是日志運(yùn)行截圖說(shuō)明執(zhí)行情況,如下圖所示:
這篇文章記錄了 LruCache 和 DiskLruCache 的基本使用方式,至少應(yīng)該對(duì)這兩個(gè)緩存輔助類有了一定的了解,它的具體實(shí)現(xiàn)請(qǐng)參考源碼。
【文中代碼】:傳送門
可以關(guān)注公眾號(hào):jzman-blog,一起交流學(xué)習(xí)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/62123.html
摘要:加載并顯示圖片或加載并執(zhí)行回調(diào)接口。加載圖片主要分為三類接口表示異步加載并顯示圖片到對(duì)應(yīng)的上。以上三類接口最終都會(huì)調(diào)用到這個(gè)函數(shù)進(jìn)行圖片加載。不允許訪問(wèn)網(wǎng)絡(luò)的圖片下載器。創(chuàng)建圖片下載器,返回一個(gè)。 1. 功能介紹 1.1 Android Universal Image Loader Android Universal Image Loader 是一個(gè)強(qiáng)大的、可高度定制的圖片緩存,本文簡(jiǎn)...
摘要:加載圖的機(jī)制是什么,為何不會(huì)內(nèi)存泄漏自定義可拖動(dòng)的顯示高清大圖的技術(shù)博客大總結(jié)提供一個(gè)設(shè)置圖片的入口,里面去獲得圖片的真實(shí)的寬度和高度,以及初始化我們的重寫,在里面根據(jù)用戶移動(dòng)的手勢(shì),去更新顯示區(qū)域的參數(shù)。 目錄介紹 7.0.0.1 加載bitmap圖片的時(shí)候需要注意什么?為何bitmap容易造成OOM?如何計(jì)算Bitmap占用內(nèi)存? 7.0.0.2 如何理解recycle釋放內(nèi)存問(wèn)...
閱讀 2951·2021-10-28 09:32
閱讀 2984·2021-10-11 10:57
閱讀 3131·2021-10-08 10:05
閱讀 2611·2021-09-28 09:36
閱讀 2223·2019-08-30 15:55
閱讀 2278·2019-08-30 15:44
閱讀 2404·2019-08-30 14:02
閱讀 3084·2019-08-29 17:16