天天看點

picasso圖檔緩存架構 代碼分析

picasso使用簡單,如下

picasso圖檔緩存架構 代碼分析

picasso.with(context).load("http://i.imgur.com/dvpvklr.png").into(imageview);  

主要有以下一些特性:

在adapter中回收和取消目前的下載下傳;

使用最少的記憶體完成複雜的圖形轉換操作;

自動的記憶體和硬碟緩存;

圖形轉換操作,如變換大小,旋轉等,提供了接口來讓使用者可以自定義轉換操作;

加載載網絡或本地資源;

picasso圖檔緩存架構 代碼分析
picasso圖檔緩存架構 代碼分析

lrucacha,主要是get和set方法,存儲的結構采用了linkedhashmap,這種map内部實作了lru算法(least recently used 近期最少使用算法)。

picasso圖檔緩存架構 代碼分析

this.map = new linkedhashmap<string, bitmap>(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操作後都會判斷目前緩存區是否已滿,如果滿了就清掉最少使用的圖形。代碼如下

picasso圖檔緩存架構 代碼分析

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<string, bitmap> toevict = map.entryset().iterator()  

                        .next();  

                key = toevict.getkey();  

                value = toevict.getvalue();  

                map.remove(key);  

                size -= utils.getbitmapbytes(value);  

                evictioncount++;  

            }  

        }  

}  

picasso圖檔緩存架構 代碼分析
picasso圖檔緩存架構 代碼分析

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

picasso圖檔緩存架構 代碼分析

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代表了一個具體的加載任務,主要用于圖檔加載後的結果回調,有兩個抽象方法,complete和error,也就是當圖檔解析為bitmap後使用者希望做什麼。最簡單的就是将bitmap設定給imageview,失敗了就将錯誤通過回調通知到上層。

picasso圖檔緩存架構 代碼分析
picasso圖檔緩存架構 代碼分析

imageviewaction實作了action,在complete中将bitmap和imageview組成了一個picassodrawable,裡面會實作淡出的動畫效果。

picasso圖檔緩存架構 代碼分析

@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。

picasso圖檔緩存架構 代碼分析

bitmaphunter是一個runnable,其中有一個decode的抽象方法,用于子類實作不同類型資源的解析。

picasso圖檔緩存架構 代碼分析
picasso圖檔緩存架構 代碼分析

    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) {  

            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。

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

picasso圖檔緩存架構 代碼分析
picasso圖檔緩存架構 代碼分析

dispatcher内有一個handlerthread,所有的請求都會通過這個thread轉換,也就是請求也是異步的,這樣應該是為了ui線程更加流暢,同時保證請求的順序,因為handler的消息隊列。

外部調用的是dispatchxxx方法,然後通過handler将請求轉換到對應的performxxx方法。

例如生成action以後就會調用dispather的dispatchsubmit()來請求執行,

picasso圖檔緩存架構 代碼分析

void dispatchsubmit(action action) {  

        handler.sendmessage(handler.obtainmessage(request_submit, action));  

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

picasso圖檔緩存架構 代碼分析

void performsubmit(action action) {  

        bitmaphunter hunter = huntermap.get(action.getkey());  

        if (hunter != null) {  

            hunter.attach(action);  

        if (service.isshutdown()) {  

        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("http://i.imgur.com/dvpvklr.png").into(imageview)的過程,

picasso圖檔緩存架構 代碼分析

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執行。