1. 程式人生 > >簡談原始碼-Picasso原始碼(v_2.71828)

簡談原始碼-Picasso原始碼(v_2.71828)

Picasso官網
Picasso

Picasso.get().load(url).into(iv);

Picasso的常見使用步驟很簡單,下面我們概要的看看其實現。

1. 例項獲取get()

/* Picasso.class */
@SuppressLint("StaticFieldLeak") static volatile Picasso singleton = null;

public static Picasso get() {
    if (singleton == null) {
        synchronized (Picasso.class) {
            if
(singleton == null) { if (PicassoProvider.context == null) { throw new IllegalStateException("context == null"); } singleton = new Builder(PicassoProvider.context).build(); } } } return singleton; }

使用double checked locking單例模式

建立Picasso例項,建立過程中使用建造者Builder。

1.1 context獲取

通過ContentProvider,在AndroidManifest 中註冊PicassoProvider,app啟動後呼叫onCreate()即可獲取所需context例項。

public final class PicassoProvider extends ContentProvider {

    @SuppressLint("StaticFieldLeak") static Context context;

    @Override public boolean onCreate
() { context = getContext(); return true; } }

1.2 Picasso建立

使用Builder Pattern建造者模式,將一個複雜的構建與其表示相分離,使得同樣的構建過程可以建立不同的表示,簡單點說就是用多個簡單的物件一步一步構建成一個複雜的物件。

/* Picasso.class */
public Builder(@NonNull Context context) {
    if (context == null) {
        throw new IllegalArgumentException("Context must not be null.");
    }
    this.context = context.getApplicationContext();
}

具體建造過程如下。

/* Picasso.class */
public Picasso build() {
    Context context = this.context;

    // 配置下載器,下載網路圖片資源(預設使用OkHttp3Downloader)
    if (downloader == null) {
        downloader = new OkHttp3Downloader(context);
    }

    // 配置記憶體快取,提高圖片載入效率(預設使用LruCache[Least Recently Used])
    // 記憶體快取大小為RAM的15%;磁碟快取大小為ROM的2%且範圍為5~50mb
    if (cache == null) {
        cache = new LruCache(context);
    }

    // 配置執行緒池
    // 預設3個執行緒;使用wifi為4個,4G為3個,3G為2個,2G為1個
    if (service == null) {
        service = new PicassoExecutorService();
    }

    // 配置請求轉換器(預設不做處理,直接返回原請求)
    if (transformer == null) {
        transformer = RequestTransformer.IDENTITY;
    }

    // 統計:統計快取命中數量、圖片下載數量等
    Stats stats = new Stats(cache);

    // 分發器:由HANDLER分發圖片請求、請求完成等等事件
    Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

    return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
            defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}

至此,Picasso.get()過程完成。

2. 獲取請求load(url)

即通過請求建立器RequestCreator建立請求Request,通過Request可以執行placeholder(placeholderResId)方法設定佔位圖,error(errorResId)方法設定錯誤圖,noFade()方法取消圖片漸變效果等操作。

/* Picasso.class */
public RequestCreator load(@Nullable String path) {
    ...
    return load(Uri.parse(path));
}

public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
}

RequestCreator的構造方法如下,核心是通過建造者模式建造Request例項。

通過picasso可以發起請求、推遲(defer)請求、取消請求、檢查快取配置等操作;通過建立的Request例項data可以呼叫centerCrop()、centerInside()等方法。

boolean shutdown;
private final Picasso picasso;
private final Request.Builder data;

RequestCreator(Picasso picasso, Uri uri, int resourceId) {
    if (picasso.shutdown) {
        throw new IllegalStateException("Picasso instance already shut down. Cannot submit new requests.");
    }
    this.picasso = picasso;
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}

3. 顯示into(iv)

核心邏輯為建立Action,即一次圖片請求的活動過程,然後分發該action。

/* RequestCreator.class */
/**
 * 非同步請求圖片資源至使用者指定的ImageView
 * 使用弱引用關聯ImageView,資源將會被自動回收
 */
public void into(ImageView target) {
    into(target, null);
}

/**
 * 若使用者指定Callback,為了防止Activity/Fragment回收,會是一個強引用;推薦及時呼叫cancelRequest()方法防止記憶體洩露
 */
public void into(ImageView target, Callback callback) {
    // 記錄開始時間,並檢查執行緒是否安全
    long started = System.nanoTime();
    checkMain();

    if (target == null) {
        throw new IllegalArgumentException("Target must not be null.");
    }

    // 若Request例項為空,取消當前請求,設定佔位圖
    if (!data.hasImage()) {
        picasso.cancelRequest(target);
        if (setPlaceholder) {
            setPlaceholder(target, getPlaceholderDrawable());
        }
        return;
    }

    // deferred預設是false的
    if (deferred) {
        if (data.hasSize()) {
            throw new IllegalStateException("Fit cannot be used with resize.");
        }
        int width = target.getWidth();
        int height = target.getHeight();
        if (width == 0 || height == 0) {
            if (setPlaceholder) {
                setPlaceholder(target, getPlaceholderDrawable());
            }
            // 若獲取不到目標ImageView的寬高,就延遲請求,直到獲取寬高時再次呼叫into()方法
            picasso.defer(target, new DeferredRequestCreator(this, target, callback));
            return;
        }
        data.resize(width, height);
    }

    // 建立Request例項並標記
    Request request = createRequest(started);
    String requestKey = createKey(request);

    // 控制從快取中讀取,若命中requestKey對應的Bitmap則取消當前請求,並顯示圖片
    if (shouldReadFromMemoryCache(memoryPolicy)) {
        Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
        if (bitmap != null) {
            picasso.cancelRequest(target);
            setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
            if (picasso.loggingEnabled) {
                log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
            }
            if (callback != null) {
                callback.onSuccess();
            }
            return;
        }
    }

    if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
    }

    // 記憶體快取未命中,則建立Action例項,並提交至picasso
    Action action =
            new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
                    errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
}

3.1 action的建立

上述程式碼中用到的關鍵活動類ImageViewAction就是繼承自Action,並重寫了Action中的方法complete()、error()、cancel(),具體實現和方法註釋說明如下。

class ImageViewAction extends Action<ImageView> {

    Callback callback;

    ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
                    int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
                    Callback callback, boolean noFade) {
        super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,
                tag, noFade);
        this.callback = callback;
    }

    // 圖片請求成功,使用PicassoDrawable將圖片顯示到指定的ImageView控制元件中
    @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 indicatorsEnabled = picasso.indicatorsEnabled;
        PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

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

    // 圖片請求失敗,判斷是否顯示佔位圖
    @Override public void error(Exception e) {
        ImageView target = this.target.get();
        if (target == null) {
            return;
        }
        Drawable placeholder = target.getDrawable();
        if (placeholder instanceof Animatable) {
            ((Animatable) placeholder).stop();
        }
        if (errorResId != 0) {
            target.setImageResource(errorResId);
        } else if (errorDrawable != null) {
            target.setImageDrawable(errorDrawable);
        }

        if (callback != null) {
            callback.onError(e);
        }
    }

    // 取消本次請求
    @Override void cancel() {
        super.cancel();
        if (callback != null) {
            callback = null;
        }
    }
}

3.2 action的提交

picasso提交action,然後通過分發器dispatcher分發訊息。

/* Picasso.class */
void enqueueAndSubmit(Action action) {
    ...
    submit(action);
}

void submit(Action action) {
    dispatcher.dispatchSubmit(action);
}

3.3 action的分發

dispatcher通過Handler傳送action,交由圖片捕獲器BitmapHunter處理。

/* Dispatcher.class */
void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

@Override public void handleMessage(final Message msg) {
    switch (msg.what) {
        case REQUEST_SUBMIT: {
            Action action = (Action) msg.obj;
            dispatcher.performSubmit(action);
            break;
        ...
        }
    }
}

void performSubmit(Action action) {
    performSubmit(action, true);
}

void performSubmit(Action action, boolean dismissFailed) {
    ...
    // 剩下的工作將由BitmapHunter完成
    BitmapHunter hunter = BitmapHunter.forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);
    ...
}

3.3.1 BitmapHunter.forRequest()獲取圖片捕獲器

/* BitmapHunter.class */
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
        RequestHandler requestHandler = requestHandlers.get(i);
        // 輪詢獲取能夠處理這次活動請求(action's request)的請求處理器
        if (requestHandler.canHandleRequest(request)) {
            return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
        }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

其中所有的請求處理器requestHandlers是在Picasso構造時初始化的,總共為7種。

Picasso(...) {
    ...
    allRequestHandlers.add(new ResourceRequestHandler(context));
    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
    allRequestHandlers.add(new MediaStoreRequestHandler(context));
    allRequestHandlers.add(new ContentStreamRequestHandler(context));
    allRequestHandlers.add(new AssetRequestHandler(context));
    allRequestHandlers.add(new FileRequestHandler(context));
    // 載入網路圖片的請求處理器
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
    ...
}

看看網路請求處理器中的匹配規則,即Uri中包含"http"或"https"。

/* NetworkRequestHandler.class */
@Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return ("http".equals(scheme) || "https".equals(scheme));
}

3.3.2 service.submit(hunter)開啟圖片捕獲器hunter執行緒,獲取bitmap

圖片捕獲器hunter是一個Runnable,hunter獲取後執行submit()方法開啟hunter執行緒。

/* ExecutorService.class */
public Future<?> submit(Runnable task) {
    ...
    execute(ftask);
    return ftask;
}

hunter執行緒中的核心操作就是獲取圖片資源,然後分發獲取結果

/* BitmapHunter.class */
@Override public void run() {

    Bitmap result = hunt();

    if (result == null) {
        dispatcher.dispatchFailed(this);
    } else {
        dispatcher.dispatchComplete(this);
    }
    ...
}

Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
    // 從快取中讀取圖片資源
    if (shouldReadFromMemoryCache(memoryPolicy)) {
        bitmap = cache.get(key);
        ...
        return bitmap;
    }

    // 快取未命中,使用請求處理器載入圖片資源
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
        ...
        bitmap = result.getBitmap();
        ...
    }
    ...
    return bitmap;
}

本篇以請求網路圖片為背景展開介紹,load()方法中使用下載器downloader來下載圖片,當前Picasso版本中的網路下載器貌似只有OkHttp3Downloader,沒有UrlConnectionDownloader了。

網路請求處理器NetworkRequestHandler中的load()方法如下,即使用下載器下載圖片資源。

/* NetworkRequestHandler.class */
@Override public Result load(Request request, int networkPolicy) throws IOException {
    okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
    Response response = downloader.load(downloaderRequest);
    ResponseBody body = response.body();
    ...
    return new Result(body.source(), loadedFrom);
}

網路下載器OkHttp3Downloader中的load()方法如下,請求完成即可獲取圖片資源。

/* OkHttp3Downloader.class */
@NonNull @Override public Response load(@NonNull Request request) throws IOException {