在我們項目中肯定會用到一些第三方的library,有網絡框架,圖片處理框架等。而我現在常用的就是volley和ImageLoader了,上上篇博客我們把Volley的代碼分析了一下,今天我們就來拿一個常用的圖片框架——UniversalImageLoader來分析一下。
如何去看源碼?我一般都是從公開的使用方法介入,對于今天我們要看的ImageLoader當然就是,
~~~
ImageLoader.displayImage();
~~~
方法了,我們進入ImageLoader類發現,這當中有好幾個重載的displayImage方法,但是不管調用哪一個,最后都會輾轉來到,
~~~
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener);
~~~
方法,理所當然,我們就從這個方法上手開始分析ImageLoader的代碼,
~~~
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
checkConfiguration();
if (imageAware == null) {
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
if (listener == null) {
listener = defaultListener;
}
if (options == null) {
// 默認的設置
// 通過defaultDisplayImageOptions = DisplayImageOptions.createSimple();
// 而來,具體的默認設置可以看DisplayImageOptions里的代碼
options = configuration.defaultDisplayImageOptions;
}
// 閑的蛋疼給了一個空的uri
if (TextUtils.isEmpty(uri)) {
engine.cancelDisplayTaskFor(imageAware);
// 這里的listener或許是你設置的,一般是默認的
// 回調下載開始
listener.onLoadingStarted(uri, imageAware.getWrappedView());
if (options.shouldShowImageForEmptyUri()) {
// 顯示為空時的圖片
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {
imageAware.setImageDrawable(null);
}
// 回調下載完成
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
return;
}
if (targetSize == null) {
// 封裝圖片的大小
// 這里的大小是根據Imageview來的
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
// 生成一個緩存用的key
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
// 回調開始加載圖片
listener.onLoadingStarted(uri, imageAware.getWrappedView());
// 嘗試從內存緩存中獲取圖片
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
// 如果獲取到圖片
// 并且圖片沒有被回收了
if (bmp != null && !bmp.isRecycled()) {
L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
// mark 怎么去處理?
/**
* public boolean shouldPostProcess() {
return postProcessor != null;
}
*/
// 因為是默認的,所以這里返回false
if (options.shouldPostProcess()) {
// ImageLoadingInfo封裝了uri
// 該任務的圖片大小
// 該任務的設置,監聽器等信息
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
// 處理并顯示圖片的任務
// defineHandler 是去獲取的一個handler
// 通常是new一個UI線程上的handler
// 這里面的邏輯其實就是當提供了BitmapProcessor時
// 我們可以在圖片顯示之前去處理一下圖片
// 最后還是會走到LoadAndDisplayImageTask
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run(); // 同步的任務,直接調用run方法
} else {
// 異步任務, 將任務提交出去
// 從這里我們也可以看出ProcessAndDisplayImageTask
// 實現了了Runnable接口
engine.submit(displayTask);
}
} else {
// 直接顯示圖片
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
// 沒有從內存中獲取到圖片
// 或者圖片被標記回收了
if (options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {
imageAware.setImageDrawable(null);
}
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);
}
}
}
~~~
在代碼中注釋我已經寫的很詳細了,這里我們簡單的說明一下,`imageAware`是ImageLoader框架對ImageView的包裝,提供了幾個便捷的方法供ImageLoader使用,例如`ImageViewAware`的源碼:
~~~
// ImageAware其實就是包裝了一下ImageView
public class ImageViewAware extends ViewAware {
/**
* Constructor. <br />
* References {@link #ImageViewAware(ImageView, boolean) ImageViewAware(imageView, true)}.
*
* @param imageView {@link ImageView ImageView} to work with
*/
public ImageViewAware(ImageView imageView) {
super(imageView);
}
/**
* Constructor
*
* @param imageView {@link ImageView ImageView} to work with
* @param checkActualViewSize <b>true</b> - then {@link #getWidth()} and {@link #getHeight()} will check actual
* size of ImageView. It can cause known issues like
* <a href="https://github.com/nostra13/Android-Universal-Image-Loader/issues/376">this</a>.
* But it helps to save memory because memory cache keeps bitmaps of actual (less in
* general) size.
* <p/>
* <b>false</b> - then {@link #getWidth()} and {@link #getHeight()} will <b>NOT</b>
* consider actual size of ImageView, just layout parameters. <br /> If you set 'false'
* it's recommended 'android:layout_width' and 'android:layout_height' (or
* 'android:maxWidth' and 'android:maxHeight') are set with concrete values. It helps to
* save memory.
* <p/>
*/
public ImageViewAware(ImageView imageView, boolean checkActualViewSize) {
super(imageView, checkActualViewSize);
}
/**
* {@inheritDoc}
* <br />
* 3) Get <b>maxWidth</b>.
*/
@Override
public int getWidth() {
int width = super.getWidth();
if (width <= 0) {
ImageView imageView = (ImageView) viewRef.get();
if (imageView != null) {
width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check maxWidth parameter
}
}
return width;
}
/**
* {@inheritDoc}
* <br />
* 3) Get <b>maxHeight</b>
*/
@Override
public int getHeight() {
int height = super.getHeight();
if (height <= 0) {
ImageView imageView = (ImageView) viewRef.get();
if (imageView != null) {
height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check maxHeight parameter
}
}
return height;
}
@Override
public ViewScaleType getScaleType() {
ImageView imageView = (ImageView) viewRef.get();
if (imageView != null) {
return ViewScaleType.fromImageView(imageView);
}
return super.getScaleType();
}
@Override
public ImageView getWrappedView() {
return (ImageView) super.getWrappedView();
}
@Override
protected void setImageDrawableInto(Drawable drawable, View view) {
((ImageView) view).setImageDrawable(drawable);
if (drawable instanceof AnimationDrawable) {
((AnimationDrawable)drawable).start();
}
}
@Override
protected void setImageBitmapInto(Bitmap bitmap, View view) {
((ImageView) view).setImageBitmap(bitmap);
}
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
try {
Field field = ImageView.class.getDeclaredField(fieldName);
field.setAccessible(true);
int fieldValue = (Integer) field.get(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
}
} catch (Exception e) {
L.e(e);
}
return value;
}
}
~~~
我們繼續分析displayImage方法,一個if語句,去判斷uri是否為空,當我們無比蛋疼的塞給ImageLoader一個空的Uri的使用這里的邏輯會執行,其實很簡單,就是各種回調,還有就是顯示我們配置的當數據為空的時的圖片,大部分情況下,我們還是會來到這個if下面的代碼,
~~~
if (targetSize == null) {
// 封裝圖片的大小
// 這里的大小是根據Imageview來的
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
~~~
這里的作用其實就是為我們的圖片準備了一個寬高,看到上面ImageViewAware的代碼了,這里面一般都是去調用imageAware.getWidth()和imageAware.getHeight()方法去獲取大小。接下來的代碼就是試圖從內存中獲取圖片,如果獲取到了圖片,這里還有一個判斷,這個判斷是判斷我們是不是設置了BitmapProcessor,一般情況下我們是不去設置這個東西的,但是我們還是需要去了解一下這里面都是干了
啥,
首先我們包裝了一個ImageLoadingInfo,接下來執行了ProcessAndDisplayImageTask的run方法,不管是直接執行run方法還是他被submit了,但肯定都是執行了run方法,區別就是是在當前線程中執行還是其他線程。我們來看看ProcessAndDisplayImageTask的run方法都是干了
啥吧,
~~~
@Override
public void run() {
L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);
// 沒有找到默認的Processor
// BitmapProcessor是一個接口
// 只有一個process方法,目的是在顯示之前允許我們處理一下圖片
// 那這里不會報nullpointer嗎?
// 當然不是,因為在ImageLoader.displayImage中 submit該任務之前去判斷了
BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
Bitmap processedBitmap = processor.process(bitmap);
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
LoadedFrom.MEMORY_CACHE);
LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}
~~~
就是調用了BitmapProcessor的process方法處理一下圖片,至于怎么處理,完全取決于我們怎么實現BitmapProcessor接口了,這也就允許我們在現實圖片之前處理一下圖片,接下來的關鍵肯定就是`DisplayBitmapTask`了,我們來看一下這個類的run方法,
~~~
@Override
public void run() {
// 各種Listener的回調
if (imageAware.isCollected()) {
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else if (isViewWasReused()) {
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else {
L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
// 通過設置不同的displayer來顯示不同樣式的圖片
displayer.display(bitmap, imageAware, loadedFrom);
engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
}
}
~~~
這里的主要的最用就是回調然后調用BitmapDisplayer來顯示圖片,至于BitmapDisplayer怎么去顯示,當然是調用ImageAware的方法去給我們傳進來的ImageView設置圖片啦。
好,繼續回到之前的代碼,繼續看else,如果我們沒有提供BitmapProcessor,那么直接顯示圖片,現在講解的是從內存中獲取了圖片,那獲取不到呢?我們繼續看看else里是怎么做的,
~~~
// 沒有從內存中獲取到圖片
// 或者圖片被標記回收了
if (options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {
imageAware.setImageDrawable(null);
}
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);
}
~~~
首先如果我們設置了加載中的圖片,這里會顯示加載中的圖片,然后封裝了一個ImageLoadingInfo和LoadAndDisplayImageTask,并且執行它,我們相信,很多關鍵的代碼都在LoadAndDisplayImageTask當中,而且是在他的run方法中!
~~~
final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {
@Override
public void run() {
if (waitIfPaused()) return;
if (delayIfNeed()) return;
// 得到互斥鎖
// 對于Lock不熟悉的朋友可以去看java的Lock部分
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
if (loadFromUriLock.isLocked()) {
L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
}
// 啟動鎖
loadFromUriLock.lock();
Bitmap bmp;
try {
checkTaskNotActual();
// 嘗試從內存緩存中獲取圖片
bmp = configuration.memoryCache.get(memoryCacheKey);
// 如果圖片為空,或者圖片標記回收了
if (bmp == null || bmp.isRecycled()) {
// mark 這里面干了嗎?
bmp = tryLoadBitmap();
if (bmp == null) return; // listener callback already was fired
checkTaskNotActual();
checkTaskInterrupted();
if (options.shouldPreProcess()) {
L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPreProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
}
}
// 緩存到內存中
if (bmp != null && options.isCacheInMemory()) {
L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
configuration.memoryCache.put(memoryCacheKey, bmp);
}
} else {
loadedFrom = LoadedFrom.MEMORY_CACHE;
L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
}
if (bmp != null && options.shouldPostProcess()) {
L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
bmp = options.getPostProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
}
}
checkTaskNotActual();
checkTaskInterrupted();
} catch (TaskCancelledException e) {
fireCancelEvent();
return;
} finally {
// 釋放鎖
loadFromUriLock.unlock();
}
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
}
}
~~~
首先我們獲取了一個鎖,這里為什么要用鎖呢?別忘了,大多數情況下,我們是異步加載圖片,這里可能是在不同的線程中去加載圖片,加鎖的目的就是為了保證每次處理完成了以后其他的處理才能得以進行,關鍵代碼其實就是bmp = tryLoadBitmap();這一句,這個tryLoadBitmap里面到底干了什么呢?
~~~
private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {
// 從磁盤緩存中獲取圖片
File imageFile = configuration.diskCache.get(uri);
// 如果文件存在
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
loadedFrom = LoadedFrom.DISC_CACHE;
checkTaskNotActual();
// 直接從磁盤中加載圖片
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
// 如果圖片不存在
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
loadedFrom = LoadedFrom.NETWORK;
String imageUriForDecoding = uri;
// 將圖片緩存到磁盤
// tryCacheImageOnDisk去加載了網絡圖片
// mark tryCacheImageOnDisk的實現
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
// 加載成功了, 則獲取本地圖片文件
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
// 將uri替換成圖片在本地磁盤地址
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
checkTaskNotActual();
// 如果上面允許緩存, 則在上面就加載了圖片
// 并且將imageUriForDecoding替換成了本地uri
// 否則這里是去加載網絡圖片
bitmap = decodeImage(imageUriForDecoding);
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null);
}
}
} catch (IllegalStateException e) {
fireFailEvent(FailType.NETWORK_DENIED, null);
} catch (TaskCancelledException e) {
throw e;
} catch (IOException e) {
L.e(e);
fireFailEvent(FailType.IO_ERROR, e);
} catch (OutOfMemoryError e) {
L.e(e);
fireFailEvent(FailType.OUT_OF_MEMORY, e);
} catch (Throwable e) {
L.e(e);
fireFailEvent(FailType.UNKNOWN, e);
}
return bitmap;
}
~~~
這里才是加載圖片的關鍵,首先去判斷磁盤中是否存在圖片,如果存在,則直接從磁盤加載圖片,接下來,如果本地沒有,那就到了最后沖刺的階段了,到了從網絡獲取圖片的時候了,一個tryCacheImageOnDisk方法搞定了圖片從網絡的獲取,怎么去網絡獲取圖片我們先放一放,我們繼續看代碼,如果圖片下載成功了,那本地肯定有了新的圖片,那么我們就將uri替換成本地的uri,為啥要替換?我們繼續分析代碼,等分析完了,你再回頭來看,就明白什么意思了!接下來的代碼就很簡單了,就是在合適的時機將圖片緩存起來。那么關鍵,我們就來看看圖片怎么下載下來的吧。
~~~
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
boolean loaded;
try {
loaded = downloadImage();
if (loaded) {
int width = configuration.maxImageWidthForDiskCache;
int height = configuration.maxImageHeightForDiskCache;
if (width > 0 || height > 0) {
L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
resizeAndSaveImage(width, height); // TODO : process boolean result
}
}
} catch (IOException e) {
L.e(e);
loaded = false;
}
return loaded;
}
~~~
這里面的關鍵就是downloadImage了,我們繼續看看downloadImage,
~~~
private boolean downloadImage() throws IOException {
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
if (is == null) {
L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
return false;
} else {
try {
return configuration.diskCache.save(uri, is, this);
} finally {
IoUtils.closeSilently(is);
}
}
}
~~~
直接從Downloader中獲取一個stream,然后調用磁盤緩存類的save方法,先看看怎么save的?
BaseDiskCache.save方法,
~~~
@Override
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
File imageFile = getFile(imageUri);
File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
boolean loaded = false;
try {
OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
try {
loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);
} finally {
IoUtils.closeSilently(os);
}
} finally {
if (loaded && !tmpFile.renameTo(imageFile)) {
loaded = false;
}
if (!loaded) {
tmpFile.delete();
}
}
return loaded;
}
~~~
這里主要就是將從網絡獲取到stream寫入到本地。可以看到這幾個操作我們唯一可以用來區分的就是那個作為key的uri了,也正是因為uri,我們可以保證在圖片下載完成后,可以繼續從本地中獲取圖片然后設置大小并且顯示。
那上面的那個ImageDownloader是個啥呢?我們隨便來看一個ImageDownloader吧,也是ImageLoader唯一提供的一個實現了的ImageDownloader——BaseImageDownloader,
~~~
public class BaseImageDownloader implements ImageDownloader {
@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
// 根據不同的scheme去調用不同的方法
switch (Scheme.ofUri(imageUri)) {
case HTTP:
case HTTPS:
return getStreamFromNetwork(imageUri, extra);
case FILE:
return getStreamFromFile(imageUri, extra);
case CONTENT:
return getStreamFromContent(imageUri, extra);
case ASSETS:
return getStreamFromAssets(imageUri, extra);
case DRAWABLE:
return getStreamFromDrawable(imageUri, extra);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}
}
~~~
這里面根據不同的scheme來從不同的途徑來獲取圖片,我們現在只關心從網絡獲取部分,
~~~
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
HttpURLConnection conn = createConnection(imageUri, extra);
int redirectCount = 0;
while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
conn = createConnection(conn.getHeaderField("Location"), extra);
redirectCount++;
}
InputStream imageStream;
try {
imageStream = conn.getInputStream();
} catch (IOException e) {
// Read all data to allow reuse connection (http://bit.ly/1ad35PY)
IoUtils.readAndCloseStream(conn.getErrorStream());
throw e;
}
if (!shouldBeProcessed(conn)) {
IoUtils.closeSilently(imageStream);
throw new IOException("Image request failed with response code " + conn.getResponseCode());
}
return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}
~~~
這里就不再多說了,就是去調用HttpUrlConnection實現網絡圖片的獲取,并且獲取一個Stream。
好了,現在圖片下載到本地了,我們還有一個decoder.decode沒看,
~~~
private Bitmap decodeImage(String imageUri) throws IOException {
ViewScaleType viewScaleType = imageAware.getScaleType();
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
getDownloader(), options);
return decoder.decode(decodingInfo);
}
~~~
來看一個Decoder的實現,BaseImageDeocder,
~~~
@Override
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
Bitmap decodedBitmap;
ImageFileInfo imageInfo;
// 這里面會根據就scheme去從不同的地方加載圖片
// 從網絡加載的部分利用HttpUrlConnection
// 最后返回一個InputStream
InputStream imageStream = getImageStream(decodingInfo);
if (imageStream == null) {
L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
return null;
}
try {
// 獲取圖片的寬高和exif信息
imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
// 還原stream
// 什么叫還原? 因為通過上面的操作,我們的stream游標可能已經
// 已經不再首部,這時再去讀取,還是繼續讀取,造成了信息不完成
// 所以這里需要reset一下
imageStream = resetStream(imageStream, decodingInfo);
// 主要就是判斷圖片大小
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
// 通過stream decode出Bitmap
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
} finally {
IoUtils.closeSilently(imageStream);
}
if (decodedBitmap == null) {
L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
} else {
decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
~~~
這里面我們根據我們傳進來的stream來生成一個Bitmap對象,這里是調用了我們熟悉的代碼`BitmapFactory.decodeStream`來decode出了一個Bitmap對象,最后返回。這一切的一切完成以后,我們繼續看看LoadAndDisplayBitmapTask的run方法,這里還有一些工作沒有完成,
~~~
public void run() {
...
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
}
~~~
這里最終還是去執行了DisplayBitmapTask,將我們最后獲取到的Bitmap顯示到界面上,關于DisplayBitmapTask,我們在前面已經講解過了,大家可以去翻看前面的內容。
好了,到現在我們整個主流程都跑通了,但是還有很多細節沒有說,感興趣的朋友可以打開源碼去看一下。
參考資料:[https://github.com/nostra13/Android-Universal-Image-Loader](https://github.com/nostra13/Android-Universal-Image-Loader)