Android缓存学习入门
本文主要包括以下內容
內存緩存的實現
public class PhotoWallAdapter extends ArrayAdapter<String> implements OnScrollListener {/*** 記錄所有正在下載或等待下載的任務。*/private Set<BitmapWorkerTask> taskCollection;/*** 圖片緩存技術的核心類,用于緩存所有下載好的圖片,在程序內存達到設定值時會將最少最近使用的圖片移除掉。*/private LruCache<String, Bitmap> mMemoryCache;/*** GridView的實例*/private GridView mPhotoWall;/*** 第一張可見圖片的下標*/private int mFirstVisibleItem;/*** 一屏有多少張圖片可見*/private int mVisibleItemCount;/*** 記錄是否剛打開程序,用于解決進入程序不滾動屏幕,不會下載圖片的問題。*/private boolean isFirstEnter = true;public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects,GridView photoWall) {super(context, textViewResourceId, objects);mPhotoWall = photoWall;taskCollection = new HashSet<BitmapWorkerTask>();// 獲取應用程序最大可用內存int maxMemory = (int) Runtime.getRuntime().maxMemory();int cacheSize = maxMemory / 8;// 設置圖片緩存大小為程序最大可用內存的1/8mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap bitmap) {return bitmap.getByteCount();}};mPhotoWall.setOnScrollListener(this);}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {final String url = getItem(position);View view;if (convertView == null) {view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null);} else {view = convertView;}final ImageView photo = (ImageView) view.findViewById(R.id.photo);// 給ImageView設置一個Tag,保證異步加載圖片時不會亂序photo.setTag(url);setImageView(url, photo);return view;}/*** 給ImageView設置圖片。首先從LruCache中取出圖片的緩存,設置到ImageView上。如果LruCache中沒有該圖片的緩存,* 就給ImageView設置一張默認圖片。* * @param imageUrl* 圖片的URL地址,用于作為LruCache的鍵。* @param imageView* 用于顯示圖片的控件。*/private void setImageView(String imageUrl, ImageView imageView) {Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);if (bitmap != null) {imageView.setImageBitmap(bitmap);} else {imageView.setImageResource(R.drawable.empty_photo);}}/*** 將一張圖片存儲到LruCache中。* * @param key* LruCache的鍵,這里傳入圖片的URL地址。* @param bitmap* LruCache的鍵,這里傳入從網絡上下載的Bitmap對象。*/public void addBitmapToMemoryCache(String key, Bitmap bitmap) {if (getBitmapFromMemoryCache(key) == null) {mMemoryCache.put(key, bitmap);}}/*** 從LruCache中獲取一張圖片,如果不存在就返回null。* * @param key* LruCache的鍵,這里傳入圖片的URL地址。* @return 對應傳入鍵的Bitmap對象,或者null。*/public Bitmap getBitmapFromMemoryCache(String key) {return mMemoryCache.get(key);}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {// 僅當GridView靜止時才去下載圖片,GridView滑動時取消所有正在下載的任務if (scrollState == SCROLL_STATE_IDLE) {loadBitmaps(mFirstVisibleItem, mVisibleItemCount);} else {cancelAllTasks();}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,int totalItemCount) {mFirstVisibleItem = firstVisibleItem;mVisibleItemCount = visibleItemCount;// 下載的任務應該由onScrollStateChanged里調用,但首次進入程序時onScrollStateChanged并不會調用,// 因此在這里為首次進入程序開啟下載任務。if (isFirstEnter && visibleItemCount > 0) {loadBitmaps(firstVisibleItem, visibleItemCount);isFirstEnter = false;}}/*** 加載Bitmap對象。此方法會在LruCache中檢查所有屏幕中可見的ImageView的Bitmap對象,* 如果發現任何一個ImageView的Bitmap對象不在緩存中,就會開啟異步線程去下載圖片。* * @param firstVisibleItem* 第一個可見的ImageView的下標* @param visibleItemCount* 屏幕中總共可見的元素數*/private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {try {for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {String imageUrl = Images.imageThumbUrls[i];Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);if (bitmap == null) {BitmapWorkerTask task = new BitmapWorkerTask();taskCollection.add(task);task.execute(imageUrl);} else {ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);if (imageView != null && bitmap != null) {imageView.setImageBitmap(bitmap);}}}} catch (Exception e) {e.printStackTrace();}}/*** 取消所有正在下載或等待下載的任務。*/public void cancelAllTasks() {if (taskCollection != null) {for (BitmapWorkerTask task : taskCollection) {task.cancel(false);}}}/*** 異步下載圖片的任務。* * @author guolin*/class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {/*** 圖片的URL地址*/private String imageUrl;@Overrideprotected Bitmap doInBackground(String... params) {imageUrl = params[0];// 在后臺開始下載圖片Bitmap bitmap = downloadBitmap(params[0]);if (bitmap != null) {// 圖片下載完成后緩存到LrcCache中addBitmapToMemoryCache(params[0], bitmap);}return bitmap;}@Overrideprotected void onPostExecute(Bitmap bitmap) {super.onPostExecute(bitmap);// 根據Tag找到相應的ImageView控件,將下載好的圖片顯示出來。ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);if (imageView != null && bitmap != null) {imageView.setImageBitmap(bitmap);}taskCollection.remove(this);}/*** 建立HTTP請求,并獲取Bitmap對象。* * @param imageUrl* 圖片的URL地址* @return 解析后的Bitmap對象*/private Bitmap downloadBitmap(String imageUrl) {Bitmap bitmap = null;HttpURLConnection con = null;try {URL url = new URL(imageUrl);con = (HttpURLConnection) url.openConnection();con.setConnectTimeout(5 * 1000);con.setReadTimeout(10 * 1000);bitmap = BitmapFactory.decodeStream(con.getInputStream());} catch (Exception e) {e.printStackTrace();} finally {if (con != null) {con.disconnect();}}return bitmap;}}}PhotoWallAdapter是整個照片墻程序中最關鍵的一個類了,這里我來重點給大家講解一下。首先在PhotoWallAdapter的構造函數中,我們初始化了LruCache類,并設置了最大緩存容量為程序最大可用內存的1/8,接下來又為GridView注冊了一個滾動監聽器。然后在getView()方法中,我們為每個ImageView設置了一個唯一的Tag,這個Tag的作用是為了后面能夠準確地找回這個ImageView,不然異步加載圖片會出現亂序的情況。之后調用了setImageView()方法為ImageView設置一張圖片,這個方法首先會從LruCache緩存中查找是否已經緩存了這張圖片,如果成功找到則將緩存中的圖片顯示在ImageView上,否則就顯示一張默認的空圖片。
看了半天,那到底是在哪里下載圖片的呢?這是在GridView的滾動監聽器中進行的,在onScrollStateChanged()方法中,我們對GridView的滾動狀態進行了判斷,如果當前GridView是靜止的,則調用loadBitmaps()方法去下載圖片,如果GridView正在滾動,則取消掉所有下載任務,這樣可以保證GridView滾動的流暢性。在loadBitmaps()方法中,我們為屏幕上所有可見的GridView子元素開啟了一個線程去執行下載任務,下載成功后將圖片存儲到LruCache當中,然后通過Tag找到相應的ImageView控件,把下載好的圖片顯示出來。
由于我們使用了LruCache來緩存圖片,所以不需要擔心內存溢出的情況,當LruCache中存儲圖片的總大小達到容量上限的時候,會自動把最近最少使用的圖片從緩存中移除。
硬盤緩存實現
主要包括以下幾步
1、 下載DiskLruCache的源碼。下載好了源碼之后,只需要在項目中新建一個libcore.io包,然后將DiskLruCache.java文件復制到這個包中即可。
2、 打開緩存
DiskLruCache mDiskLruCache = null; try {File cacheDir = getDiskCacheDir(context, "bitmap");if (!cacheDir.exists()) {cacheDir.mkdirs();}mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024); } catch (IOException e) {e.printStackTrace(); }3、寫入緩存
new Thread(new Runnable() {@Overridepublic void run() {try {String imageUrl = "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg";String key = hashKeyForDisk(imageUrl);DiskLruCache.Editor editor = mDiskLruCache.edit(key);if (editor != null) {OutputStream outputStream = editor.newOutputStream(0);if (downloadUrlToStream(imageUrl, outputStream)) {editor.commit();} else {editor.abort();}}mDiskLruCache.flush();} catch (IOException e) {e.printStackTrace();}} }).start();private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {HttpURLConnection urlConnection = null;BufferedOutputStream out = null;BufferedInputStream in = null;try {final URL url = new URL(urlString);urlConnection = (HttpURLConnection) url.openConnection();in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);out = new BufferedOutputStream(outputStream, 8 * 1024);int b;while ((b = in.read()) != -1) {out.write(b);}return true;} catch (final IOException e) {e.printStackTrace();} finally {if (urlConnection != null) {urlConnection.disconnect();}try {if (out != null) {out.close();}if (in != null) {in.close();}} catch (final IOException e) {e.printStackTrace();}}return false; }4、讀取緩存
try {String imageUrl = "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg";String key = hashKeyForDisk(imageUrl);DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);if (snapShot != null) {InputStream is = snapShot.getInputStream(0);Bitmap bitmap = BitmapFactory.decodeStream(is);mImage.setImageBitmap(bitmap);} } catch (IOException e) {e.printStackTrace(); }LruCache與DiskLruCache結合實例
public class PhotoWallAdapter extends ArrayAdapter<String> {/*** 記錄所有正在下載或等待下載的任務。*/private Set<BitmapWorkerTask> taskCollection;/*** 圖片緩存技術的核心類,用于緩存所有下載好的圖片,在程序內存達到設定值時會將最少最近使用的圖片移除掉。*/private LruCache<String, Bitmap> mMemoryCache;/*** 圖片硬盤緩存核心類。*/private DiskLruCache mDiskLruCache;/*** GridView的實例*/private GridView mPhotoWall;/*** 記錄每個子項的高度。*/private int mItemHeight = 0;public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects,GridView photoWall) {super(context, textViewResourceId, objects);mPhotoWall = photoWall;taskCollection = new HashSet<BitmapWorkerTask>();// 獲取應用程序最大可用內存int maxMemory = (int) Runtime.getRuntime().maxMemory();int cacheSize = maxMemory / 8;// 設置圖片緩存大小為程序最大可用內存的1/8mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap bitmap) {return bitmap.getByteCount();}};try {// 獲取圖片緩存路徑File cacheDir = getDiskCacheDir(context, "thumb");if (!cacheDir.exists()) {cacheDir.mkdirs();}// 創建DiskLruCache實例,初始化緩存數據mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);} catch (IOException e) {e.printStackTrace();}}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {final String url = getItem(position);View view;if (convertView == null) {view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null);} else {view = convertView;}final ImageView imageView = (ImageView) view.findViewById(R.id.photo);if (imageView.getLayoutParams().height != mItemHeight) {imageView.getLayoutParams().height = mItemHeight;}// 給ImageView設置一個Tag,保證異步加載圖片時不會亂序imageView.setTag(url);imageView.setImageResource(R.drawable.empty_photo);loadBitmaps(imageView, url);return view;}/*** 將一張圖片存儲到LruCache中。* * @param key* LruCache的鍵,這里傳入圖片的URL地址。* @param bitmap* LruCache的鍵,這里傳入從網絡上下載的Bitmap對象。*/public void addBitmapToMemoryCache(String key, Bitmap bitmap) {if (getBitmapFromMemoryCache(key) == null) {mMemoryCache.put(key, bitmap);}}/*** 從LruCache中獲取一張圖片,如果不存在就返回null。* * @param key* LruCache的鍵,這里傳入圖片的URL地址。* @return 對應傳入鍵的Bitmap對象,或者null。*/public Bitmap getBitmapFromMemoryCache(String key) {return mMemoryCache.get(key);}/*** 加載Bitmap對象。此方法會在LruCache中檢查所有屏幕中可見的ImageView的Bitmap對象,* 如果發現任何一個ImageView的Bitmap對象不在緩存中,就會開啟異步線程去下載圖片。*/public void loadBitmaps(ImageView imageView, String imageUrl) {try {Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);if (bitmap == null) {BitmapWorkerTask task = new BitmapWorkerTask();taskCollection.add(task);task.execute(imageUrl);} else {if (imageView != null && bitmap != null) {imageView.setImageBitmap(bitmap);}}} catch (Exception e) {e.printStackTrace();}}/*** 取消所有正在下載或等待下載的任務。*/public void cancelAllTasks() {if (taskCollection != null) {for (BitmapWorkerTask task : taskCollection) {task.cancel(false);}}}/*** 根據傳入的uniqueName獲取硬盤緩存的路徑地址。*/public File getDiskCacheDir(Context context, String uniqueName) {String cachePath;if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())|| !Environment.isExternalStorageRemovable()) {cachePath = context.getExternalCacheDir().getPath();} else {cachePath = context.getCacheDir().getPath();}return new File(cachePath + File.separator + uniqueName);}/*** 獲取當前應用程序的版本號。*/public int getAppVersion(Context context) {try {PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),0);return info.versionCode;} catch (NameNotFoundException e) {e.printStackTrace();}return 1;}/*** 設置item子項的高度。*/public void setItemHeight(int height) {if (height == mItemHeight) {return;}mItemHeight = height;notifyDataSetChanged();}/*** 使用MD5算法對傳入的key進行加密并返回。*/public String hashKeyForDisk(String key) {String cacheKey;try {final MessageDigest mDigest = MessageDigest.getInstance("MD5");mDigest.update(key.getBytes());cacheKey = bytesToHexString(mDigest.digest());} catch (NoSuchAlgorithmException e) {cacheKey = String.valueOf(key.hashCode());}return cacheKey;}/*** 將緩存記錄同步到journal文件中。*/public void fluchCache() {if (mDiskLruCache != null) {try {mDiskLruCache.flush();} catch (IOException e) {e.printStackTrace();}}}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();}/*** 異步下載圖片的任務。* * @author guolin*/class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {/*** 圖片的URL地址*/private String imageUrl;@Overrideprotected Bitmap doInBackground(String... params) {imageUrl = params[0];FileDescriptor fileDescriptor = null;FileInputStream fileInputStream = null;Snapshot snapShot = null;try {// 生成圖片URL對應的keyfinal String key = hashKeyForDisk(imageUrl);// 查找key對應的緩存snapShot = mDiskLruCache.get(key);if (snapShot == null) {// 如果沒有找到對應的緩存,則準備從網絡上請求數據,并寫入緩存DiskLruCache.Editor editor = mDiskLruCache.edit(key);if (editor != null) {OutputStream outputStream = editor.newOutputStream(0);if (downloadUrlToStream(imageUrl, outputStream)) {editor.commit();} else {editor.abort();}}// 緩存被寫入后,再次查找key對應的緩存snapShot = mDiskLruCache.get(key);}if (snapShot != null) {fileInputStream = (FileInputStream) snapShot.getInputStream(0);fileDescriptor = fileInputStream.getFD();}// 將緩存數據解析成Bitmap對象Bitmap bitmap = null;if (fileDescriptor != null) {bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);}if (bitmap != null) {// 將Bitmap對象添加到內存緩存當中addBitmapToMemoryCache(params[0], bitmap);}return bitmap;} catch (IOException e) {e.printStackTrace();} finally {if (fileDescriptor == null && fileInputStream != null) {try {fileInputStream.close();} catch (IOException e) {}}}return null;}@Overrideprotected void onPostExecute(Bitmap bitmap) {super.onPostExecute(bitmap);// 根據Tag找到相應的ImageView控件,將下載好的圖片顯示出來。ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);if (imageView != null && bitmap != null) {imageView.setImageBitmap(bitmap);}taskCollection.remove(this);}/*** 建立HTTP請求,并獲取Bitmap對象。* * @param imageUrl* 圖片的URL地址* @return 解析后的Bitmap對象*/private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {HttpURLConnection urlConnection = null;BufferedOutputStream out = null;BufferedInputStream in = null;try {final URL url = new URL(urlString);urlConnection = (HttpURLConnection) url.openConnection();in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);out = new BufferedOutputStream(outputStream, 8 * 1024);int b;while ((b = in.read()) != -1) {out.write(b);}return true;} catch (final IOException e) {e.printStackTrace();} finally {if (urlConnection != null) {urlConnection.disconnect();}try {if (out != null) {out.close();}if (in != null) {in.close();}} catch (final IOException e) {e.printStackTrace();}}return false;}}}代碼有點長,我們一點點進行分析。首先在PhotoWallAdapter的構造函數中,我們初始化了LruCache類,并設置了內存緩存容量為程序最大可用內存的1/8,緊接著調用了DiskLruCache的open()方法來創建實例,并設置了硬盤緩存容量為10M,這樣我們就把LruCache和DiskLruCache的初始化工作完成了。
接著在getView()方法中,我們為每個ImageView設置了一個唯一的Tag,這個Tag的作用是為了后面能夠準確地找回這個ImageView,不然異步加載圖片會出現亂序的情況。然后在getView()方法的最后調用了loadBitmaps()方法,加載圖片的具體邏輯也就是在這里執行的了。
進入到loadBitmaps()方法中可以看到,實現是調用了getBitmapFromMemoryCache()方法來從內存中獲取緩存,如果獲取到了則直接調用ImageView的setImageBitmap()方法將圖片顯示到界面上。如果內存中沒有獲取到,則開啟一個BitmapWorkerTask任務來去異步加載圖片。
那么在BitmapWorkerTask的doInBackground()方法中,我們就靈活運用了上篇文章中學習的DiskLruCache的各種用法。首先根據圖片的URL生成對應的MD5 key,然后調用DiskLruCache的get()方法來獲取硬盤緩存,如果沒有獲取到的話則從網絡上請求圖片并寫入硬盤緩存,接著將Bitmap對象解析出來并添加到內存緩存當中,最后將這個Bitmap對象顯示到界面上,這樣一個完整的流程就執行完了。
那么我們再來分析一下上述流程,每次加載圖片的時候都優先去內存緩存當中讀取,當讀取不到的時候則回去硬盤緩存中讀取,而如果硬盤緩存仍然讀取不到的話,就從網絡上請求原始數據。不管是從硬盤緩存還是從網絡獲取,讀取到了數據之后都應該添加到內存緩存當中,這樣的話我們下次再去讀取圖片的時候就能迅速從內存當中讀取到,而如果該圖片從內存中被移除了的話,那就重復再執行一遍上述流程就可以了。
這樣我們就把LruCache和DiskLruCache完美結合到一起了。
效果如下
瀑布流效果
源碼下載
內存緩存
內存與硬盤結合
瀑布流
參考鏈接
Android照片墻應用實現,再多的圖片也不怕崩潰 - 郭霖的專欄 - 博客頻道 - CSDN.NET
Android DiskLruCache完全解析,硬盤緩存的最佳方案 - 郭霖的專欄 - 博客頻道 - CSDN.NET
Android照片墻完整版,完美結合LruCache和DiskLruCache - 郭霖的專欄 - 博客頻道 - CSDN.NET
Android瀑布流照片墻實現,體驗不規則排列的美感 - 郭霖的專欄 - 博客頻道 - CSDN.NET
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Android缓存学习入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实现TFrecords文件的保存与读取
- 下一篇: 三层神经网络实现手写数字的识别(基于te