Android圖形緩存庫picasso解析

picasso是Square公司開源的一個Android圖形緩存庫,地址https://square.github.io/picasso/,可以實現圖片下載和緩存功能。

picasso使用簡單,如下

Picasso.with(context).load("https://i.imgur.com/DvpvklR.png").into(imageView);

主要有以下一些特性:

在adapter中回收和取消當前的下載;使用最少的內存完成復雜的圖形轉換操作;自動的內存和硬盤緩存;圖形轉換操作,如變換大小,旋轉等,提供瞭接口來讓用戶可以自定義轉換操作;加載載網絡或本地資源;

代碼分析

Cache,緩存類

Lrucacha,主要是get和set方法,存儲的結構采用瞭LinkedHashMap,這種map內部實現瞭lru算法(Least Recently Used 近期最少使用算法)。

this.map = new LinkedHashMap(0, 0.75f, true);

最後一個參數的解釋:

true if the ordering should be done based on the last access (from least-recently accessed to most-recently accessed), and false if the ordering should be the order in which the entries were inserted.

因為可能會涉及多線程,所以在存取的時候都會加鎖。而且每次set操作後都會判斷當前緩存區是否已滿,如果滿瞭就清掉最少使用的圖形。代碼如下

private void trimToSize(int maxSize) {
		while (true) {
			String key;
			Bitmap value;
			synchronized (this) {
				if (size < 0 || (map.isEmpty() && size != 0)) {
					throw new IllegalStateException(getClass().getName()
							+ ".sizeOf() is reporting inconsistent results!");
				}

				if (size <= maxSize || map.isEmpty()) {
					break;
				}

				Map.Entry toEvict = map.entrySet().iterator()
						.next();
				key = toEvict.getKey();
				value = toEvict.getValue();
				map.remove(key);
				size -= Utils.getBitmapBytes(value);
				evictionCount++;
			}
		}
}

Request,操作封裝類

所有對圖形的操作都會記錄在這裡,供之後圖形的創建使用,如重新計算大小,旋轉角度,也可以自定義變換,隻需要實現Transformation,一個bitmap轉換的接口。

public interface Transformation {
  /**
   * Transform the source bitmap into a new bitmap. If you create a new bitmap instance, you must
   * call {@link android.graphics.Bitmap#recycle()} on {@code source}. You may return the original
   * if no transformation is required.
   */
  Bitmap transform(Bitmap source);

  /**
   * Returns a unique key for the transformation, used for caching purposes. If the transformation
   * has parameters (e.g. size, scale factor, etc) then these should be part of the key.
   */
  String key();
}

當操作封裝好以後,會將Request傳到另一個結構中Action。

Action

Action代表瞭一個具體的加載任務,主要用於圖片加載後的結果回調,有兩個抽象方法,complete和error,也就是當圖片解析為bitmap後用戶希望做什麼。最簡單的就是將bitmap設置給imageview,失敗瞭就將錯誤通過回調通知到上層。

ImageViewAction實現瞭Action,在complete中將bitmap和imageview組成瞭一個PicassoDrawable,裡面會實現淡出的動畫效果。

@Override
	public void complete(Bitmap result, Picasso.LoadedFrom from) {
		if (result == null) {
			throw new AssertionError(String.format(
					"Attempted to complete action with no result!\n%s", this));
		}

		ImageView target = this.target.get();
		if (target == null) {
			return;
		}

		Context context = picasso.context;
		boolean debugging = picasso.debugging;
		PicassoDrawable.setBitmap(target, context, result, from, noFade,
				debugging);

		if (callback != null) {
			callback.onSuccess();
		}
	}

有瞭加載任務,具體的圖片下載與解析是在哪裡呢?這些都是耗時的操作,應該放在異步線程中進行,就是下面的BitmapHunter。

BitmapHunter

BitmapHunter是一個Runnable,其中有一個decode的抽象方法,用於子類實現不同類型資源的解析。

@Override
	public void run() {
		try {
			Thread.currentThread()
					.setName(Utils.THREAD_PREFIX + data.getName());

			result = hunt();

			if (result == null) {
				dispatcher.dispatchFailed(this);
			} else {
				dispatcher.dispatchComplete(this);
			}
		} catch (IOException e) {
			exception = e;
			dispatcher.dispatchRetry(this);
		} catch (Exception e) {
			exception = e;
			dispatcher.dispatchFailed(this);
		} finally {
			Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
		}
	}

	abstract Bitmap decode(Request data) throws IOException;

	Bitmap hunt() throws IOException {
		Bitmap bitmap;

		if (!skipMemoryCache) {
			bitmap = cache.get(key);
			if (bitmap != null) {
				stats.dispatchCacheHit();
				loadedFrom = MEMORY;
				return bitmap;
			}
		}

		bitmap = decode(data);

		if (bitmap != null) {
			stats.dispatchBitmapDecoded(bitmap);
			if (data.needsTransformation() || exifRotation != 0) {
				synchronized (DECODE_LOCK) {
					if (data.needsMatrixTransform() || exifRotation != 0) {
						bitmap = transformResult(data, bitmap, exifRotation);
					}
					if (data.hasCustomTransformations()) {
						bitmap = applyCustomTransformations(
								data.transformations, bitmap);
					}
				}
				stats.dispatchBitmapTransformed(bitmap);
			}
		}

		return bitmap;
	}

可以看到,在decode生成原始bitmap,之後會做需要的轉換transformResult和applyCustomTransformations。最後在將最終的結果傳遞到上層dispatcher.dispatchComplete(this)。

基本的組成元素有瞭,那這一切是怎麼連接起來運行呢,答案是Dispatcher。

Dispatcher任務調度器

在bitmaphunter成功得到bitmap後,就是通過dispatcher將結果傳遞出去的,當然讓bitmaphunter執行也要通過Dispatcher。


Dispatcher內有一個HandlerThread,所有的請求都會通過這個thread轉換,也就是請求也是異步的,這樣應該是為瞭Ui線程更加流暢,同時保證請求的順序,因為handler的消息隊列。
外部調用的是dispatchXXX方法,然後通過handler將請求轉換到對應的performXXX方法。
例如生成Action以後就會調用dispather的dispatchSubmit()來請求執行,

void dispatchSubmit(Action action) {
		handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
	}

handler接到消息後轉換到performSubmit方法

void performSubmit(Action action) {
		BitmapHunter hunter = hunterMap.get(action.getKey());
		if (hunter != null) {
			hunter.attach(action);
			return;
		}

		if (service.isShutdown()) {
			return;
		}

		hunter = forRequest(context, action.getPicasso(), this, cache, stats,
				action, downloader);
		hunter.future = service.submit(hunter);
		hunterMap.put(action.getKey(), hunter);
	}

這裡將通過action得到具體的BitmapHunder,然後交給ExecutorService執行。

下面是Picasso.with(context).load(“https://i.imgur.com/DvpvklR.png”).into(imageView)的過程,

public static Picasso with(Context context) {
		if (singleton == null) {
			singleton = new Builder(context).build();
		}
		return singleton;
	}
	
	public Picasso build() {
			Context context = this.context;

			if (downloader == null) {
				downloader = Utils.createDefaultDownloader(context);
			}
			if (cache == null) {
				cache = new LruCache(context);
			}
			if (service == null) {
				service = new PicassoExecutorService();
			}
			if (transformer == null) {
				transformer = RequestTransformer.IDENTITY;
			}

			Stats stats = new Stats(cache);

			Dispatcher dispatcher = new Dispatcher(context, service, HANDLER,
					downloader, cache, stats);

			return new Picasso(context, dispatcher, cache, listener,
					transformer, stats, debugging);
		}

在Picasso.with()的時候會將執行所需的所有必備元素創建出來,如緩存cache、執行executorService、調度dispatch等,在load()時創建Request,在into()中創建action、bitmapHunter,並最終交給dispatcher執行。

發佈留言