Android照片牆完整版,完美結合LruCache和DiskLruCache

轉載:http://blog.csdn.net/guolin_blog/article/details/34093441

在上一篇文章當中,我們學習了DiskLruCache的概念和基本用法,但僅僅是掌握理論知識顯然是不夠的,那麼本篇文章我們就來繼續進階一下,看一看在實戰當中應該怎樣合理使用DiskLruCache。還不熟悉DiskLruCache用法的朋友可以先去參考我的上一篇文章 Android DiskLruCache完全解析,硬盤緩存的最佳方案 。

其實,在真正的項目實戰當中如果僅僅是使用硬盤緩存的話,程序是有明顯短板的。而如果只使用內存緩存的話,程序當然也會有很大的缺陷。因此,一個優秀的程序必然會將內存緩存和硬盤緩存結合到一起使用,那麼本篇文章我們就來看一看,如何才能將LruCache和DiskLruCache完美結合到一起。

在 Android照片牆應用實現,再多的圖片也不怕崩潰 這篇文章當中,我編寫了一個照片牆的應用程序,但當時只是單純使用到了內存緩存而已,而今天我們就對這個例子進行擴展,製作一個完整版的照片牆。

那我們開始動手吧,新建一個Android項目,起名叫PhotoWallDemo,這裏我使用的是Android 4.0的API。然後新建一個libcore.io包,並將DiskLruCache.java文件拷貝到這個包下,這樣就把準備工作完成了。

接下來首先需要考慮的仍然是圖片源的問題,簡單起見,我仍然是吧所有圖片都上傳到了我的CSDN相冊當中,然後新建一個Images類,將所有相冊中圖片的網址都配置進去,代碼如下所示:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class Images {  
  2.   
  3.     public final static String[] imageThumbUrls = new String[] {  
  4.         "https://img-my.csdn.net/uploads/201407/26/1406383299_1976.jpg",  
  5.         "https://img-my.csdn.net/uploads/201407/26/1406383291_6518.jpg",  
  6.         "https://img-my.csdn.net/uploads/201407/26/1406383291_8239.jpg",  
  7.         "https://img-my.csdn.net/uploads/201407/26/1406383290_9329.jpg",  
  8.         "https://img-my.csdn.net/uploads/201407/26/1406383290_1042.jpg",  
  9.         "https://img-my.csdn.net/uploads/201407/26/1406383275_3977.jpg",  
  10.         "https://img-my.csdn.net/uploads/201407/26/1406383265_8550.jpg",  
  11.         "https://img-my.csdn.net/uploads/201407/26/1406383264_3954.jpg",  
  12.         "https://img-my.csdn.net/uploads/201407/26/1406383264_4787.jpg",  
  13.         "https://img-my.csdn.net/uploads/201407/26/1406383264_8243.jpg",  
  14.         "https://img-my.csdn.net/uploads/201407/26/1406383248_3693.jpg",  
  15.         "https://img-my.csdn.net/uploads/201407/26/1406383243_5120.jpg",  
  16.         "https://img-my.csdn.net/uploads/201407/26/1406383242_3127.jpg",  
  17.         "https://img-my.csdn.net/uploads/201407/26/1406383242_9576.jpg",  
  18.         "https://img-my.csdn.net/uploads/201407/26/1406383242_1721.jpg",  
  19.         "https://img-my.csdn.net/uploads/201407/26/1406383219_5806.jpg",  
  20.         "https://img-my.csdn.net/uploads/201407/26/1406383214_7794.jpg",  
  21.         "https://img-my.csdn.net/uploads/201407/26/1406383213_4418.jpg",  
  22.         "https://img-my.csdn.net/uploads/201407/26/1406383213_3557.jpg",  
  23.         "https://img-my.csdn.net/uploads/201407/26/1406383210_8779.jpg",  
  24.         "https://img-my.csdn.net/uploads/201407/26/1406383172_4577.jpg",  
  25.         "https://img-my.csdn.net/uploads/201407/26/1406383166_3407.jpg",  
  26.         "https://img-my.csdn.net/uploads/201407/26/1406383166_2224.jpg",  
  27.         "https://img-my.csdn.net/uploads/201407/26/1406383166_7301.jpg",  
  28.         "https://img-my.csdn.net/uploads/201407/26/1406383165_7197.jpg",  
  29.         "https://img-my.csdn.net/uploads/201407/26/1406383150_8410.jpg",  
  30.         "https://img-my.csdn.net/uploads/201407/26/1406383131_3736.jpg",  
  31.         "https://img-my.csdn.net/uploads/201407/26/1406383130_5094.jpg",  
  32.         "https://img-my.csdn.net/uploads/201407/26/1406383130_7393.jpg",  
  33.         "https://img-my.csdn.net/uploads/201407/26/1406383129_8813.jpg",  
  34.         "https://img-my.csdn.net/uploads/201407/26/1406383100_3554.jpg",  
  35.         "https://img-my.csdn.net/uploads/201407/26/1406383093_7894.jpg",  
  36.         "https://img-my.csdn.net/uploads/201407/26/1406383092_2432.jpg",  
  37.         "https://img-my.csdn.net/uploads/201407/26/1406383092_3071.jpg",  
  38.         "https://img-my.csdn.net/uploads/201407/26/1406383091_3119.jpg",  
  39.         "https://img-my.csdn.net/uploads/201407/26/1406383059_6589.jpg",  
  40.         "https://img-my.csdn.net/uploads/201407/26/1406383059_8814.jpg",  
  41.         "https://img-my.csdn.net/uploads/201407/26/1406383059_2237.jpg",  
  42.         "https://img-my.csdn.net/uploads/201407/26/1406383058_4330.jpg",  
  43.         "https://img-my.csdn.net/uploads/201407/26/1406383038_3602.jpg",  
  44.         "https://img-my.csdn.net/uploads/201407/26/1406382942_3079.jpg",  
  45.         "https://img-my.csdn.net/uploads/201407/26/1406382942_8125.jpg",  
  46.         "https://img-my.csdn.net/uploads/201407/26/1406382942_4881.jpg",  
  47.         "https://img-my.csdn.net/uploads/201407/26/1406382941_4559.jpg",  
  48.         "https://img-my.csdn.net/uploads/201407/26/1406382941_3845.jpg",  
  49.         "https://img-my.csdn.net/uploads/201407/26/1406382924_8955.jpg",  
  50.         "https://img-my.csdn.net/uploads/201407/26/1406382923_2141.jpg",  
  51.         "https://img-my.csdn.net/uploads/201407/26/1406382923_8437.jpg",  
  52.         "https://img-my.csdn.net/uploads/201407/26/1406382922_6166.jpg",  
  53.         "https://img-my.csdn.net/uploads/201407/26/1406382922_4843.jpg",  
  54.         "https://img-my.csdn.net/uploads/201407/26/1406382905_5804.jpg",  
  55.         "https://img-my.csdn.net/uploads/201407/26/1406382904_3362.jpg",  
  56.         "https://img-my.csdn.net/uploads/201407/26/1406382904_2312.jpg",  
  57.         "https://img-my.csdn.net/uploads/201407/26/1406382904_4960.jpg",  
  58.         "https://img-my.csdn.net/uploads/201407/26/1406382900_2418.jpg",  
  59.         "https://img-my.csdn.net/uploads/201407/26/1406382881_4490.jpg",  
  60.         "https://img-my.csdn.net/uploads/201407/26/1406382881_5935.jpg",  
  61.         "https://img-my.csdn.net/uploads/201407/26/1406382880_3865.jpg",  
  62.         "https://img-my.csdn.net/uploads/201407/26/1406382880_4662.jpg",  
  63.         "https://img-my.csdn.net/uploads/201407/26/1406382879_2553.jpg",  
  64.         "https://img-my.csdn.net/uploads/201407/26/1406382862_5375.jpg",  
  65.         "https://img-my.csdn.net/uploads/201407/26/1406382862_1748.jpg",  
  66.         "https://img-my.csdn.net/uploads/201407/26/1406382861_7618.jpg",  
  67.         "https://img-my.csdn.net/uploads/201407/26/1406382861_8606.jpg",  
  68.         "https://img-my.csdn.net/uploads/201407/26/1406382861_8949.jpg",  
  69.         "https://img-my.csdn.net/uploads/201407/26/1406382841_9821.jpg",  
  70.         "https://img-my.csdn.net/uploads/201407/26/1406382840_6603.jpg",  
  71.         "https://img-my.csdn.net/uploads/201407/26/1406382840_2405.jpg",  
  72.         "https://img-my.csdn.net/uploads/201407/26/1406382840_6354.jpg",  
  73.         "https://img-my.csdn.net/uploads/201407/26/1406382839_5779.jpg",  
  74.         "https://img-my.csdn.net/uploads/201407/26/1406382810_7578.jpg",  
  75.         "https://img-my.csdn.net/uploads/201407/26/1406382810_2436.jpg",  
  76.         "https://img-my.csdn.net/uploads/201407/26/1406382809_3883.jpg",  
  77.         "https://img-my.csdn.net/uploads/201407/26/1406382809_6269.jpg",  
  78.         "https://img-my.csdn.net/uploads/201407/26/1406382808_4179.jpg",  
  79.         "https://img-my.csdn.net/uploads/201407/26/1406382790_8326.jpg",  
  80.         "https://img-my.csdn.net/uploads/201407/26/1406382789_7174.jpg",  
  81.         "https://img-my.csdn.net/uploads/201407/26/1406382789_5170.jpg",  
  82.         "https://img-my.csdn.net/uploads/201407/26/1406382789_4118.jpg",  
  83.         "https://img-my.csdn.net/uploads/201407/26/1406382788_9532.jpg",  
  84.         "https://img-my.csdn.net/uploads/201407/26/1406382767_3184.jpg",  
  85.         "https://img-my.csdn.net/uploads/201407/26/1406382767_4772.jpg",  
  86.         "https://img-my.csdn.net/uploads/201407/26/1406382766_4924.jpg",  
  87.         "https://img-my.csdn.net/uploads/201407/26/1406382766_5762.jpg",  
  88.         "https://img-my.csdn.net/uploads/201407/26/1406382765_7341.jpg"  
  89.     };  
  90. }  
設置好了圖片源之後,我們需要一個GridView來展示照片牆上的每一張圖片。打開或修改activity_main.xml中的代碼,如下所示:
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent" >  
  5.   
  6.     <GridView  
  7.         android:id="@+id/photo_wall"  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="match_parent"  
  10.         android:columnWidth="@dimen/image_thumbnail_size"  
  11.         android:gravity="center"  
  12.         android:horizontalSpacing="@dimen/image_thumbnail_spacing"  
  13.         android:numColumns="auto_fit"  
  14.         android:stretchMode="columnWidth"  
  15.         android:verticalSpacing="@dimen/image_thumbnail_spacing" >  
  16.     </GridView>  
  17.   
  18. </LinearLayout>  
很簡單,只是在LinearLayout中寫了一個GridView而已。接着我們要定義GridView中每一個子View的佈局,新建一個photo_layout.xml佈局,加入如下代碼:
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="wrap_content"  
  4.     android:layout_height="wrap_content" >  
  5.   
  6.     <ImageView   
  7.         android:id="@+id/photo"  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="match_parent"  
  10.         android:layout_centerInParent="true"  
  11.         android:scaleType="fitXY"  
  12.         />  
  13.   
  14. </RelativeLayout>  
仍然很簡單,photo_layout.xml佈局中只有一個ImageView控件,就是用它來顯示圖片的。這樣我們就把所有的佈局文件都寫好了。

接下來新建PhotoWallAdapter做爲GridView的適配器,代碼如下所示:

[java] view plaincopy
  1. public class PhotoWallAdapter extends ArrayAdapter<String> {  
  2.   
  3.     /** 
  4.      * 記錄所有正在下載或等待下載的任務。 
  5.      */  
  6.     private Set<BitmapWorkerTask> taskCollection;  
  7.   
  8.     /** 
  9.      * 圖片緩存技術的核心類,用於緩存所有下載好的圖片,在程序內存達到設定值時會將最少最近使用的圖片移除掉。 
  10.      */  
  11.     private LruCache<String, Bitmap> mMemoryCache;  
  12.   
  13.     /** 
  14.      * 圖片硬盤緩存核心類。 
  15.      */  
  16.     private DiskLruCache mDiskLruCache;  
  17.   
  18.     /** 
  19.      * GridView的實例 
  20.      */  
  21.     private GridView mPhotoWall;  
  22.   
  23.     /** 
  24.      * 記錄每個子項的高度。 
  25.      */  
  26.     private int mItemHeight = 0;  
  27.   
  28.     public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects,  
  29.             GridView photoWall) {  
  30.         super(context, textViewResourceId, objects);  
  31.         mPhotoWall = photoWall;  
  32.         taskCollection = new HashSet<BitmapWorkerTask>();  
  33.         // 獲取應用程序最大可用內存  
  34.         int maxMemory = (int) Runtime.getRuntime().maxMemory();  
  35.         int cacheSize = maxMemory / 8;  
  36.         // 設置圖片緩存大小爲程序最大可用內存的1/8  
  37.         mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
  38.             @Override  
  39.             protected int sizeOf(String key, Bitmap bitmap) {  
  40.                 return bitmap.getByteCount();  
  41.             }  
  42.         };  
  43.         try {  
  44.             // 獲取圖片緩存路徑  
  45.             File cacheDir = getDiskCacheDir(context, "thumb");  
  46.             if (!cacheDir.exists()) {  
  47.                 cacheDir.mkdirs();  
  48.             }  
  49.             // 創建DiskLruCache實例,初始化緩存數據  
  50.             mDiskLruCache = DiskLruCache  
  51.                     .open(cacheDir, getAppVersion(context), 110 * 1024 * 1024);  
  52.         } catch (IOException e) {  
  53.             e.printStackTrace();  
  54.         }  
  55.     }  
  56.   
  57.     @Override  
  58.     public View getView(int position, View convertView, ViewGroup parent) {  
  59.         final String url = getItem(position);  
  60.         View view;  
  61.         if (convertView == null) {  
  62.             view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null);  
  63.         } else {  
  64.             view = convertView;  
  65.         }  
  66.         final ImageView imageView = (ImageView) view.findViewById(R.id.photo);  
  67.         if (imageView.getLayoutParams().height != mItemHeight) {  
  68.             imageView.getLayoutParams().height = mItemHeight;  
  69.         }  
  70.         // 給ImageView設置一個Tag,保證異步加載圖片時不會亂序  
  71.         imageView.setTag(url);  
  72.         imageView.setImageResource(R.drawable.empty_photo);  
  73.         loadBitmaps(imageView, url);  
  74.         return view;  
  75.     }  
  76.   
  77.     /** 
  78.      * 將一張圖片存儲到LruCache中。 
  79.      *  
  80.      * @param key 
  81.      *            LruCache的鍵,這裏傳入圖片的URL地址。 
  82.      * @param bitmap 
  83.      *            LruCache的鍵,這裏傳入從網絡上下載的Bitmap對象。 
  84.      */  
  85.     public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
  86.         if (getBitmapFromMemoryCache(key) == null) {  
  87.             mMemoryCache.put(key, bitmap);  
  88.         }  
  89.     }  
  90.   
  91.     /** 
  92.      * 從LruCache中獲取一張圖片,如果不存在就返回null。 
  93.      *  
  94.      * @param key 
  95.      *            LruCache的鍵,這裏傳入圖片的URL地址。 
  96.      * @return 對應傳入鍵的Bitmap對象,或者null。 
  97.      */  
  98.     public Bitmap getBitmapFromMemoryCache(String key) {  
  99.         return mMemoryCache.get(key);  
  100.     }  
  101.   
  102.     /** 
  103.      * 加載Bitmap對象。此方法會在LruCache中檢查所有屏幕中可見的ImageView的Bitmap對象, 
  104.      * 如果發現任何一個ImageView的Bitmap對象不在緩存中,就會開啓異步線程去下載圖片。 
  105.      */  
  106.     public void loadBitmaps(ImageView imageView, String imageUrl) {  
  107.         try {  
  108.             Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);  
  109.             if (bitmap == null) {  
  110.                 BitmapWorkerTask task = new BitmapWorkerTask();  
  111.                 taskCollection.add(task);  
  112.                 task.execute(imageUrl);  
  113.             } else {  
  114.                 if (imageView != null && bitmap != null) {  
  115.                     imageView.setImageBitmap(bitmap);  
  116.                 }  
  117.             }  
  118.         } catch (Exception e) {  
  119.             e.printStackTrace();  
  120.         }  
  121.     }  
  122.   
  123.     /** 
  124.      * 取消所有正在下載或等待下載的任務。 
  125.      */  
  126.     public void cancelAllTasks() {  
  127.         if (taskCollection != null) {  
  128.             for (BitmapWorkerTask task : taskCollection) {  
  129.                 task.cancel(false);  
  130.             }  
  131.         }  
  132.     }  
  133.   
  134.     /** 
  135.      * 根據傳入的uniqueName獲取硬盤緩存的路徑地址。 
  136.      */  
  137.     public File getDiskCacheDir(Context context, String uniqueName) {  
  138.         String cachePath;  
  139.         if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())  
  140.                 || !Environment.isExternalStorageRemovable()) {  
  141.             cachePath = context.getExternalCacheDir().getPath();  
  142.         } else {  
  143.             cachePath = context.getCacheDir().getPath();  
  144.         }  
  145.         return new File(cachePath + File.separator + uniqueName);  
  146.     }  
  147.   
  148.     /** 
  149.      * 獲取當前應用程序的版本號。 
  150.      */  
  151.     public int getAppVersion(Context context) {  
  152.         try {  
  153.             PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),  
  154.                     0);  
  155.             return info.versionCode;  
  156.         } catch (NameNotFoundException e) {  
  157.             e.printStackTrace();  
  158.         }  
  159.         return 1;  
  160.     }  
  161.   
  162.     /** 
  163.      * 設置item子項的高度。 
  164.      */  
  165.     public void setItemHeight(int height) {  
  166.         if (height == mItemHeight) {  
  167.             return;  
  168.         }  
  169.         mItemHeight = height;  
  170.         notifyDataSetChanged();  
  171.     }  
  172.   
  173.     /** 
  174.      * 使用MD5算法對傳入的key進行加密並返回。 
  175.      */  
  176.     public String hashKeyForDisk(String key) {  
  177.         String cacheKey;  
  178.         try {  
  179.             final MessageDigest mDigest = MessageDigest.getInstance("MD5");  
  180.             mDigest.update(key.getBytes());  
  181.             cacheKey = bytesToHexString(mDigest.digest());  
  182.         } catch (NoSuchAlgorithmException e) {  
  183.             cacheKey = String.valueOf(key.hashCode());  
  184.         }  
  185.         return cacheKey;  
  186.     }  
  187.       
  188.     /** 
  189.      * 將緩存記錄同步到journal文件中。 
  190.      */  
  191.     public void fluchCache() {  
  192.         if (mDiskLruCache != null) {  
  193.             try {  
  194.                 mDiskLruCache.flush();  
  195.             } catch (IOException e) {  
  196.                 e.printStackTrace();  
  197.             }  
  198.         }  
  199.     }  
  200.   
  201.     private String bytesToHexString(byte[] bytes) {  
  202.         StringBuilder sb = new StringBuilder();  
  203.         for (int i = 0; i < bytes.length; i++) {  
  204.             String hex = Integer.toHexString(0xFF & bytes[i]);  
  205.             if (hex.length() == 1) {  
  206.                 sb.append('0');  
  207.             }  
  208.             sb.append(hex);  
  209.         }  
  210.         return sb.toString();  
  211.     }  
  212.   
  213.     /** 
  214.      * 異步下載圖片的任務。 
  215.      *  
  216.      * @author guolin 
  217.      */  
  218.     class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {  
  219.   
  220.         /** 
  221.          * 圖片的URL地址 
  222.          */  
  223.         private String imageUrl;  
  224.   
  225.         @Override  
  226.         protected Bitmap doInBackground(String... params) {  
  227.             imageUrl = params[0];  
  228.             FileDescriptor fileDescriptor = null;  
  229.             FileInputStream fileInputStream = null;  
  230.             Snapshot snapShot = null;  
  231.             try {  
  232.                 // 生成圖片URL對應的key  
  233.                 final String key = hashKeyForDisk(imageUrl);  
  234.                 // 查找key對應的緩存  
  235.                 snapShot = mDiskLruCache.get(key);  
  236.                 if (snapShot == null) {  
  237.                     // 如果沒有找到對應的緩存,則準備從網絡上請求數據,並寫入緩存  
  238.                     DiskLruCache.Editor editor = mDiskLruCache.edit(key);  
  239.                     if (editor != null) {  
  240.                         OutputStream outputStream = editor.newOutputStream(0);  
  241.                         if (downloadUrlToStream(imageUrl, outputStream)) {  
  242.                             editor.commit();  
  243.                         } else {  
  244.                             editor.abort();  
  245.                         }  
  246.                     }  
  247.                     // 緩存被寫入後,再次查找key對應的緩存  
  248.                     snapShot = mDiskLruCache.get(key);  
  249.                 }  
  250.                 if (snapShot != null) {  
  251.                     fileInputStream = (FileInputStream) snapShot.getInputStream(0);  
  252.                     fileDescriptor = fileInputStream.getFD();  
  253.                 }  
  254.                 // 將緩存數據解析成Bitmap對象  
  255.                 Bitmap bitmap = null;  
  256.                 if (fileDescriptor != null) {  
  257.                     bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);  
  258.                 }  
  259.                 if (bitmap != null) {  
  260.                     // 將Bitmap對象添加到內存緩存當中  
  261.                     addBitmapToMemoryCache(params[0], bitmap);  
  262.                 }  
  263.                 return bitmap;  
  264.             } catch (IOException e) {  
  265.                 e.printStackTrace();  
  266.             } finally {  
  267.                 if (fileDescriptor == null && fileInputStream != null) {  
  268.                     try {  
  269.                         fileInputStream.close();  
  270.                     } catch (IOException e) {  
  271.                     }  
  272.                 }  
  273.             }  
  274.             return null;  
  275.         }  
  276.   
  277.         @Override  
  278.         protected void onPostExecute(Bitmap bitmap) {  
  279.             super.onPostExecute(bitmap);  
  280.             // 根據Tag找到相應的ImageView控件,將下載好的圖片顯示出來。  
  281.             ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);  
  282.             if (imageView != null && bitmap != null) {  
  283.                 imageView.setImageBitmap(bitmap);  
  284.             }  
  285.             taskCollection.remove(this);  
  286.         }  
  287.   
  288.         /** 
  289.          * 建立HTTP請求,並獲取Bitmap對象。 
  290.          *  
  291.          * @param imageUrl 
  292.          *            圖片的URL地址 
  293.          * @return 解析後的Bitmap對象 
  294.          */  
  295.         private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {  
  296.             HttpURLConnection urlConnection = null;  
  297.             BufferedOutputStream out = null;  
  298.             BufferedInputStream in = null;  
  299.             try {  
  300.                 final URL url = new URL(urlString);  
  301.                 urlConnection = (HttpURLConnection) url.openConnection();  
  302.                 in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);  
  303.                 out = new BufferedOutputStream(outputStream, 8 * 1024);  
  304.                 int b;  
  305.                 while ((b = in.read()) != -1) {  
  306.                     out.write(b);  
  307.                 }  
  308.                 return true;  
  309.             } catch (final IOException e) {  
  310.                 e.printStackTrace();  
  311.             } finally {  
  312.                 if (urlConnection != null) {  
  313.                     urlConnection.disconnect();  
  314.                 }  
  315.                 try {  
  316.                     if (out != null) {  
  317.                         out.close();  
  318.                     }  
  319.                     if (in != null) {  
  320.                         in.close();  
  321.                     }  
  322.                 } catch (final IOException e) {  
  323.                     e.printStackTrace();  
  324.                 }  
  325.             }  
  326.             return false;  
  327.         }  
  328.   
  329.     }  
  330.   
  331. }  

代碼有點長,我們一點點進行分析。首先在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的代碼,非常簡單,如下所示:

[java] view plaincopy
  1. public class MainActivity extends Activity {  
  2.   
  3.     /** 
  4.      * 用於展示照片牆的GridView 
  5.      */  
  6.     private GridView mPhotoWall;  
  7.   
  8.     /** 
  9.      * GridView的適配器 
  10.      */  
  11.     private PhotoWallAdapter mAdapter;  
  12.   
  13.     private int mImageThumbSize;  
  14.     private int mImageThumbSpacing;  
  15.   
  16.     @Override  
  17.     protected void onCreate(Bundle savedInstanceState) {  
  18.         super.onCreate(savedInstanceState);  
  19.         setContentView(R.layout.activity_main);  
  20.         mImageThumbSize = getResources().getDimensionPixelSize(  
  21.                 R.dimen.image_thumbnail_size);  
  22.         mImageThumbSpacing = getResources().getDimensionPixelSize(  
  23.                 R.dimen.image_thumbnail_spacing);  
  24.         mPhotoWall = (GridView) findViewById(R.id.photo_wall);  
  25.         mAdapter = new PhotoWallAdapter(this0, Images.imageThumbUrls,  
  26.                 mPhotoWall);  
  27.         mPhotoWall.setAdapter(mAdapter);  
  28.         mPhotoWall.getViewTreeObserver().addOnGlobalLayoutListener(  
  29.                 new ViewTreeObserver.OnGlobalLayoutListener() {  
  30.                       
  31.                     @Override  
  32.                     public void onGlobalLayout() {  
  33.                         final int numColumns = (int) Math.floor(mPhotoWall  
  34.                                 .getWidth()  
  35.                                 / (mImageThumbSize + mImageThumbSpacing));  
  36.                         if (numColumns > 0) {  
  37.                             int columnWidth = (mPhotoWall.getWidth() / numColumns)  
  38.                                     - mImageThumbSpacing;  
  39.                             mAdapter.setItemHeight(columnWidth);  
  40.                             mPhotoWall.getViewTreeObserver()  
  41.                                     .removeGlobalOnLayoutListener(this);  
  42.                         }  
  43.                     }  
  44.                 });  
  45.     }  
  46.       
  47.     @Override  
  48.     protected void onPause() {  
  49.         super.onPause();  
  50.         mAdapter.fluchCache();  
  51.     }  
  52.   
  53.     @Override  
  54.     protected void onDestroy() {  
  55.         super.onDestroy();  
  56.         // 退出程序時結束所有的下載任務  
  57.         mAdapter.cancelAllTasks();  
  58.     }  
  59.   
  60. }  

上述代碼中,我們通過getViewTreeObserver()的方式監聽View的佈局事件,當佈局完成以後,我們重新修改一下GridView中子View的高度,以保證子View的寬度和高度可以保持一致。

到這裏還沒有結束,最後還需要配置一下AndroidManifest.xml文件,並加入相應的權限,如下所示:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     package="com.example.photoswalldemo"  
  3.     android:versionCode="1"  
  4.     android:versionName="1.0" >  
  5.   
  6.     <uses-sdk  
  7.         android:minSdkVersion="14"  
  8.         android:targetSdkVersion="17" />  
  9.   
  10.     <uses-permission android:name="android.permission.INTERNET" />  
  11.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  
  12.   
  13.     <application  
  14.         android:allowBackup="true"  
  15.         android:icon="@drawable/ic_launcher"  
  16.         android:label="@string/app_name"  
  17.         android:theme="@style/AppTheme" >  
  18.         <activity  
  19.             android:name="com.example.photoswalldemo.MainActivity"  
  20.             android:label="@string/app_name" >  
  21.             <intent-filter>  
  22.                 <action android:name="android.intent.action.MAIN" />  
  23.                 <category android:name="android.intent.category.LAUNCHER" />  
  24.             </intent-filter>  
  25.         </activity>  
  26.     </application>  
  27.   
  28. </manifest>  

好了,全部代碼都在這兒了,讓我們來運行一下吧,效果如下圖所示:


第一次從網絡上請求圖片的時候有點慢,但之後加載圖片就會非常快了,滑動起來也很流暢。

那麼我們最後再檢查一下這些圖片是不是已經正確緩存在指定地址了,進入 /sdcard/Android/data/<application package>/cache/thumb 這個路徑,如下圖所示:


可以看到,每張圖片的緩存以及journal文件都在這裏了,說明我們的硬盤緩存已經成功了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章