轉載請註明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/39057201),請尊重他人的辛勤勞動成果,謝謝!
本篇文章主要是帶大家從源碼的角度上面去解讀這個強大的圖片加載框架,自己很久沒有寫文章了,感覺生疏了許多,距離上一篇文章三個月多了,確實是自己平常忙,換了工作很多東西都要去看去理解,然後加上自己也懶了,沒有以前那麼有激情了,我感覺這節奏不對,我要繼續保持以前的激情,正所謂好記性不如爛筆頭,有時候自己也會去翻看下之前寫的東西,我覺得知識寫下來比在腦海中留存的更久,今天就給大家來讀一讀這個框架的源碼,我感覺這個圖片加載框架確實寫的很不錯,讀完代碼自己也學到了很多。我希望大家可以先去看下Android 開源框架Universal-Image-Loader完全解析(一)--- 基本介紹及使用, Android 開源框架Universal-Image-Loader完全解析(二)--- 圖片緩存策略詳解 ,我希望大家可以堅持看完,看完了對你絕對是有收穫的。
- ImageView mImageView = (ImageView) findViewById(R.id.image);
- String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";
- //顯示圖片的配置
- DisplayImageOptions options = new DisplayImageOptions.Builder()
- .showImageOnLoading(R.drawable.ic_stub)
- .showImageOnFail(R.drawable.ic_error)
- .cacheInMemory(true)
- .cacheOnDisk(true)
- .bitmapConfig(Bitmap.Config.RGB_565)
- .build();
- ImageLoader.getInstance().displayImage(imageUrl, mImageView, options);
- public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
- displayImage(uri, new ImageViewAware(imageView), options, null, null);
- }
接下來看具體的displayImage方法啦,由於這個方法代碼量蠻多的,所以這裏我分開來讀
- checkConfiguration();
- if (imageAware == null) {
- throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
- }
- if (listener == null) {
- listener = emptyListener;
- }
- if (options == null) {
- options = configuration.defaultDisplayImageOptions;
- }
- if (TextUtils.isEmpty(uri)) {
- engine.cancelDisplayTaskFor(imageAware);
- 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;
- }
12-21行主要是針對url爲空的時候做的處理,第13行代碼中,ImageLoaderEngine中存在一個HashMap,用來記錄正在加載的任務,加載圖片的時候會將ImageView的id和圖片的url加上尺寸加入到HashMap中,加載完成之後會將其移除,然後將DisplayImageOptions的imageResForEmptyUri的圖片設置給ImageView,最後回調給ImageLoadingListener接口告訴它這次任務完成了。
- ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
- 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);
- if (options.shouldPostProcess()) {
- ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
- options, listener, progressListener, engine.getLockForUri(uri));
- ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
- defineHandler(options));
- if (options.isSyncLoading()) {
- displayTask.run();
- } else {
- engine.submit(displayTask);
- }
- } else {
- options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
- listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
- }
- }
第7行從內存緩存中獲取Bitmap對象,我們可以再ImageLoaderConfiguration中配置內存緩存邏輯,默認使用的是LruMemoryCache,這個類我在前面的文章中講過
第11行中有一個判斷,我們如果在DisplayImageOptions中設置了postProcessor就進入true邏輯,不過默認postProcessor是爲null的,BitmapProcessor接口主要是對Bitmap進行處理,這個框架並沒有給出相對應的實現,如果我們有自己的需求的時候可以自己實現BitmapProcessor接口(比如將圖片設置成圓形的)
第22 -23行是將Bitmap設置到ImageView上面,這裏我們可以在DisplayImageOptions中配置顯示需求displayer,默認使用的是SimpleBitmapDisplayer,直接將Bitmap設置到ImageView上面,我們可以配置其他的顯示邏輯, 他這裏提供了FadeInBitmapDisplayer(透明度從0-1)RoundedBitmapDisplayer(4個角是圓弧)等, 然後回調到ImageLoadingListener接口
- 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);
- }
接下來我們就看LoadAndDisplayImageTask的run(), 這個類還是蠻複雜的,我們還是一段一段的分析
- if (waitIfPaused()) return;
- if (delayIfNeed()) return;
如果waitIfPaused(), delayIfNeed()返回true的話,直接從run()方法中返回了,不執行下面的邏輯, 接下來我們先看看waitIfPaused()
- private boolean waitIfPaused() {
- AtomicBoolean pause = engine.getPause();
- if (pause.get()) {
- synchronized (engine.getPauseLock()) {
- if (pause.get()) {
- L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);
- try {
- engine.getPauseLock().wait();
- } catch (InterruptedException e) {
- L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
- return true;
- }
- L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);
- }
- }
- }
- return isTaskNotActual();
- }
這個方法是幹嘛用呢,主要是我們在使用ListView,GridView去加載圖片的時候,有時候爲了滑動更加的流暢,我們會選擇手指在滑動或者猛地一滑動的時候不去加載圖片,所以才提出了這麼一個方法,那麼要怎麼用呢? 這裏用到了PauseOnScrollListener這個類,使用很簡單ListView.setOnScrollListener(new PauseOnScrollListener(pauseOnScroll, pauseOnFling )), pauseOnScroll控制我們緩慢滑動ListView,GridView是否停止加載圖片,pauseOnFling 控制猛的滑動ListView,GridView是否停止加載圖片
除此之外,這個方法的返回值由isTaskNotActual()決定,我們接着看看isTaskNotActual()的源碼
- private boolean isTaskNotActual() {
- return isViewCollected() || isViewReused();
- }
- 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()) {
- 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();
- }
- ReentrantLock getLockForUri(String uri) {
- ReentrantLock lock = uriLocks.get(uri);
- if (lock == null) {
- lock = new ReentrantLock();
- uriLocks.put(uri, lock);
- }
- return lock;
- }
來到第12行,它們會先從內存緩存中獲取一遍,如果內存緩存中沒有在去執行下面的邏輯,所以ReentrantLock的作用就是避免這種情況下重複的去從網絡上面請求圖片。
第14行的方法tryLoadBitmap(),這個方法確實也有點長,我先告訴大家,這裏面的邏輯是先從文件緩存中獲取有沒有Bitmap對象,如果沒有在去從網絡中獲取,然後將bitmap保存在文件系統中,我們還是具體分析下
- File imageFile = configuration.diskCache.get(uri);
- if (imageFile != null && imageFile.exists()) {
- 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;
- if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
- imageFile = configuration.diskCache.get(uri);
- if (imageFile != null) {
- imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
- }
- }
- checkTaskNotActual();
- bitmap = decodeImage(imageUriForDecoding);
- if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
- fireFailEvent(FailType.DECODING_ERROR, null);
- }
- }
- 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);
- }
- /** @return <b>true</b> - if image was downloaded successfully; <b>false</b> - otherwise */
- 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;
- }
- private boolean downloadImage() throws IOException {
- InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
- return configuration.diskCache.save(uri, is, this);
- }
第16-17行,獲取ImageLoaderConfiguration是否設置保存在文件系統中的圖片大小,如果設置了maxImageWidthForDiskCache和maxImageHeightForDiskCache,會調用resizeAndSaveImage()方法對圖片進行裁剪然後在替換之前的原圖,保存裁剪後的圖片到文件系統的,之前有同學問過我說這個框架保存在文件系統的圖片都是原圖,怎麼才能保存縮略圖,只要在Application中實例化ImageLoaderConfiguration的時候設置maxImageWidthForDiskCache和maxImageHeightForDiskCache就行了
- 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);
- }
- DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
- runTask(displayBitmapTask, syncLoading, handler, engine);
- @Override
- public void run() {
- 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.display(bitmap, imageAware, loadedFrom);
- engine.cancelDisplayTaskFor(imageAware);
- listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
- }
- }
假如ImageView被回收了或者被重用了,回調給ImageLoadingListener接口,否則就調用BitmapDisplayer去顯示Bitmap
文章寫到這裏就已經寫完了,不知道大家對這個開源框架有沒有進一步的理解,這個開源框架設計也很靈活,用了很多的設計模式,比如建造者模式,裝飾模式,代理模式,策略模式等等,這樣方便我們去擴展,實現我們想要的功能,今天的講解就到這了,有對這個框架不明白的地方可以在下面留言,我會盡量爲大家解答的。