編輯:關於Android編程
在上一篇文章當中,我們學習了DiskLruCache的概念和基本用法,但僅僅是掌握理論知識顯然是不夠的,那麼本篇文章我們就來繼續進階一下,看一看在實戰當中應該怎樣合理使用DiskLruCache。還不熟悉DiskLruCache用法的朋友可以先去參考我的上一篇文章 Android DiskLruCache完全解析,硬盤緩存的最佳方案 。
其實,在真正的項目實戰當中如果僅僅是使用硬盤緩存的話,程序是有明顯短板的。而如果只使用內存緩存的話,程序當然也會有很大的缺陷。因此,一個優秀的程序必然會將內存緩存和硬盤緩存結合到一起使用,那麼本篇文章我們就來看一看,如何才能將LruCache和DiskLruCache完美結合到一起。
在 Android照片牆應用實現,再多的圖片也不怕崩潰 這篇文章當中,我編寫了一個照片牆的應用程序,但當時只是單純使用到了內存緩存而已,而今天我們就對這個例子進行擴展,制作一個完整版的照片牆。
那我們開始動手吧,新建一個Android項目,起名叫PhotoWallDemo,這裡我使用的是Android 4.0的API。然後新建一個libcore.io包,並將DiskLruCache.java文件拷貝到這個包下,這樣就把准備工作完成了。
接下來首先需要考慮的仍然是圖片源的問題,簡單起見,我仍然是吧所有圖片都上傳到了我的CSDN相冊當中,然後新建一個Images類,將所有相冊中圖片的網址都配置進去,代碼如下所示:
public class Images { public final static String[] imageThumbUrls = new String[] { http://img.my.csdn.net/uploads/201407/26/1406383299_1976.jpg, http://img.my.csdn.net/uploads/201407/26/1406383291_6518.jpg, http://img.my.csdn.net/uploads/201407/26/1406383291_8239.jpg, http://img.my.csdn.net/uploads/201407/26/1406383290_9329.jpg, http://img.my.csdn.net/uploads/201407/26/1406383290_1042.jpg, http://img.my.csdn.net/uploads/201407/26/1406383275_3977.jpg, http://img.my.csdn.net/uploads/201407/26/1406383265_8550.jpg, http://img.my.csdn.net/uploads/201407/26/1406383264_3954.jpg, http://img.my.csdn.net/uploads/201407/26/1406383264_4787.jpg, http://img.my.csdn.net/uploads/201407/26/1406383264_8243.jpg, http://img.my.csdn.net/uploads/201407/26/1406383248_3693.jpg, http://img.my.csdn.net/uploads/201407/26/1406383243_5120.jpg, http://img.my.csdn.net/uploads/201407/26/1406383242_3127.jpg, http://img.my.csdn.net/uploads/201407/26/1406383242_9576.jpg, http://img.my.csdn.net/uploads/201407/26/1406383242_1721.jpg, http://img.my.csdn.net/uploads/201407/26/1406383219_5806.jpg, http://img.my.csdn.net/uploads/201407/26/1406383214_7794.jpg, http://img.my.csdn.net/uploads/201407/26/1406383213_4418.jpg, http://img.my.csdn.net/uploads/201407/26/1406383213_3557.jpg, http://img.my.csdn.net/uploads/201407/26/1406383210_8779.jpg, http://img.my.csdn.net/uploads/201407/26/1406383172_4577.jpg, http://img.my.csdn.net/uploads/201407/26/1406383166_3407.jpg, http://img.my.csdn.net/uploads/201407/26/1406383166_2224.jpg, http://img.my.csdn.net/uploads/201407/26/1406383166_7301.jpg, http://img.my.csdn.net/uploads/201407/26/1406383165_7197.jpg, http://img.my.csdn.net/uploads/201407/26/1406383150_8410.jpg, http://img.my.csdn.net/uploads/201407/26/1406383131_3736.jpg, http://img.my.csdn.net/uploads/201407/26/1406383130_5094.jpg, http://img.my.csdn.net/uploads/201407/26/1406383130_7393.jpg, http://img.my.csdn.net/uploads/201407/26/1406383129_8813.jpg, http://img.my.csdn.net/uploads/201407/26/1406383100_3554.jpg, http://img.my.csdn.net/uploads/201407/26/1406383093_7894.jpg, http://img.my.csdn.net/uploads/201407/26/1406383092_2432.jpg, http://img.my.csdn.net/uploads/201407/26/1406383092_3071.jpg, http://img.my.csdn.net/uploads/201407/26/1406383091_3119.jpg, http://img.my.csdn.net/uploads/201407/26/1406383059_6589.jpg, http://img.my.csdn.net/uploads/201407/26/1406383059_8814.jpg, http://img.my.csdn.net/uploads/201407/26/1406383059_2237.jpg, http://img.my.csdn.net/uploads/201407/26/1406383058_4330.jpg, http://img.my.csdn.net/uploads/201407/26/1406383038_3602.jpg, http://img.my.csdn.net/uploads/201407/26/1406382942_3079.jpg, http://img.my.csdn.net/uploads/201407/26/1406382942_8125.jpg, http://img.my.csdn.net/uploads/201407/26/1406382942_4881.jpg, http://img.my.csdn.net/uploads/201407/26/1406382941_4559.jpg, http://img.my.csdn.net/uploads/201407/26/1406382941_3845.jpg, http://img.my.csdn.net/uploads/201407/26/1406382924_8955.jpg, http://img.my.csdn.net/uploads/201407/26/1406382923_2141.jpg, http://img.my.csdn.net/uploads/201407/26/1406382923_8437.jpg, http://img.my.csdn.net/uploads/201407/26/1406382922_6166.jpg, http://img.my.csdn.net/uploads/201407/26/1406382922_4843.jpg, http://img.my.csdn.net/uploads/201407/26/1406382905_5804.jpg, http://img.my.csdn.net/uploads/201407/26/1406382904_3362.jpg, http://img.my.csdn.net/uploads/201407/26/1406382904_2312.jpg, http://img.my.csdn.net/uploads/201407/26/1406382904_4960.jpg, http://img.my.csdn.net/uploads/201407/26/1406382900_2418.jpg, http://img.my.csdn.net/uploads/201407/26/1406382881_4490.jpg, http://img.my.csdn.net/uploads/201407/26/1406382881_5935.jpg, http://img.my.csdn.net/uploads/201407/26/1406382880_3865.jpg, http://img.my.csdn.net/uploads/201407/26/1406382880_4662.jpg, http://img.my.csdn.net/uploads/201407/26/1406382879_2553.jpg, http://img.my.csdn.net/uploads/201407/26/1406382862_5375.jpg, http://img.my.csdn.net/uploads/201407/26/1406382862_1748.jpg, http://img.my.csdn.net/uploads/201407/26/1406382861_7618.jpg, http://img.my.csdn.net/uploads/201407/26/1406382861_8606.jpg, http://img.my.csdn.net/uploads/201407/26/1406382861_8949.jpg, http://img.my.csdn.net/uploads/201407/26/1406382841_9821.jpg, http://img.my.csdn.net/uploads/201407/26/1406382840_6603.jpg, http://img.my.csdn.net/uploads/201407/26/1406382840_2405.jpg, http://img.my.csdn.net/uploads/201407/26/1406382840_6354.jpg, http://img.my.csdn.net/uploads/201407/26/1406382839_5779.jpg, http://img.my.csdn.net/uploads/201407/26/1406382810_7578.jpg, http://img.my.csdn.net/uploads/201407/26/1406382810_2436.jpg, http://img.my.csdn.net/uploads/201407/26/1406382809_3883.jpg, http://img.my.csdn.net/uploads/201407/26/1406382809_6269.jpg, http://img.my.csdn.net/uploads/201407/26/1406382808_4179.jpg, http://img.my.csdn.net/uploads/201407/26/1406382790_8326.jpg, http://img.my.csdn.net/uploads/201407/26/1406382789_7174.jpg, http://img.my.csdn.net/uploads/201407/26/1406382789_5170.jpg, http://img.my.csdn.net/uploads/201407/26/1406382789_4118.jpg, http://img.my.csdn.net/uploads/201407/26/1406382788_9532.jpg, http://img.my.csdn.net/uploads/201407/26/1406382767_3184.jpg, http://img.my.csdn.net/uploads/201407/26/1406382767_4772.jpg, http://img.my.csdn.net/uploads/201407/26/1406382766_4924.jpg, http://img.my.csdn.net/uploads/201407/26/1406382766_5762.jpg, http://img.my.csdn.net/uploads/201407/26/1406382765_7341.jpg }; }設置好了圖片源之後,我們需要一個GridView來展示照片牆上的每一張圖片。打開或修改activity_main.xml中的代碼,如下所示:
很簡單,只是在LinearLayout中寫了一個GridView而已。接著我們要定義GridView中每一個子View的布局,新建一個photo_layout.xml布局,加入如下代碼:
仍然很簡單,photo_layout.xml布局中只有一個ImageView控件,就是用它來顯示圖片的。這樣我們就把所有的布局文件都寫好了。
接下來新建PhotoWallAdapter做為GridView的適配器,代碼如下所示:
public class PhotoWallAdapter extends ArrayAdapter{ /** * 記錄所有正在下載或等待下載的任務。 */ private Set taskCollection; /** * 圖片緩存技術的核心類,用於緩存所有下載好的圖片,在程序內存達到設定值時會將最少最近使用的圖片移除掉。 */ private LruCache 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 (); // 獲取應用程序最大可用內存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 8; // 設置圖片緩存大小為程序最大可用內存的1/8 mMemoryCache = new LruCache (cacheSize) { @Override protected 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(); } } @Override public 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 { /** * 圖片的URL地址 */ private String imageUrl; @Override protected Bitmap doInBackground(String... params) { imageUrl = params[0]; FileDescriptor fileDescriptor = null; FileInputStream fileInputStream = null; Snapshot snapShot = null; try { // 生成圖片URL對應的key final 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; } @Override protected 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完美結合到一起了。接下來還需要編寫MainActivity的代碼,非常簡單,如下所示:
public class MainActivity extends Activity { /** * 用於展示照片牆的GridView */ private GridView mPhotoWall; /** * GridView的適配器 */ private PhotoWallAdapter mAdapter; private int mImageThumbSize; private int mImageThumbSpacing; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImageThumbSize = getResources().getDimensionPixelSize( R.dimen.image_thumbnail_size); mImageThumbSpacing = getResources().getDimensionPixelSize( R.dimen.image_thumbnail_spacing); mPhotoWall = (GridView) findViewById(R.id.photo_wall); mAdapter = new PhotoWallAdapter(this, 0, Images.imageThumbUrls, mPhotoWall); mPhotoWall.setAdapter(mAdapter); mPhotoWall.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { final int numColumns = (int) Math.floor(mPhotoWall .getWidth() / (mImageThumbSize + mImageThumbSpacing)); if (numColumns > 0) { int columnWidth = (mPhotoWall.getWidth() / numColumns) - mImageThumbSpacing; mAdapter.setItemHeight(columnWidth); mPhotoWall.getViewTreeObserver() .removeGlobalOnLayoutListener(this); } } }); } @Override protected void onPause() { super.onPause(); mAdapter.fluchCache(); } @Override protected void onDestroy() { super.onDestroy(); // 退出程序時結束所有的下載任務 mAdapter.cancelAllTasks(); } }
上述代碼中,我們通過getViewTreeObserver()的方式監聽View的布局事件,當布局完成以後,我們重新修改一下GridView中子View的高度,以保證子View的寬度和高度可以保持一致。
到這裡還沒有結束,最後還需要配置一下AndroidManifest.xml文件,並加入相應的權限,如下所示:
好了,全部代碼都在這兒了,讓我們來運行一下吧,效果如下圖所示:
第一次從網絡上請求圖片的時候有點慢,但之後加載圖片就會非常快了,滑動起來也很流暢。
那麼我們最後再檢查一下這些圖片是不是已經正確緩存在指定地址了,進入 /sdcard/Android/data//cache/thumb 這個路徑,如下圖所示:
<主菜>RecyclerView簡介RecyclerView是Android 5.0提供的新控件,已經用了很長時間了,但是一直沒有時間去仔細的梳理一下。現在有
本文實例為大家分享了Android仿微信二維碼和條形碼的具體代碼,供大家參考,具體內容如下package your.QRCode.namespace;import jav
概述ListView:一個可以垂直滑動的列表視圖。setEmptyView()接口繼承至ListView的父類AdapterView。可想而知,ListView為空時,才
確定取消對話框(帶圖標) //(上下文,主題) new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE