源碼解析 Universal Image Loader

在大多數的項目中我們都會用到從網絡加載圖片顯示的功能,在Google發佈的Volley框架中也提供了類似的功能,但是個人還是比較習慣Universal Image Loader。在今天的文章中從源碼角度簡單的分析一下Universal Image Loader的實現流程。

在這裏就不去介紹Universal Image Loader簡單的用法了,如果還沒有用過的朋友可以直接看一下該框架中自帶的Demo,沒有太多複雜的地方。

在使用UIL的時候(Universal Image Loader下文中都將寫爲UIL),我們需要先在Application中配置一下,以下的內容是自帶的Demo中的配置信息:

ImageLoaderConfiguration.Builder config = new ImageLoaderConfiguration.Builder(context);
		config.threadPriority(Thread.NORM_PRIORITY - 2);//在這裏設置了正常的配置線程  爲3個線程共同工作
		config.denyCacheImageMultipleSizesInMemory();
		config.diskCacheFileNameGenerator(new Md5FileNameGenerator());//設置緩存文件的名字爲MD5寫入到文件中
		config.diskCacheSize(50 * 1024 * 1024); // 50 MiB
		config.tasksProcessingOrder(QueueProcessingType.LIFO);
		config.writeDebugLogs(); // Remove for release app   
		// Initialize ImageLoader with configuration.
		
		ImageLoader.getInstance().init(config.build());

我們最開始接觸的就是ImageLoaderConfiguration類,就先從它開始看起。可以看到這個類非常的長,但是仔細觀察會發現主題的代碼卻沒有多少:

public final class ImageLoaderConfiguration {
        // 在獲取圖片最大尺寸時會用到
	final Resources resources;
	// 內存緩存圖片的最大寬度。
	final int maxImageWidthForMemoryCache;
	// 內存緩存圖片的最大高度。
	final int maxImageHeightForMemoryCache;
	// 磁盤緩存圖片的最大寬度。
	final int maxImageWidthForDiskCache;
	// 磁盤緩存圖片的最大高度。
	final int maxImageHeightForDiskCache;
	// 處理磁盤緩存圖片的處理器
	final BitmapProcessor processorForDiskCache;

	// 執行從網絡(或者其他地方)獲取圖片任務的線程池
	final Executor taskExecutor;
	// 執行從緩存中獲取圖片任務的線程池
	final Executor taskExecutorForCachedImages;
	// 用戶是否自定義了taskExecutor。
	final boolean customExecutor;
	// 用戶是否自定義了taskExecutorForCachedImages。
	final boolean customExecutorForCachedImages;
	// 上述線程池的核心線程數
	final int threadPoolSize;
	// 線程池中線程的優先級
	final int threadPriority;
	// 線程池中隊列的類型
	final QueueProcessingType tasksProcessingType;
	// 內存緩存接口。
	final MemoryCache memoryCache;
	// 磁盤緩存接口
	final DiskCache diskCache;
	// 圖片下載器
	final ImageDownloader downloader;
	// 圖片解碼器
	final ImageDecoder decoder;
	// 顯示圖片的配置
	final DisplayImageOptions defaultDisplayImageOptions;
	// 不允許訪問網絡的圖片下載器
	final ImageDownloader networkDeniedDownloader;
	// 慢網絡情況下的圖片下載器。
  	final ImageDownloader slowNetworkDownloader;

 	private ImageLoaderConfiguration(final Builder builder) {
 		resources = builder.context.getResources();
 		maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache;
		maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache;
 		maxImageWidthForDiskCache = builder.maxImageWidthForDiskCache;
 		maxImageHeightForDiskCache = builder.maxImageHeightForDiskCache;
 		processorForDiskCache = builder.processorForDiskCache;
		taskExecutor = builder.taskExecutor;
 		taskExecutorForCachedImages = builder.taskExecutorForCachedImages;
 		threadPoolSize = builder.threadPoolSize;
 		threadPriority = builder.threadPriority;
 		tasksProcessingType = builder.tasksProcessingType;
                diskCache = builder.diskCache;
		memoryCache = builder.memoryCache;
 		defaultDisplayImageOptions = builder.defaultDisplayImageOptions;
 		downloader = builder.downloader;
		decoder = builder.decoder;
 
		customExecutor = builder.customExecutor;
		customExecutorForCachedImages = builder.customExecutorForCachedImages;

		networkDeniedDownloader = new NetworkDeniedImageDownloader(downloader);
		slowNetworkDownloader = new SlowNetworkImageDownloader(downloader);

		L.writeDebugLogs(builder.writeLogs);
	}


	public static ImageLoaderConfiguration createDefault(Context context) {
		return new Builder(context).build();
	}

	ImageSize getMaxImageSize() {
		DisplayMetrics displayMetrics = resources.getDisplayMetrics();

		int width = maxImageWidthForMemoryCache;
		if (width <= 0) {
			width = displayMetrics.widthPixels;
		}
 		int height = maxImageHeightForMemoryCache;
		if (height <= 0) {
			height = displayMetrics.heightPixels;
		}
		return new ImageSize(width, height);
	}
}

在上面代碼的註釋中已經說明了各個屬性的作用,裏面涉及到的類在接下來的分析中如果有涉及將會解釋。在這個類的構造方法中使用了構造者模式,在參數較多的情況下保證了良好的可讀性。其內部類Builder(也就是構造者)中的屬性和ImageLoaderConfiguration中大致相同,其採用了鏈表的形式,即在保存了一個傳遞參數後返回自身的引用,如下所示:

                public Builder imageDownloader(ImageDownloader imageDownloader) {
			this.downloader = imageDownloader;
			return this;
		}

		
		public Builder imageDecoder(ImageDecoder imageDecoder) {
			this.decoder = imageDecoder;
			return this;
		}

在這裏只列舉出兩個舉例。我們可以使用.的方式來進行構建,如下所示:

config.threadPriority(Thread.NORM_PRIORITY - 2).
		denyCacheImageMultipleSizesInMemory().
		diskCacheFileNameGenerator(new Md5FileNameGenerator()).
		diskCacheSize(50 * 1024 * 1024).
		build();

其中重點看一下build方法:

public ImageLoaderConfiguration build() {
			initEmptyFieldsWithDefaultValues();
			return new ImageLoaderConfiguration(this);
		}

再看一下initEmptyFieldsWithDefaultValues方法:

private void initEmptyFieldsWithDefaultValues() {
			if (taskExecutor == null) {
				taskExecutor = DefaultConfigurationFactory.createExecutor(
						threadPoolSize, threadPriority, tasksProcessingType);
			} else {
				customExecutor = true;
			}
			if (taskExecutorForCachedImages == null) {
				taskExecutorForCachedImages = DefaultConfigurationFactory
						.createExecutor(threadPoolSize, threadPriority,
								tasksProcessingType);
			} else {
				customExecutorForCachedImages = true;
			}
			if (diskCache == null) {
				if (diskCacheFileNameGenerator == null) {
					diskCacheFileNameGenerator = DefaultConfigurationFactory
							.createFileNameGenerator();
				}
				diskCache = DefaultConfigurationFactory.createDiskCache(
						context, diskCacheFileNameGenerator, diskCacheSize,
						diskCacheFileCount);
			}
			if (memoryCache == null) {
				memoryCache = DefaultConfigurationFactory.createMemoryCache(
						context, memoryCacheSize);
			}
			if (denyCacheImageMultipleSizesInMemory) {
				memoryCache = new FuzzyKeyMemoryCache(memoryCache,
						MemoryCacheUtils.createFuzzyKeyComparator());
			}
			if (downloader == null) {
				downloader = DefaultConfigurationFactory
						.createImageDownloader(context);
			}
			if (decoder == null) {
				decoder = DefaultConfigurationFactory
						.createImageDecoder(writeLogs);
			}
			if (defaultDisplayImageOptions == null) {
				defaultDisplayImageOptions = DisplayImageOptions.createSimple();
			}
		}

在其中給一些用戶沒有手動設置的屬性進行了初始化,然後在build方法中返回了一個ImageLoaderConfiguration類的實例。

到這裏爲止我們就將ImageLoaderConfiguration這個類簡單的看了一遍,如果有興趣可以深入的去讀一下代碼。

由於在Application中寫了這樣一行代碼:ImageLoader.getInstance().init(config.build());  我們來看一下整個框架中最主要的一個類ImageLoader。

首先是獲取實例的方法,採用了單例模式來實現。

private volatile static ImageLoader instance;

	/** Returns singleton class instance */
	public static ImageLoader getInstance() {
		if (instance == null) {
			synchronized (ImageLoader.class) {
				if (instance == null) {
					instance = new ImageLoader();
				}
			}
		}
		return instance;
	}

使用了雙重檢查來提高性能,並且這種寫法需要將實例對象volatile保證可見性。

而後是init方法:

public synchronized void init(ImageLoaderConfiguration configuration) {
		if (configuration == null) {
			throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
		}
		if (this.configuration == null) {
			L.d(LOG_INIT_CONFIG);//"Initialize ImageLoader with configuration"
			engine = new ImageLoaderEngine(configuration);
			this.configuration = configuration;
		} else {//"Try to initialize ImageLoader which had already been initialized before. " + "To re-init ImageLoader with new confi
//			guration call ImageLoader.destroy() at first."
			L.w(WARNING_RE_INIT_CONFIG);
		}
	}

簡單的保存了一下ImageLoaderConfiguration,然後用它創建出了一個叫engin的對象,定義如下:

private ImageLoaderEngine engine;

它的作用是一個分發器,將任務分發到線程池中來執行,在後面會詳細的對它進行分析。

這樣我們的配置工作就結束了,但是光有配置也沒用,UIL給我們提供了幾種加載圖片的方法:

public void loadImage(String uri, ImageLoadingListener listener) {
		loadImage(uri, null, null, listener, null);
}
public void loadImage(String uri, ImageSize targetImageSize, ImageLoadingListener listener) {
		loadImage(uri, targetImageSize, null, listener, null);
}
public void loadImage(String uri, DisplayImageOptions options, ImageLoadingListener listener) {
		loadImage(uri, null, options, listener, null);
}
public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
			ImageLoadingListener listener) {
		loadImage(uri, targetImageSize, options, listener, null);
}




public void displayImage(String uri, ImageAware imageAware) {
		displayImage(uri, imageAware, null, null, null);
}
public void displayImage(String uri, ImageAware imageAware, ImageLoadingListener listener) {
		displayImage(uri, imageAware, null, listener, null);
}
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options) {
		displayImage(uri, imageAware, options, null, null);
}
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
			ImageLoadingListener listener) {
		displayImage(uri, imageAware, options, listener, null);
}
public void displayImage(String uri, ImageView imageView) {
		displayImage(uri, new ImageViewAware(imageView), null, null, null);
}
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
		displayImage(uri, new ImageViewAware(imageView), options, null, null);
}
public void displayImage(String uri, ImageView imageView, ImageLoadingListener listener) {
		displayImage(uri, new ImageViewAware(imageView), null, listener, null);
}
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options,
			ImageLoadingListener listener) {
		displayImage(uri, imageView, options, listener, null);
}
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options,
			ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
		displayImage(uri, new ImageViewAware(imageView), options, listener, progressListener);
}



public Bitmap loadImageSync(String uri) {
	return loadImageSync(uri, null, null);
}
public Bitmap loadImageSync(String uri, DisplayImageOptions options) {
	return loadImageSync(uri, null, options);
}
public Bitmap loadImageSync(String uri, ImageSize targetImageSize) {
	return loadImageSync(uri, targetImageSize, null);
}

一共有三種加載圖片的方法,看名字也大概可以猜出含義,第一種是簡單的加載圖片,第二種是顯示圖片,第三種是同步加載圖片。

先來看第一種最終調用的方法:

public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
			ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
		checkConfiguration();
		//如果當前的目標大小用戶沒有傳入的話     直接利用configuration裏面默認的最大的ImageSize信息
		if (targetImageSize == null) {
			targetImageSize = configuration.getMaxImageSize();
		}
		if (options == null) {
			options = configuration.defaultDisplayImageOptions;
		}

		NonViewAware imageAware = new NonViewAware(uri, targetImageSize, ViewScaleType.CROP);
		displayImage(uri, imageAware, options, listener, progressListener);
	}

可以看到這個方法就簡單的檢查了一下configuration後創建出了一個ViewAware包裝類(後面會有介紹)後調用了displayImage的一個重載方法。

再來看一下第三種最終調用的方法:

public Bitmap loadImageSync(String uri, ImageSize targetImageSize, DisplayImageOptions options) {
		if (options == null) {
			options = configuration.defaultDisplayImageOptions;
		}
		options = new DisplayImageOptions.Builder().cloneFrom(options).syncLoading(true).build();

		SyncImageLoadingListener listener = new SyncImageLoadingListener();
		loadImage(uri, targetImageSize, options, listener);
		return listener.getLoadedBitmap();
	}

在這個方法中最終調用的是loadImage的一個重載方法,那麼它最終也會調用到displayImage方法。不同的就是由於這個方法是同步的,所以它可以直接返回加載出來的Bitmap。

那麼現在來看一下第二種最終調用的方法,所有的方法也都會調用到它身上:

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
			ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
		//檢查configuration是否爲空,如果爲空,拋異常
		checkConfiguration();
		//當圖片展示的包裝類爲空的時候拋異常,這也就是爲什麼loadImage的時候仍然需要傳入一個包裝類
		if (imageAware == null) {
			throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
		}
		//加載中監聽器爲空創建一個默認的
		if (listener == null) {
			listener = defaultListener;
		}
		//展示圖片的配置選項爲空時創建一個默認的
		if (options == null) {
			options = configuration.defaultDisplayImageOptions;
		}

		//uri爲空時取消ImageLoaderEngine對任務的分發,而後判斷是否有Uri爲空時的默認顯示圖片,若有則加載
		//加載完成後直接返回
		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;
		}

		//根據要顯示圖片的控件和最大的圖片大小來生成一個圖片的尺寸
		ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
		//以當前Uri和尺寸爲關鍵字來生成緩存的key
		String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
		//ImageLoaderEngine完成加載前的準備工作,主要是將緩存key和加載圖片控件的ID關聯起來放到一個map中
		engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

		//回調listener中的方法
		listener.onLoadingStarted(uri, imageAware.getWrappedView());

		//從configuration保存的緩存實例對象中使用當前的緩存key嘗試着拿一下緩存
		Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
		
		//當這個緩存的Bitmap存在並且還沒有被回收
		if (bmp != null && !bmp.isRecycled()) {
			L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);

			//如果當前的圖片展示選項中需要對圖片進行後續處理
			if (options.shouldPostProcess()) {
				//封裝一個ImageLoadingInfo對象
				ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
						options, listener, progressListener, engine.getLockForUri(uri));
				//創建出處理並且展示圖片的任務,本質是一個Runnable
				ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
						defineHandler(options));
				//在同步的情況下直接運行
				if (options.isSyncLoading()) {
					displayTask.run();
				} else {//在異步的情況下使用ImageLoaderEngine將其分發到線程池中去執行
					engine.submit(displayTask);
				}
			} else {//不需要對圖片進行後續處理
				
				//直接對圖片進行展示
				options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
				//回調監聽
				listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
			}
		} else {//當前緩存的Bitmap不存在或者已經被回收,需要重新從磁盤或者網絡加載

			//看一下是否需要展示一個代表加載中的圖片
			if (options.shouldShowImageOnLoading()) {
				imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
			} else if (options.isResetViewBeforeLoading()) {
				imageAware.setImageDrawable(null);
			}
			
			//使用一個ImageLoadingInfo對象包裝加載信息
			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到底是個什麼,可以看到它是個接口,以下是接口的定義:

public interface ImageAware {
	int getWidth();
	int getHeight();
	ViewScaleType getScaleType();
	View getWrappedView();
	boolean isCollected();
	int getId();
	boolean setImageDrawable(Drawable drawable);
	boolean setImageBitmap(Bitmap bitmap);
<span style="font-size:18px;">}</span>

在這裏因爲篇幅的原因把註釋都刪掉了,大致的意思就是包裝了一下顯示圖片控件的寬高,圖片的縮放模式,控件的ID,展示的圖片啊這些的內容,如果有興趣也可以讀一下注釋,寫的非常清楚。在正常情況下我們都使用ImageView來實現圖片的展示,那麼ImageView是如何轉換爲ImageAware的呢,可以隨便拿一個displayImage方法來看一下:

public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
		displayImage(uri, new ImageViewAware(imageView), options, null, null);
	}

使用了一個ImageViewAware來包裝了ImageView。這裏就不再深入代碼細節了,但是有一點需要注意,就是UIL的這種模式是對擴展開放的,如果我們有一個自定義的控件,只需要爲它寫一個適配器(Adapter模式)ImageAware就可以去使用了,非常的靈活!

再來看一下剛分析的displayImage方法中的參數DisplayImageOptions,從名字上可以看出來它是展示圖片的配置信息,同樣使用構造者模式來進行組裝,如果有興趣可以看一下代碼,屬性的名字也非常直觀,這裏就不一一列舉說明了。

還有一個沒有用到的參數ImageLoadingProgressListener,看一下定義:

public interface ImageLoadingProgressListener {
	void onProgressUpdate(String imageUri, View view, int current, int total);
}

可以看到它回調了圖片加載的進度。

下面接着說displayImage方法的主邏輯,該方法中有多處使用了ImageLoaderEngine類的對象,顯然要了解整個方法,我們必須將這個類看一下,首先是構造方法:

ImageLoaderEngine(ImageLoaderConfiguration configuration) {
		this.configuration = configuration;

		taskExecutor = configuration.taskExecutor;
		taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;

		taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
	}

前幾句只是簡單的將configuration的值賦給了ImageLoaderEngine中的屬性,這個就不再說了,如果忘記了是什麼意思可以看一下前面。最後一行創建出了一個分發任務的線程池。

在這個類中還有一些屬性是很重要的,來看一下:

        //key爲ImageAware的id,value爲該控件顯示的圖片的Uri
	private final Map<Integer, String> cacheKeysForImageAwares = Collections
			.synchronizedMap(new HashMap<Integer, String>());
        //圖片正在加載的重入鎖 map,key 爲圖片的 uri,value 爲標識其正在加載的重入鎖。
	private final Map<String, ReentrantLock> uriLocks = new WeakHashMap<String, ReentrantLock>();
        //當前加載圖片是否被暫停。爲true則所有加載圖片的事件都會暫停。
	private final AtomicBoolean paused = new AtomicBoolean(false);
        //是否禁止網絡訪問,爲true則網絡訪問失效
	private final AtomicBoolean networkDenied = new AtomicBoolean(false);
        //當前是否爲慢網絡情況,如果爲true則調用SlowNetworkImageDownloader加載圖片
	private final AtomicBoolean slowNetwork = new AtomicBoolean(false);
        //在engine被暫停後調用這個鎖等待。
	private final Object pauseLock = new Object();

類中的方法比較簡單,在這裏只看一下我們用到的,如果有興趣可以讀一下:

void submit(final LoadAndDisplayImageTask task) {
		taskDistributor.execute(new Runnable() {
			@Override
			public void run() {
				File image = configuration.diskCache.get(task.getLoadingUri());
				boolean isImageCachedOnDisk = image != null && image.exists();
				
				initExecutorsIfNeed();
				if (isImageCachedOnDisk) {
					taskExecutorForCachedImages.execute(task);
				} else {
					taskExecutor.execute(task);
				}
			}
		});
	}

如果還有印象的話可以知道這個方法的調用是在內存緩存不存在或者被回收的時候。首先查看一下當前磁盤上是否有這個Uri的緩存內容,然後調用initExecutorsIfNeed在線程池不存在的時候初始化一下線程池,如果當前在磁盤上有緩存的話,將其交給taskExecutorForCachedImages執行,如果沒有緩存需要從網絡拉取的話,交給taskExecutor來執行。

void submit(ProcessAndDisplayImageTask task) {
		
		initExecutorsIfNeed();
		taskExecutorForCachedImages.execute(task);
	}

調用這個方法的前提是在內存裏有對應的緩存並且沒有被回收,那麼直接交給taskExecutorForCachedImages來執行。

void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
		//將控件中所加載的URI傳入相應的map中來進行保存
		cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
	}

	void cancelDisplayTaskFor(ImageAware imageAware) {
		//取消對map中數據的保存
		cacheKeysForImageAwares.remove(imageAware.getId());
	}

這兩個方法也是剛纔用到的,分別爲準備分發任務和取消任務,對應的僅僅是將顯示圖片控件id爲key的實例從map中移除了。

在displayImage中我們還剩下三個類沒有去看,分別是ImageLoadingInfo,ProcessAndDisplayImageTask和LoadAndDisplayImageTask。先來看ImageLoadingInfo

final class ImageLoadingInfo {

	final String uri;
	final String memoryCacheKey;
	final ImageAware imageAware;
	final ImageSize targetSize;
	final DisplayImageOptions options;
	final ImageLoadingListener listener;
	final ImageLoadingProgressListener progressListener;
	final ReentrantLock loadFromUriLock;

	public ImageLoadingInfo(String uri, ImageAware imageAware, ImageSize targetSize, String memoryCacheKey,
			DisplayImageOptions options, ImageLoadingListener listener,
			ImageLoadingProgressListener progressListener, ReentrantLock loadFromUriLock) {
		this.uri = uri;
		this.imageAware = imageAware;
		this.targetSize = targetSize;
		this.options = options;
		this.listener = listener;
		this.progressListener = progressListener;
		this.loadFromUriLock = loadFromUriLock;
		this.memoryCacheKey = memoryCacheKey;
	}
}

這個類非常的簡單,包裝了要加載圖片的相關信息,並且將屬性設置爲final類型,保證我們不能去修改。這個類中屬性的含義前面都有提及,這裏就不多加贅述。

第二個是ProcessAndDisplayImageTask類,這是一個Runnable,來直接看一下run方法:

	@Override
	public void run() {
		L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);

		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);
	}

可以看到在經過後處理以後,調用了LoadAndDisplayImageTask類的runTask方法,由於在LoadAndDisplayImageTask的run方法中最後也將調用runTask,所以我們先來看一下LoadAndDisplayImageTask的run方法,然後一起來看runTask:

public void run() {
		
		if (waitIfPaused()) return;
		if (delayIfNeed()) return;
		
		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();

			//從內存中去讀取bitmap
			bmp = configuration.memoryCache.get(memoryCacheKey);
			//當bitmap爲空  或者已經被回收了的時候
			if (bmp == null || bmp.isRecycled()) {
				//嘗試着去加載bitmap
				bmp = tryLoadBitmap();
				if (bmp == null) return; // listener callback already was fired

				checkTaskNotActual();
				checkTaskInterrupted();

				//當需要對bitmap進行處理的時候進行處理
				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);
					//當需要在內存中進行緩存的話     直接將memoryKey放入到map中進行緩存
					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);
	}

方法中還是先判斷了一下在內存中是否存在bitmap實例,如果存在的話打一個標記,過一會在構建展示圖片任務的時候將會用到。如果不存在的話調用tryLoadBitmap方法去加載圖片,方法體如下:

private Bitmap tryLoadBitmap() throws TaskCancelledException {
		Bitmap bitmap = null;
		try {
			//從磁盤中獲取相應的uri
			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 = LoadedFrom.DISC_CACHE;

				checkTaskNotActual();
				//從磁盤的緩存中讀取相應的Bitmap文件
				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信息
				bitmap = decodeImage(imageUriForDecoding);
				//當bitmap爲空的時候標記爲失敗
				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方法下載並保存圖片,再跟進一下這個方法:

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一個方法,再看一下這個方法:

private boolean downloadImage() throws IOException {
		//調用getDownloader去下載圖片
		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);
			}
		}
	}

這裏調用了getDownloader得到與當前狀態相匹配的加載器去加載流文件。到這裏tryLoadBitmap大體上看完了。回頭繼續來走run中的方法邏輯,在加載了圖片後判斷一下是否需要對圖片進行預處理,如果需要則調用相應處理器處理。然後看一下圖片是否需要在內存緩存,如果需要將其放入代表內存緩存的map中。而後看一下圖片是否需要後處理,如果需要則處理。而後創建出了一個DisplayBitmapTask類的對象,並且調用了runTask方法,我們先來看一下runTask方法:

static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
		if (sync) {
			r.run();
		} else if (handler == null) {
			engine.fireCallback(r);
		} else {
			handler.post(r);
		}
	}

可以看到它的作用不管是提交也好,運行也好,目的都是讓Runnable去執行,那麼這個Runnable是什麼呢,沒錯就是剛剛創建出的DisplayBitmapTask類的對象。如果細心的話可能會發現在ProcessAndDisplayImageTask我們也創建了這個類的對象。既然這個類是一個Runnable,那我們就看一下它的run方法:

public void run() {
		//判斷當前的imageAware是否被GC回收   如果被回收   直接調用取消下載的接口
		if (imageAware.isCollected()) {
			L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
			listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
		} else if (isViewWasReused()) {//判斷imageAware是否被複用    如果被複用  取消下載接口
			L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
			listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
		} else {
			
			//調用displayer展示圖片
			L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
			displayer.display(bitmap, imageAware, loadedFrom);
			//將imageWare從正在下載的map中移除
			engine.cancelDisplayTaskFor(imageAware);
			//調用下載完成的回調
			listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
		}
	}

註釋已經寫出了這個方法的作用,我們主要關注的應該是它展示圖片的時候,所以這裏看一下display方法:

void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom);

它是接口中聲明的一個方法,我們找一下接口的實現類,這裏就看一下SimpleBitmapDisplayer

public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
		imageAware.setImageBitmap(bitmap);
	}

可以看到這裏簡單的給ImageAware設置了一個圖片。到這裏爲止run方法就已經結束了,我們也看到了圖片時怎麼從加載到展示的過程。這裏沒有去仔細分析Downloader和一些類的代碼細節,如果有興趣可以去深入一下代碼細節和設計上的細節~



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