溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何快速上手Picasso

發(fā)布時間:2022-02-19 13:46:30 來源:億速云 閱讀:157 作者:小新 欄目:開發(fā)技術

這篇文章將為大家詳細講解有關如何快速上手Picasso,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

Picasso是Square公司出品的一款非常優(yōu)秀的開源圖片加載庫,是目前Android開發(fā)中超級流行的圖片加載庫之一

如何快速上手Picasso

Picasso基本使用

一 工程引入

在我們的項目build.gradle中添加對Picasso框架的依賴:

compile 'com.squareup.picasso:picasso:2.5.2'

在這里我使用的是2.5.2的最新版本。

二 添加權限

因為加載圖片需要訪問網(wǎng)絡,由此我們在Manifest中添加訪問網(wǎng)絡的權限:

"android.permission.INTERNET"/>
三 創(chuàng)建加載圖片布局文件

在我們的MainActivity的布局文件中添加一個Button和一個ImageView:

如何快速上手Picasso
Picasso 基本使用和源碼完全解析Picasso 基本使用和源碼完全解析
四 MainActivity中點擊加載圖片

在這里我們點擊Button讓它加載一張百度Logo的圖片并顯示在ImageView控件中。

如何快速上手Picasso
Picasso 基本使用和源碼完全解析Picasso 基本使用和源碼完全解析
五 加載圖片

點擊Button加載圖片,來看結果:

如何快速上手Picasso

ok,已加載出來了,但是我們到底做了什么呢?

基本加載

其實我們所做的事情非常的簡單,那就是在Button的點擊事件中添加一行代碼:

Picasso.with(MainActivity.this).load(url).into(headerImage);

沒錯就是一行代碼搞定圖片加載。至于它是怎么實現(xiàn)的,我們會在后面的源碼分析中詳細的解析它,現(xiàn)在來看看它的其他使用。

占位圖

不光加載圖片是非常簡單的事情,而且還可以在加載過程中使用占位圖,可以很友好的告訴用戶它正在加載,而不是加載中顯示空白的界面。

Picasso.with(MainActivity.this)
.load(url)
.placeholder(R.mipmap.ic_launcher)
.into(headerImage);

使用placeholder為尚未加載到圖片的ImageView設置占位圖,這是一種友好的顯示方式。

如何快速上手Picasso
Picasso 基本使用和源碼完全解析Picasso 基本使用和源碼完全解析
異常圖

不光可以設置占位圖,而且還可以在圖片加載不出來,或是找不到要加載的圖片后,可以為ImageView設置異常圖:

Picasso.with(MainActivity.this)
      .load(url)
      .placeholder(R.mipmap.ic_launcher)
      .error(R.drawable.error)
      .into(headerImage);

使用error可以為加載異常的ImageView設置異常圖,修改我們的圖片URL,使它無法獲取到正確的圖片地址,然后來看結果:

如何快速上手Picasso
轉(zhuǎn)換器

不僅如此,我們還可以對加載到的圖片進行重新調(diào)整,比如改變圖片的大小,顯示形狀等,可以使用transform方法,例如:

首先我們先自定義一個Transformation:

private class customTransformer implements Transformation{

       @Override
       public Bitmap transform(Bitmap source) {
           //在這里可以對Bitmap進行操作,比如改變大小,形狀等

           return source;
       }
       @Override
       public String key() {
           return null;
       }
   }

然后在transform方法中進行處理:

Picasso.with(MainActivity.this)
       .load(url)
       .placeholder(R.mipmap.ic_launcher)
       .transform(new customTransformer())
       .error(R.drawable.error)
       .into(headerImage);

這樣的話就可以對圖片進行一定意義上的控制和選擇,使它更加符合我們的需求。

當然Picasso中還有很多其他的應用,比如可以設置加載大小,使用resizeDimen方法,填充方式使用centerCrop,fit等等,大家如果有需要的話可以自己嘗試使用。這里就不要一一的介紹了。

Picasso 源碼解析

ok,上面介紹了Picasso的部分基礎使用,非常的簡單,一行代碼搞定你的所需,那么下面我們從源碼的角度來解析下它到底是怎么實現(xiàn)我們的圖片加載和使用的。

以下面最簡潔的加載為示例:

Picasso.with(MainActivity.this).load(url).into(headerImage);
with

首先Picasso會調(diào)用靜態(tài)with方法,那么我們來看看with方法是怎么實現(xiàn)的:

如何快速上手Picasso

由上面的源碼我們可以看到,在with方法中主要做了一件事,那就是返回一個Picasso實例,當然這個實例也不是那么簡單的創(chuàng)建的,為了防止Picasso的多次創(chuàng)建,這里使用了雙重加鎖的單例模式來創(chuàng)建的,主要目的是為了保證線程的安全性。但是它又不是直接的使用單例模式創(chuàng)建的,在創(chuàng)建實例的過程中使用了Builder模式,它可以使Picasso在創(chuàng)建時初始化很多對象,以便后期使用,那么我們就來看看這個Builder是怎么操作的:

如何快速上手Picasso

在Builder的構造方法中就只是獲取到當前應用級別的上下文,也就說明了Picasso是針對應用級別的使用,不會是隨著Activity或是Fragment的生命周期而產(chǎn)生變化,只有當當前的應用退出或是銷毀時Picasso才會停止它的行為。

那么接下來看下build方法中做了哪些操作呢:

如何快速上手Picasso
Picasso 基本使用和源碼完全解析Picasso 基本使用和源碼完全解析

這里代碼也是很簡單,主要是初始化了downloader,cache,service,dispatcher等幾個實例變量,而這幾個變量值也是已設置的,如源碼:

 public Builder downloader(Downloader downloader) {
     if (downloader == null) {
       throw new IllegalArgumentException("Downloader must not be null.");
     }
     if (this.downloader != null) {
       throw new IllegalStateException("Downloader already set.");
     }
     this.downloader = downloader;
     return this;
   }

   public Builder executor(ExecutorService executorService) {
     if (executorService == null) {
       throw new IllegalArgumentException("Executor service must not be null.");
     }
     if (this.service != null) {
       throw new IllegalStateException("Executor service already set.");
     }
     this.service = executorService;
     return this;
   }

   public Builder memoryCache(Cache memoryCache) {
     if (memoryCache == null) {
       throw new IllegalArgumentException("Memory cache must not be null.");
     }
     if (this.cache != null) {
       throw new IllegalStateException("Memory cache already set.");
     }
     this.cache = memoryCache;
     return this;
   }

   ...

這些設置就像我們平常使用AlertDialog一樣,貨到到Builder之后分別進行設置就行。

ok,我們現(xiàn)在來看看初始化中幾個非常重要的變量: downloader 下載器 首先看下downloader,builder中首先判斷downloader是否為空值,當為空值時就為它初始化默認值,如:

if (downloader == null) {
       downloader = Utils.createDefaultDownloader(context);
}

來看下Utils中是怎么實現(xiàn)downloader 的初始化的:

如何快速上手Picasso

createDefaultDownloader方法中首先使用Java反射機制來查找項目中是否使用了OKHttp網(wǎng)絡加載框架,如果使用了則會使用okhttp作為圖片的加載方式,如果沒有使用,則會使用內(nèi)置的封裝加載器UrlConnectionDownloader。

注:由于okhttp3的包名已更換,所以在這里都是使用內(nèi)置的封裝下載器,這個是一個小bug等待完善。當修復之后Picasso+okhttp3則是最理想的加載方式。 service 線程池 同樣的使用,首先判斷是否為空,如果為空則初始化默認對象:

if (service == null) {
    service = new PicassoExecutorService();
}

我們在來看看PicassoExecutorService的源碼:

如何快速上手Picasso

PicassoExecutorService直接繼承與ThreadPoolExecutor線程池,在構造方法中初始化了主線程大小,最大線程等,如:

public ThreadPoolExecutor(int corePoolSize,
                             int maximumPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue workQueue,
                             ThreadFactory threadFactory) {
       if (corePoolSize if (workQueue == null || threadFactory == null || handler == null)
           throw new NullPointerException();
       this.corePoolSize = corePoolSize;
       this.maximumPoolSize = maximumPoolSize;
       this.workQueue = workQueue;
       this.keepAliveTime = unit.toNanos(keepAliveTime);
       this.threadFactory = threadFactory;
       this.handler = handler;

當然,在Picasso線程池中主線程和最大線程數(shù)是可變的,根據(jù)用戶使用的網(wǎng)絡類型來設置線程數(shù)量,后面會詳細說明它的應用。 dispatcher 事務分發(fā)器 dispatcher 在Picasso中扮演著十分重要的角色,可以說它是整個圖片加載過程中的中轉(zhuǎn)站,主線程和子線程來回的切換主要都是依賴它的存在。下面我們將來仔細的研究下它的設計,理解好它的存在對整個Picasso框架的理解也基本明朗。

首先,來看看它的登場亮相:

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

它的登場就是創(chuàng)建一個Dispatcher 對象實例,當然,它傳遞了在Builder中所初始化的對象實例,比如downloader下載器,用于圖片的下載,當然下載是不能再主線程進行的,所以這里也傳遞了service線程池,而下載好的資源也是不能直接在子線程中進行更新UI的,所以同時也把主線程中的HANDLER傳遞進去,同時應用級別的上下文context,和緩存cache,狀態(tài)變化stats等也傳遞進去進行相對應的業(yè)務操作。

經(jīng)過上面的分析,我們可以很清楚的看出,就這么簡單的一個創(chuàng)建實例已經(jīng)很明確的表達出了Dispatcher存在的意義,而且我們也明確了它大概的職責。

那么我們接著看看Dispatcher的構造方法中具體的做了哪些操作:

如何快速上手Picasso

在Dispatcher的構造方法中我把它分為了5個部分,下面來詳細的解析下:

①:dispatcherThread,它是一個HandlerThread線程,如:

static class DispatcherThread extends HandlerThread {
   DispatcherThread() {
     super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
   }
 }

它的創(chuàng)建主要是開啟一個子線程供Dispatcher調(diào)用,目的就是為了在子線程中去執(zhí)行耗時的圖片下載操作。

②:對象實例的接受,主要是接受在Picasso中初始化的對象實例,這個沒有什么好說的。

③:創(chuàng)建用于保存數(shù)據(jù)、對象的集合,也就是為了保存對象或狀態(tài)用的。

④:DispatcherHandler,這是一個Handler,并且是作用在dispatcherThread線程中的Handler,它用于把在dispatcherThread子線程的操作轉(zhuǎn)到到Dispatcher中去,它的構造方法中接受了Dispatcher對象:

如何快速上手Picasso
Picasso 基本使用和源碼完全解析Picasso 基本使用和源碼完全解析

我們在來看看他的handleMessage方法是怎么處理消息的:

@Override public void handleMessage(final Message msg) {
     switch (msg.what) {
       case REQUEST_SUBMIT: {
         Action action = (Action) msg.obj;
         dispatcher.performSubmit(action);
         break;
       }
       case REQUEST_CANCEL: {
         Action action = (Action) msg.obj;
         dispatcher.performCancel(action);
         break;
       }
       case TAG_PAUSE: {
         Object tag = msg.obj;
         dispatcher.performPauseTag(tag);
         break;
       }
       case TAG_RESUME: {
         Object tag = msg.obj;
         dispatcher.performResumeTag(tag);
         break;
       }
       case HUNTER_COMPLETE: {
         BitmapHunter hunter = (BitmapHunter) msg.obj;
         dispatcher.performComplete(hunter);
         break;
       }
       case HUNTER_RETRY: {
         BitmapHunter hunter = (BitmapHunter) msg.obj;
         dispatcher.performRetry(hunter);
         break;
       }
       case HUNTER_DECODE_FAILED: {
         BitmapHunter hunter = (BitmapHunter) msg.obj;
         dispatcher.performError(hunter, false);
         break;
       }
       case HUNTER_DELAY_NEXT_BATCH: {
         dispatcher.performBatchComplete();
         break;
       }
       case NETWORK_STATE_CHANGE: {
         NetworkInfo info = (NetworkInfo) msg.obj;
         dispatcher.performNetworkStateChange(info);
         break;
       }
       case AIRPLANE_MODE_CHANGE: {
         dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);
         break;
       }
       default:
         Picasso.HANDLER.post(new Runnable() {
           @Override public void run() {
             throw new AssertionError("Unknown handler message received: " + msg.what);
           }
         });
     }
   }

而從它的處理消息的handleMessage中我們可以看出,所有的消息并沒有被直接進行處理而是轉(zhuǎn)移到了dispatcher中,在dispatcher中進行相應的處理。

⑤:監(jiān)聽網(wǎng)絡變化操作,它是用于監(jiān)聽用戶手機網(wǎng)絡變化而存在的。我們主要來看看NetworkBroadcastReceiver這個類:

如何快速上手Picasso

在構造參數(shù)中也接收到Dispatcher對象,并有注冊廣播和銷毀廣播的方法,當然它也沒有直接的處理,也是傳遞到Dispatcher中進行消化的。

我們在來看看當用戶的網(wǎng)絡發(fā)生變化時,它會做哪些操作:

如何快速上手Picasso

我們可以看到主要分為兩個,一個是航班模式,一個是正常的網(wǎng)絡狀態(tài)變化,航班模式我們先不用理會,主要看下網(wǎng)絡變化的操作:

void dispatchNetworkStateChange(NetworkInfo info) {  
        handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info));
}

這里的handler就是DispatcherHandler,那么我們看看它又是怎么做的:

case NETWORK_STATE_CHANGE: {
         NetworkInfo info = (NetworkInfo) msg.obj;
         dispatcher.performNetworkStateChange(info);
         break;
       }

調(diào)用dispatcher的performNetworkStateChange方法來處理:

如何快速上手Picasso

當網(wǎng)絡變化時,它會傳遞給我們的PicassoExecutorService線程池,在adjustThreadCount方法中判斷用戶是使用的那類型網(wǎng)絡,如wifi,4G等,然后為線程池設置相應的線程數(shù)。來看:

 void adjustThreadCount(NetworkInfo info) {
   if (info == null || !info.isConnectedOrConnecting()) {
     setThreadCount(DEFAULT_THREAD_COUNT);
     return;
   }
   switch (info.getType()) {
     case ConnectivityManager.TYPE_WIFI:
     case ConnectivityManager.TYPE_WIMAX:
     case ConnectivityManager.TYPE_ETHERNET:
       setThreadCount(4);
       break;
     case ConnectivityManager.TYPE_MOBILE:
       switch (info.getSubtype()) {
         case TelephonyManager.NETWORK_TYPE_LTE:  // 4G
         case TelephonyManager.NETWORK_TYPE_HSPAP:
         case TelephonyManager.NETWORK_TYPE_EHRPD:
           setThreadCount(3);
           break;
         case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
         case TelephonyManager.NETWORK_TYPE_CDMA:
         case TelephonyManager.NETWORK_TYPE_EVDO_0:
         case TelephonyManager.NETWORK_TYPE_EVDO_A:
         case TelephonyManager.NETWORK_TYPE_EVDO_B:
           setThreadCount(2);
           break;
         case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
         case TelephonyManager.NETWORK_TYPE_EDGE:
           setThreadCount(1);
           break;
         default:
           setThreadCount(DEFAULT_THREAD_COUNT);
       }
       break;
     default:
       setThreadCount(DEFAULT_THREAD_COUNT);
   }
 }

 private void setThreadCount(int threadCount) {
   setCorePoolSize(threadCount);
   setMaximumPoolSize(threadCount);
 }

就如上面所說,根據(jù)用戶使用不同的網(wǎng)絡類型分別設置線程的數(shù)量,比如當用戶使用的是wifi,線程數(shù)量將會設置為4個,4G的話設為3個等,這樣根據(jù)用戶的具體情況來設計線程數(shù)量是非常人性化的,也是值得我們效仿的。

ok,到此我們重要的Dispatcher對象的構造方法已完全的解析完成了,從上面的解析中我們很清楚的看到了Dispatcher作為中轉(zhuǎn)站存在的意義,幾乎所有的線程轉(zhuǎn)換操作都是由Dispatcher來操控的,當然可能還有小伙伴們并不清楚它是怎么運作的,怎么進入子線程的,那是因為我們的講解還沒有進行到進入子線程的步驟而已,下面將會進一步的講解。

總結下Dispatcher中所包含的重要對象實例:

①:PicassoExecutorService線程池

②:downloader 下載器

③:針對DispatcherThread線程的DispatcherHandler處理器

④:NetworkBroadcastReceiver網(wǎng)絡監(jiān)聽器

⑤:mainThreadHandler主線程的Handler

⑥:保存數(shù)據(jù)的集合:hunterMap,pausedActions,batch等

理解好了上面這些對象存在的意義將對下面的理解有著巨大的好處,請沒有完全理解的在仔細的閱讀一遍(非常重要)。

ok,知道了Dispatcher包含哪些重要的對象實例之后,讓我們再回到Picasso的Builder中,在build方法中最終返回的是一個Picasso的對象實例:

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

在Picasso的構造方法中值得我們只要注意的是針對requestHandlers的應用:

如何快速上手Picasso

這里把Picasso能都應用的RequestHandler都添加到集合中,然后根據(jù)具體的需求運用相對應的Handler進行業(yè)務的處理。

ok,到此Picasso中with方法中所做的事情已經(jīng)完完全全的展示在您的面前了,相信您對這一步應用理解的很透徹了。

那么來總結下,Picasso現(xiàn)在擁有了那些重要的對象實例:

①:dispatcher,重要性不言而喻,上面已充分的展示了它的重要性,作為中轉(zhuǎn)站,Picasso是必須要擁有了的。最終目的是讓我們的主線程進入子線程中去進行耗時的下載操作。

②:requestHandlers,它的存在是為了選擇一種請求處理方式,比如說,下載網(wǎng)絡圖片需要使用NetworkRequestHandler這個請求器。

③:HANDLER,這是主線程的Handler,用來處理返回結果的,比如說圖片下載成功后要更新UI就是通過它來完成的,這會在后面圖片下載完成后更新UI時詳細講解

④:一些配置對象實例等,如:緩存cache,圖片加載默認配置defaultBitmapConfig,狀態(tài)存儲stats等等。

load

with方法中主要是做了一個基礎的配置工作,比如Picasso的配置,Dispatcher的配置,這些都是非常重要的前提工作,只有做好了這些配置我們使用起來才能顯得毫不費勁。

下面我們就來看看它的應用吧。在load方法中需要我們傳遞一個參數(shù),這個參數(shù)可以是Url,可以是一個path路徑,也可以是一個文件,一個資源布局等:

①:url
public RequestCreator load(Uri uri) {
   return new RequestCreator(this, uri, 0);
}

②:path
public RequestCreator load(String path) {
   if (path == null) {
     return new RequestCreator(this, null, 0);
   }
   if (path.trim().length() == 0) {
     throw new IllegalArgumentException("Path must not be empty.");
   }
   return load(Uri.parse(path));
}

③:file
public RequestCreator load(File file) {
   if (file == null) {
     return new RequestCreator(this, null, 0);
   }
   return load(Uri.fromFile(file));
}

④:resourceId
public RequestCreator load(int resourceId) {
  if (resourceId == 0) {
     throw new IllegalArgumentException("Resource ID must not be zero.");
   }
   return new RequestCreator(this, null, resourceId);
}

不管傳遞的是一個什么參數(shù),它都是返回一個請求構造器RequestCreator,我們來看看它的構造方法做了哪些事情:

如何快速上手Picasso
Picasso 基本使用和源碼完全解析Picasso 基本使用和源碼完全解析

主要是獲取到Picasso對象,并Builder模式構建一個Request中的Builder對象:

Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
     this.uri = uri;
     this.resourceId = resourceId;
     this.config = bitmapConfig;
   }

這個Builder對象主要接受了一些參數(shù)信息,包括url,資源布局,和默認的圖片配置。

由于load方法返回的是一個RequestCreator對象,所以我們可以使用方法鏈的形式進行調(diào)用其他的方法,比如給請求添加占位圖,異常圖,轉(zhuǎn)換器等等,具體的可以看源碼。

ok,總體來說這load方法中主要是創(chuàng)建了一個RequestCreator對象,并且RequestCreator中構造了一個Request.Builder對象。

那么來看看RequestCreator擁有哪些重要的對象實例:

①:picasso對象

②:Request.Builder對象實例data,它里面包括我們請求的URL地址,資源文件以及圖片默認配置。

into

在load方法中主要創(chuàng)建了一個RequestCreator對象,并獲取到了要加載的url/path/file/resourceId資源地址路徑,那么接下來要做的就是在into方法中來加載圖片了。先來看看into方法中做了哪些事情:

public void into(ImageView target, Callback callback) {
   long started = System.nanoTime();
   checkMain();

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

   if (!data.hasImage()) {
     picasso.cancelRequest(target);
     if (setPlaceholder) {
       setPlaceholder(target, getPlaceholderDrawable());
     }
     return;
   }

   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());
       }
       picasso.defer(target, new DeferredRequestCreator(this, target, callback));
       return;
     }
     data.resize(width, height);
   }

   Request request = createRequest(started);
   String requestKey = createKey(request);

   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 action =
       new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
           errorDrawable, requestKey, tag, callback, noFade);

   picasso.enqueueAndSubmit(action);
 }

如上源碼可以知道into還是做了很多的事情,下面一一解析:

**①:checkMain():**首先檢查主否在主線程運行,如果不是在主線程就會拋出一個應該在主線程運行的異常:throw new IllegalStateException(“Method call should happen from the main thread.”);這說明到這一步還是在主線程運行的。

**②:data.hasImage():**這里的data是在之前初始化的Request.Builder對象,它里面包含url地址,resourceId和默認配置,這里是判斷uri或resourceId是否為空為0,如果是的話就取消imageview的請求:picasso.cancelRequest(target);

**③:deferred:**延遲,如果需要延遲的話就會得到執(zhí)行,然后會去獲取data中圖片的大小,如果沒有的話,就得到target的寬高來重新設置要加載的圖片的尺寸:data.resize(width, height);

**④:createRequest:**在data.build()方法中創(chuàng)建Request對象,該對象將會包含uri或resourceId以及默認圖片config。然后在得到的Request對象后進行轉(zhuǎn)換,該轉(zhuǎn)換主要是與我們在Picasso構建時是否自定義了RequestTransformer有關。

**⑤:createKey:**它主要的是返回已String類型key,主要目的是建立ImageView和key的關聯(lián),可以通過key來獲取到Imageview的狀態(tài),比如說是否已緩存

**⑥:shouldReadFromMemoryCache:**看方法名也能知道,它的作用是是否從內(nèi)存緩存中讀取數(shù)據(jù),如果是的話,就從緩存中讀取數(shù)據(jù),假如獲取到數(shù)據(jù)則會取消當前ImageView的請求并直接給它設置Bitmap,而且如果有回調(diào)的話將會回調(diào)成功的方法。

**⑦:ImageViewAction:**構建一個ImageViewAction對象,值得注意的是在構建對象時,會把ImageView添加到RequestWeakReference進行存儲,以便于使用時查找,RequestWeakReference是一個WeakReference類型的弱引用。同時ImageViewAction也是在完成圖片加載時真正更新UI的關鍵類,這在后面會進行詳細講解。

**⑧:enqueueAndSubmit:**調(diào)用picasso的enqueueAndSubmit方法進行提交任務。

如何快速上手Picasso

在來看submit方法的操作:

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

在這里再次使用到了dispatcher任務分發(fā)器,把我們的任務action提交到Dispatcher中進行處理。然后在來看下dispatchSubmit方法:

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

由Dispatcher的構造方法我們可以知道此時的handler是DispatcherHandler,那么我們看下它是怎么處理我們的任務行為的:

case REQUEST_SUBMIT: {
         Action action = (Action) msg.obj;
         dispatcher.performSubmit(action);
         break;

在handleMessage中直接獲取到我們的任務行為Action,然后調(diào)用performSubmit方法:

void performSubmit(Action action, boolean dismissFailed) {
   if (pausedTags.contains(action.getTag())) {
     pausedActions.put(action.getTarget(), action);
     if (action.getPicasso().loggingEnabled) {
       log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
           "because tag '" + action.getTag() + "' is paused");
     }
     return;
   }

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

   if (service.isShutdown()) {
     if (action.getPicasso().loggingEnabled) {
       log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
     }
     return;
   }

   hunter = forRequest(action.getPicasso(), this, cache, stats, action);
   hunter.future = service.submit(hunter);
   hunterMap.put(action.getKey(), hunter);
   if (dismissFailed) {
     failedActions.remove(action.getTarget());
   }

   if (action.getPicasso().loggingEnabled) {
     log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
   }
 }

performSubmit方法是真正意義上的任務提交的具體地方,我們來解讀下它的源碼:

①:首先根據(jù)action的標志來查詢是否已存在于暫停列表中,如果存在將會把action存放到pausedActions的集合列表中,以便等待喚醒請求。

②:然后通過action的key從hunterMap集合中查詢是否已存在該hunterMap的請求項,如果存在將會調(diào)用hunter的attach方法,進行合并請求,避免一個ImageView進行多次的重復請求:

如何快速上手Picasso

把action存放到ArrayList當中,如果有相同的action根據(jù)ArrayList的不重復性將會保存一個action,并且更新新的屬性值priority。

③:當線程池service沒有關閉的時候,通過forRequest方法獲取一個Runnable類型的BitmapHunter線程,來看下forRequest的源碼:

如何快速上手Picasso

分別從action和picasso獲取到Request請求和所有的requestHandlers請求處理器,然后遍歷所有的請求器獲取每一個請求處理器,調(diào)用canHandleRequest嘗試看是否該處理器能夠處理,來看下canHandleRequest方法:

public abstract boolean canHandleRequest(Request data);

它是一個抽象方法,需要到子類去查找,而實現(xiàn)它的子類都是根據(jù)以下的約束條件來判斷是否可以處理該請求的:

String scheme = data.uri.getScheme();

也就是說通過url地址的Scheme約束條件進行判斷的,而以我們現(xiàn)在的action中的Request獲取到url,是以http/https開頭的,那么來看下NetworkRequestHandler中的canHandleRequest源碼:

private static final String SCHEME_HTTP = "http";
private static final String SCHEME_HTTPS = "https";

@Override
public boolean canHandleRequest(Request data) {
   String scheme = data.uri.getScheme();
   return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
 }

可以看出正好的匹配,由此得出結論,將要處理我們請求數(shù)據(jù)的將是NetworkRequestHandler處理器。

匹配好了請求處理器,將會返回一個BitmapHunter的線程,獲取到線程之后會在線程池進行開啟線程,并把該線程存放到hunterMap線程集合中以便多次請求可以合并相同的線程:

   hunter.future = service.submit(hunter);
   hunterMap.put(action.getKey(), hunter);

ok,現(xiàn)在可以到我們的線程池PicassoExecutorService中看看它到底是怎么執(zhí)行的,submit源碼如下:

@Override
 public Future> submit(Runnable task) {
   PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
   execute(ftask);
   return ftask;
 }

用把我們的線程task封裝到PicassoFutureTask 中,PicassoFutureTask 是一個更便于我們控住處理的線程,然后調(diào)用execute開啟線程,之后會把我們的線程轉(zhuǎn)移到addWorker方法中,在addWorker中開啟線程start:

boolean workerStarted = false;
       boolean workerAdded = false;
       Worker w = null;
       try {
           w = new Worker(firstTask);
           final Thread t = w.thread;
           if (t != null) {
               final ReentrantLock mainLock = this.mainLock;
               mainLock.lock();
               try {
                   // Recheck while holding lock.
                   // Back out on ThreadFactory failure or if                   // shut down before lock acquired.
                   int rs = runStateOf(ctl.get());

                   if (rs if (t.isAlive()) // precheck that t is startable
                           throw new IllegalThreadStateException();
                       workers.add(w);
                       int s = workers.size();
                       if (s > largestPoolSize)
                           largestPoolSize = s;
                       workerAdded = true;
                   }
               } finally {
                   mainLock.unlock();
               }
               if (workerAdded) {
                   t.start();
                   workerStarted = true;
               }

上述源碼是執(zhí)行在ThreadPoolExecutor線程池中的,可以看下源碼。

當線程開啟后,在哪執(zhí)行呢?

我們知道我們的封裝的線程最初始的是BitmapHunter,那么我們就到它里面來看看是怎么執(zhí)行的:

如何快速上手Picasso

在BitmapHunter的run方法中,先修改線程名稱,然后執(zhí)行hunt方法,把執(zhí)行結果存放到result中,那我們先看看hunt方法是怎么執(zhí)行的:

Bitmap hunt() throws IOException {
   Bitmap bitmap = null;

   if (shouldReadFromMemoryCache(memoryPolicy)) {
     bitmap = cache.get(key);
     if (bitmap != null) {
       stats.dispatchCacheHit();
       loadedFrom = MEMORY;
       if (picasso.loggingEnabled) {
         log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
       }
       return bitmap;
     }
   }

   data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
   RequestHandler.Result result = requestHandler.load(data, networkPolicy);
   if (result != null) {
     loadedFrom = result.getLoadedFrom();
     exifRotation = result.getExifOrientation();

     bitmap = result.getBitmap();

     // If there was no Bitmap then we need to decode it from the stream.
     if (bitmap == null) {
       InputStream is = result.getStream();
       try {
         bitmap = decodeStream(is, data);
       } finally {
         Utils.closeQuietly(is);
       }
     }
   }

   if (bitmap != null) {
     if (picasso.loggingEnabled) {
       log(OWNER_HUNTER, VERB_DECODED, data.logId());
     }
     stats.dispatchBitmapDecoded(bitmap);
     if (data.needsTransformation() || exifRotation != 0) {
       synchronized (DECODE_LOCK) {
         if (data.needsMatrixTransform() || exifRotation != 0) {
           bitmap = transformResult(data, bitmap, exifRotation);
           if (picasso.loggingEnabled) {
             log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
           }
         }
         if (data.hasCustomTransformations()) {
           bitmap = applyCustomTransformations(data.transformations, bitmap);
           if (picasso.loggingEnabled) {
             log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
           }
         }
       }
       if (bitmap != null) {
         stats.dispatchBitmapTransformed(bitmap);
       }
     }
   }

   return bitmap;
 }

解析下源碼:

①:還是首先獲取到action行為對應key,通過key從緩存cache中查找bitmap是否存在,如果存在修改stats狀態(tài)并直接的把bitmap返回。

②:如果緩存中不存在則是要去網(wǎng)絡加載requestHandler.load();我們通過上面的分析知道能處理當前requesr的requestHandler是NetworkRequestHandler,那么我們?nèi)etworkRequestHandler的load方法中查看:

如何快速上手Picasso

這里很清晰的可以看到是直接調(diào)用downloader的load方法,而我們的downloader在Picasso構建Builder的時候也很清晰的說明是UrlConnectionDownloader,那么在去UrlConnectionDownloader的load方法看看:

protected HttpURLConnection openConnection(Uri path) throws IOException {
    HttpURLConnection connection = (HttpURLConnection) new URL(path.toString()).openConnection();
    connection.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS);
    connection.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS);
    return connection;
  }

  @Override public Response load(Uri uri, int networkPolicy) throws IOException {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      installCacheIfNeeded(context);
    }

    HttpURLConnection connection = openConnection(uri);
    connection.setUseCaches(true);

    if (networkPolicy != 0) {
      String headerValue;

      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        headerValue = FORCE_CACHE;
      } else {
        StringBuilder builder = CACHE_HEADER_BUILDER.get();
        builder.setLength(0);

        if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
          builder.append("no-cache");
        }
        if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
          if (builder.length() > 0) {
            builder.append(',');
          }
          builder.append("no-store");
        }

        headerValue = builder.toString();
      }

      connection.setRequestProperty("Cache-Control", headerValue);
    }

    int responseCode = connection.getResponseCode();
    if (responseCode >= 300) {
      connection.disconnect();
      throw new ResponseException(responseCode + " " + connection.getResponseMessage(),
          networkPolicy, responseCode);
    }

    long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
    boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));

    return new Response(connection.getInputStream(), fromCache, contentLength);
  }

這里的代碼相信我們都很熟悉,就是構建一個HttpURLConnection 去進行網(wǎng)絡請求,當然還有就是根據(jù)networkPolicy進行一些網(wǎng)絡緩存的策略。最后把結果存放到Response對象中。

然后NetworkRequestHandler的load方法又會從Response對象中獲取數(shù)據(jù),并把它存放到Result對象中。然后返回給BitmapHunter中hunt方法的RequestHandler.Result result中,從result中獲取輸入流,解析輸入流轉(zhuǎn)化為Bitmap并返回。

到此我們已從網(wǎng)絡中下載了數(shù)據(jù),并轉(zhuǎn)化為bitmap了,然后在返回我們的BitmapHunter中的run方法中,在run方法中我們獲取到了bitmap,然后調(diào)用dispatcher進行事物分發(fā),成功獲取則調(diào)用dispatchComplete,否則調(diào)用dispatchFailed。

下面我們看看dispatcher中的dispatchComplete方法:

void dispatchComplete(BitmapHunter hunter) {
   handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
 }

同樣的道理,這里的handler還是DispatcherHandler,那么來看看它的處理:

case HUNTER_COMPLETE: {
         BitmapHunter hunter = (BitmapHunter) msg.obj;
         dispatcher.performComplete(hunter);
         break;
       }

轉(zhuǎn)到dispatcher的performComplete方法中:

如何快速上手Picasso

這里首先把我們的結果保存在cache緩存中,然后從hunterMap集合中移除BitmapHunter對應的key,原因是請求已完成。然后調(diào)用 batch(hunter);方法:

如何快速上手Picasso

首先判斷BitmapHunter是否已取消,然后把BitmapHunter存放在一個List batch集合中,最后通過DispatcherHandler發(fā)送一個空的延遲消息,目的是為了延遲下下一個網(wǎng)絡加載以便處理當前的bitmap工作,來看下它是怎么處理的:

case HUNTER_DELAY_NEXT_BATCH: {
         dispatcher.performBatchComplete();
         break;
       }

進入performBatchComplete中查看:

void performBatchComplete() {
   List copy = new ArrayList(batch);
   batch.clear();
   mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
   logBatch(copy);
 }

performBatchComplete中首先獲取存放BitmapHunter的集合,然后調(diào)用mainThreadHandler發(fā)送消息。

還記得mainThreadHandler是怎么嗎?

在Picasso類中構建Builder的build方法中,創(chuàng)建一個Dispatcher對象,里面?zhèn)鬟M一個HANDLER的參數(shù),請看:

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

這個HANDLER就是mainThreadHandler。

那么它發(fā)送的消息是怎么處理的呢?

如何快速上手Picasso

獲取到BitmapHunter集合進行遍歷,然后直接調(diào)用Picasso中的complete方法:

void complete(BitmapHunter hunter) {
   Action single = hunter.getAction();
   List joined = hunter.getActions();

   boolean hasMultiple = joined != null && !joined.isEmpty();
   boolean shouldDeliver = single != null || hasMultiple;

   if (!shouldDeliver) {
     return;
   }

   Uri uri = hunter.getData().uri;
   Exception exception = hunter.getException();
   Bitmap result = hunter.getResult();
   LoadedFrom from = hunter.getLoadedFrom();

   if (single != null) {
     deliverAction(result, from, single);
   }

   if (hasMultiple) {
     //noinspection ForLoopReplaceableByForEach
     for (int i = 0, n = joined.size(); i if (listener != null && exception != null) {
     listener.onImageLoadFailed(this, uri, exception);
   }
 }

在complete方法中,直接從BitmapHunter中獲取原已封裝的Action,Uri,Bitmap等等數(shù)據(jù)信息,然后調(diào)用deliverAction方法:

如何快速上手Picasso

這里進行一系列的判斷,當action沒有取消,并且Bitmap不為空時,將會調(diào)用action.complete(result, from);來完成操作。

那還記得我們在創(chuàng)建action時的操作嗎?

Action action =
       new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
           errorDrawable, requestKey, tag, callback, noFade);

那么很清晰的就知道我們的Action 其實就是ImageViewAction呢

那么我們來看下ImageViewAction的complete是怎么操作的呢?

如何快速上手Picasso

target就是我們在創(chuàng)建ImageViewAction時傳遞的ImageView,現(xiàn)在獲取到它,然后調(diào)用PicassoDrawable.setBitmap方法來完成設置圖片:

如何快速上手Picasso

當我們看到target.setImageDrawable(drawable);時,是不是終于松了一口氣呢,終于看到了為ImageView設置圖片的信息了。


關于“如何快速上手Picasso”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI