【構建Android快取模組】(三)Controller & 非同步圖片載入

yangxi_001發表於2013-11-27

  節課我們學習了快取模組的實現, 快取分做兩份:Memory CacheFile Cache。方法也很簡單,分別是:

  • 儲存檔案
  • 按唯一key值索引檔案
  • 清空快取

    區別在於記憶體快取讀取優先,因為它讀寫的速度更快。但是考慮到記憶體限制,退而選用檔案儲存,分擔記憶體快取的壓力。

    原理非常簡單,在第一課中已經詳細分析了。那麼要怎麼才能將這個快取模組與UI模組的顯示關聯起來呢?在這裡我們需要一個控制器,掌管資料流向和讀寫,同時控制UI的顯示。

    那麼這個控制器需要以下的元素:

  • 記憶體快取
  • 硬碟快取
  • 非同步任務處理
  • 控制UI顯示
1 //caches
2 private MemoryCache memoryCache;
3 private FileCache fileCache;
4 //Asynchronous task
5 private static AsyncImageLoader imageLoader;
    Memory CacheFile Cache在上一課中有具體的實現,這裡有一個非同步的任務處理器——AsyncImageDownloader,它用來在後臺下載資料,完成下載後儲存資料到快取中,並更新UI的顯示 。讓我們來看看它是如何實現的:
01 class AsyncImageDownloader extends AsyncTask<Void, Void, Bitmap>{
02     private ImageView imageView;
03     private String fileName;
04      
05     public AsyncImageDownloader(ImageView imageView, String fileName){
06         this.imageView = imageView;
07         this.fileName = fileName;
08     }
09      
10     @Override
11     protected void onPreExecute() {
12         super.onPreExecute();
13         imageView.setImageResource(R.drawable.placeholder);
14     }
15      
16     @Override
17     protected Bitmap doInBackground(Void... arg0) {
18         String url = Utils.getRealUrlOfPicture(fileName);
19         HttpResponse response = new HttpRetriever().requestGet(url, null);
20         Log.i(TAG, "url: " + url);
21         Log.i(TAG, "respone: " + response);
22         InputStream in = null;
23         try {
24             if(response != null && response.getEntity() != null)
25                 in = response.getEntity().getContent();
26         catch (IllegalStateException e) {
27             e.printStackTrace();
28             return null;
29         catch (IOException e) {
30             e.printStackTrace();
31             return null;
32         }
33          
34         //TODO to be optimized: adjust the size of bitmap
35         return BitmapFactory.decodeStream(in);
36     }
37      
38     @Override
39     protected void onPostExecute(Bitmap result) {
40         super.onPostExecute(result);
41         if(result != null && imageView != null)
42             imageView.setImageBitmap(result);
43          
44         //TODO cache the bitmap both in sdcard & memory
45         memoryCache.put(fileName, result);// key is a unique token, value is the bitmap
46          
47         fileCache.put(fileName, result);
48     }
49 }

    可以看到這個類的建構函式需要兩個引數,分別是檔名和對應要顯示的ImageView,那麼在任務開始的時候,可以為該ImageView設定未下載狀態的圖片,然後下載完成後更新UI。

    需要提醒的是,這裡的唯一key值,我使用的是檔名,因為我接收到的檔名是唯一的。猿媛們也可以根據自己的需求,設計自己的唯一key值演算法。

    接下來,我們需要讀用key值索引相應的Bitmap:

01 public Bitmap getBitmap(String key){
02     Bitmap bitmap = null;
03     //1. search memory
04     bitmap = memoryCache.get(key);
05      
06     //2. search sdcard
07     if(bitmap == null){
08         File file = fileCache.getFile(key);
09         if(file != null)
10             bitmap = BitmapHelper.decodeFile(file, null);
11     }
12      
13     return bitmap;
14 }

    讀取到Bitmap後進行顯示:

01 public void displayBitmap(ImageView imageView, String fileName){
02     //no pic for this item
03     if(fileName == null || "".equals(fileName))
04         return;
05      
06     Bitmap bitmap = getBitmap(fileName);
07     //search in cache, if there is no such bitmap, launch downloads
08     if(bitmap != null){
09         imageView.setImageBitmap(bitmap);
10     }
11     else{
12         Log.w(TAG, "Can't find the file you required.");
13         new AsyncImageDownloader(imageView, fileName).execute();
14     }
15 }
    到這裡,一個簡單的快取框架就搭建成功了。它簡潔有效,但是非常單薄,似乎不夠強大,需要你們根據自己的需求進行修改。另外它本來的目的就是用於演示,理解這個以後,我們再來看Google的BitmapFun。

    不過,我將它應用在一個小專案中,效能還不錯。對於小專案的需求,應該是夠的。

    最後,附上使用方法,以及整個類的原始碼。

    使用方法:

1 AsyncImageLoader imageLoader = AsyncImageLoader.getInstance(this);、
2 imageLoader.displayBitmap(imageView, fileName);

    原始碼:

001 <strong>public class AsyncImageLoader {
002  
003     private static final String TAG = "AsyncImageLoader";
004      
005     //caches
006     private MemoryCache memoryCache;
007     private FileCache fileCache;
008     //Asynchronous task
009     private static AsyncImageLoader imageLoader;
010  
011     class AsyncImageDownloader extends AsyncTask<Void, Void, Bitmap>{
012         private ImageView imageView;
013         private String fileName;
014          
015         public AsyncImageDownloader(ImageView imageView, String fileName){
016             this.imageView = imageView;
017             this.fileName = fileName;
018         }
019          
020         @Override
021         protected void onPreExecute() {
022             super.onPreExecute();
023             imageView.setImageResource(R.drawable.placeholder);
024         }
025          
026         @Override
027         protected Bitmap doInBackground(Void... arg0) {
028             String url = Utils.getRealUrlOfPicture(fileName);
029             HttpResponse response = new HttpRetriever().requestGet(url, null);
030             Log.i(TAG, "url: " + url);
031             Log.i(TAG, "respone: " + response);
032             InputStream in = null;
033             try {
034                 if(response != null && response.getEntity() != null)
035                     in = response.getEntity().getContent();
036             catch (IllegalStateException e) {
037                 e.printStackTrace();
038                 return null;
039             catch (IOException e) {
040                 e.printStackTrace();
041                 return null;
042             }
043              
044             //TODO to be optimized: adjust the size of bitmap
045             return BitmapFactory.decodeStream(in);
046         }
047          
048         @Override
049         protected void onPostExecute(Bitmap result) {
050             super.onPostExecute(result);
051             if(result != null && imageView != null)
052                 imageView.setImageBitmap(result);
053              
054             //TODO cache the bitmap both in sdcard & memory
055             memoryCache.put(fileName, result);// key is a unique token, value is the bitmap
056              
057             fileCache.put(fileName, result);
058         }
059     }
060      
061     private AsyncImageLoader(Context context){
062         this.memoryCache        =   new MemoryCache();
063         this.fileCache          =   new FileCache(context);
064     }
065      
066     public static AsyncImageLoader getInstance(Context context){
067         if(imageLoader == null)
068             imageLoader = new AsyncImageLoader(context);
069          
070         return imageLoader;
071     }
072      
073     public void displayBitmap(ImageView imageView, String fileName){
074         //no pic for this item
075         if(fileName == null || "".equals(fileName))
076             return;
077          
078         Bitmap bitmap = getBitmap(fileName);
079         //search in cache, if there is no such bitmap, launch downloads
080         if(bitmap != null){
081             imageView.setImageBitmap(bitmap);
082         }
083         else{
084             Log.w(TAG, "Can't find the file you required.");
085             new AsyncImageDownloader(imageView, fileName).execute();
086         }
087     }
088      
089     public Bitmap getBitmap(String key){
090         Bitmap bitmap = null;
091         //1. search memory
092         bitmap = memoryCache.get(key);
093          
094         //2. search sdcard
095         if(bitmap == null){
096             File file = fileCache.getFile(key);
097             if(file != null)
098                 bitmap = BitmapHelper.decodeFile(file, null);
099         }
100          
101         return bitmap;
102     }
103      
104     public void clearCache(){
105         if(memoryCache != null)
106             memoryCache.clear();
107         if(fileCache != null)
108             fileCache.clear();
109     }
110 }</strong>


原始碼:

附上原始碼,不過伺服器的原始碼暫時還沒有放出來,先看看客戶端的吧。

https://github.com/ryanhoo/SoftRead

相關文章