一個開源項目,關於圖像異步緩存下載的簡單apps,網上有相應的代碼,但是沒有任何說明和講解(英文blog中有,講了一下框架),那就自己研究吧。
主要對ImageDownloader分析:
Bitmap downloadBitmap(String url);//從網站下載一幅圖片,比較簡單
HttpClient:
AndroidHttpClient:Apache DefaultHttpClient的子類,已經配置好默認的合理設置和Android註冊過的方案。不能直接創建對象。
AndroidHttpClient newInstance(String userAgent,Context context)創建一個新HttpClient對象。
HttpResponse:一個HTTP應答。
HttpEntity getEntity(),獲得應答的消息實例
HttpEntity:一個可以發送接收HTTP消息的實例。
在一些情況下,javaDoc根據它們的內容來源把entity分三種:streamed,即內容來源是數據流,一般不可重複的;self-contained,內容來自內
存也意味着和連接以及其他entities沒有關係,一般可重複;wrapping,內容從另一個entity獲得。
InputStream getContent():創建實例的一個新的InputStream對象
consumeContent():這個方法被調用意味着這個實例的內容不再被請求了,而該實例分配的所有資源都會被釋放。
但是在 BitmapFactory.decodeStream
之前版本的bug可能會阻止代碼進行慢連接,而newFlushedInputStream(inputStream)方法可以解決這個問題。
static class FlushedInputStream extends FilterInputStream;其中裏面的skip函數是爲了使在以後的讀輸入流時準確的跳過n個字節。
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int byte = read();//read()只讀一個字節,但返回0~255的隨機數
if (byte < 0) {
break; //到文件結尾
} else {
bytesSkipped = 1; // 讀一個字節,這裏當跳過0個字節時爲什麼要讀一個字節不是太懂……
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
如果在ListAdapter的getView方法中直接下載圖片,效果會很卡,因爲每一個新圖像的顯示都要等圖像下載下來。
非常遺憾的是,AndroidHttpClient竟然不能在主線程裏運行,否則會顯示"This thread forbids HTTP requests" 錯誤信息。對於AsyncTask類,它提供其中一個最簡單的方法來使新任務脫離UI線程運行。
我們創建了一個ImageDownloader類來負責生成這些新的下載任務(task),它提供download方法來分配一張從URL下載的圖片到對應的ImageView中。
public class ImageDownloader {
public void download(String url, ImageView imageView) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
task.execute(url);
}
}
BitmapDownloaderTask是AsyncTask的子類,用來實際下載圖片的。調用execute(url)來運行它,能夠立即返回結果,這也是UI線程調用它的主要原因。
其中doInBackground方法中調用了downloadBitmap來下載圖片,onPostExecute是當下載結束時UI線程調用的,將存儲在BitmapDownloaderTask中的imageView和下載的Bitmap相關聯,而這個ImageView是一個弱引用,可以被系統回收,所以要在onpostExecute中檢查弱引用和這個imageView不爲空。
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
// Actual download method, run in the task thread
protected Bitmap doInBackground(String... params) {
// params comes from the execute() call: params[0] is the url.
return downloadBitmap(params[0]);
}
@Override
// Once the image is downloaded, associates it to the imageView
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
if (imageView != null) {BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
// Change bitmap only if this process is still associated with this ImageView
if (this == bitmapDownloaderTask) {
imageView.setImageBitmap(bitmap);
}
}
}
}
}
對於ListView上的圖像瀏覽,當用戶快速滑動ListView時,某一個ImageView對象會被用到很多次,每一次顯示都會觸發一個下載,然後改變對應的圖片。和大多數並行應用一樣,有順序相關的問題。在這個程序中,不能保證下載會按開始的順序結束,有可能先開始的後下載完,“ The result is that the image finally displayed in the list may come from a previous item, which simply happened to have taken longer to download. ” 結果就是,由於與該item對應的網絡圖片下載慢,導致該item位置還暫時顯示着之前的item顯示的圖片(還沒被刷新,有可能長時間不被刷新)
爲了解決這個問題,我們應該記住下載的順序,使得最後的下載會被有效地顯示,要讓每一個ImageView記住它們的上一次下載任務。因此我們給出了DownloadedDrawable類,向ImageView中加入對對應下載任務的弱引用來暫時綁定正在下載圖片的ImageView。
static class DownloadedDrawable extends ColorDrawable ;//該類包含了一個對下載的弱引用
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
super(Color.BLACK);
bitmapDownloaderTaskReference =
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
上面ImageDownload中的download修改爲:
public void download(String url, ImageView imageView) {
if (cancelPotentialDownload(url, imageView)) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url);
}
}
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageview);
//通過ImageView得到對應的下載
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
下面總結下載過程:
download(url , imageview)——>
@創建一個和該imageview相對應的下載任務,這個任務對imageview進行弱引用
@創建與這個任務相對應的DownloadedDrawable,對這個任務弱引用
@imageview加載DownloadedDrawable
@執行下載任務,下載對應url的圖像 ——execute(url)進入下載任務類
在下載任務類中:
——>doInBackground ===>downloadBitmap(url) 下載圖片,結果Bitmap作爲下面的參數
——>onPostExcute(Bitmap)
@獲得任務引用的imageview
@通過DownloadedDrawable獲得該imageview所對應的任務
@如果當前任務是這個imageview所對應的任務,則設置這個imageview的圖片爲下載下
來的Bitmap
imageview和任務相互弱引用,形成綁定關係