The basic use

Picasso.get() .load(Uri.parse(getItem(position)? .pic)) .placeholder(R.drawable.ic_android_black_24dp) .into(holder.iv_img)Copy the code

Picasso Object creation

Picas.get () constructs the Picas.object and returns a singleton pattern with a double lock check

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;
}
Copy the code

Picasso was the Builder object

  • Member variables
    private final Context context; // Context application level private Downloader Downloader; // Download OkHttp3Downloader private ExecutorService service; // Thread pool for asynchronous tasks private Cache Cache; // LruCache private Listener listener; private RequestTransformer transformer; private List<RequestHandler> requestHandlers; // RequestHandler, real handle load logic private bitmap. Config defaultBitmapConfig; private boolean indicatorsEnabled; private boolean loggingEnabled;Copy the code
  • The build method

    To create a Picasso object, call the Constructor of the Picasso object based on the builder’s member variables. Since the Builder was created in the program, by default none of these member variables are assigned. The build method provides default values for these variables
    public Picasso build() { Context context = this.context; // Default downloaderif(downloader == null) { downloader = new OkHttp3Downloader(context); } // The default cacheif(cache == null) { cache = new LruCache(context); } // The default thread poolif(service == null) { service = new PicassoExecutorService(); } // The default transformerif(transformer == null) { transformer = RequestTransformer.IDENTITY; } Stats stats = new Stats(cache); Dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }
    Copy the code

Picasso was the object

  • Member variables
    static volatile Picasso singleton = null;
    
    private final Listener listener;
    private final RequestTransformer requestTransformer;
    private final CleanupThread cleanupThread;
    private final List<RequestHandler> requestHandlers;
    
    final Context context;
    final Dispatcher dispatcher;
    final Cache cache;
    final Stats stats;
    final Map<Object, Action> targetToAction;
    final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;
    final ReferenceQueue<Object> referenceQueue;
    final Bitmap.Config defaultBitmapConfig;
    
    boolean indicatorsEnabled;
    volatile boolean loggingEnabled;
    
    boolean shutdown;
    Copy the code
  • A constructor

    Initialize additional objects by assigning parameters passed in the Builder to its own member variables
    Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener, RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats, Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) { this.context = context; this.dispatcher = dispatcher; this.cache = cache; this.listener = listener; this.requestTransformer = requestTransformer; this.defaultBitmapConfig = defaultBitmapConfig; int builtInHandlers = 7; // Adjust this as internal handlers are added or removed. int extraCount = (extraRequestHandlers ! = null ? extraRequestHandlers.size() : 0); List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount); // ResourceRequestHandler needs to be the firstinthe list to avoid // forcing other RequestHandlers to perform null checks on request.uri // to cover the (request.resourceId ! = 0) case. allRequestHandlers.add(new ResourceRequestHandler(context));if(extraRequestHandlers ! = null) { allRequestHandlers.addAll(extraRequestHandlers); } 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)); requestHandlers = Collections.unmodifiableList(allRequestHandlers); this.stats = stats; this.targetToAction = new WeakHashMap<>(); this.targetToDeferredRequestCreator = new WeakHashMap<>(); this.indicatorsEnabled = indicatorsEnabled; this.loggingEnabled = loggingEnabled; this.referenceQueue = new ReferenceQueue<>(); this.cleanupThread = new CleanupThread(referenceQueue, HANDLER); this.cleanupThread.start(); }Copy the code

RequestCreator object

  • The creation is created by calling a series of load() overload methods provided in the Picasso class, which ultimately translates into calls to the load method whose parameters are Uri objects

    Public RequestCreator Load (@nullable Uri Uri) {// this is the Picasso objectreturn new RequestCreator(this, uri, 0);
      }
      
    RequestCreator(Picasso picasso, Uri uri, int resourceId) {
      if (picasso.shutdown) {
        throw new IllegalStateException(
            "Picasso instance already shut down. Cannot submit new requests."); } // Save the Picasso object as a member variable this.picasso = Picasso; Request.Builder is used to create a Request object this.data = new Request.Builder(URI, resourceId, picasso.defaultBitmapConfig); }Copy the code
  • Member variables

    private static final AtomicInteger nextId = new AtomicInteger();
    
    private final Picasso picasso;
    private final Request.Builder data;
    
    private boolean noFade;
    private boolean deferred;
    private boolean setPlaceholder = true; private int placeholderResId; Private int errorResId; Private int memoryPolicy; // memoryPolicy; private int networkPolicy; private Drawable placeholderDrawable; // Drawable (Drawable) private Drawable errorDrawable; (Drawable) Private Object tag;Copy the code
  • The RequestCreator function Picasso divides the image loading Request into two parts, one for the RequestCreator and the other for the Request, which really describes the loading Request data for the target image. Including the address of the target image, the conversion operation of the target image, the width and height of the image, the priority of the load request and so on; The Builder can be used to build the Request object. In addition, it also provides some strategic information about the image loading, such as the placeholders before the image loading and the placeholders when the image loading fails.

  • Methods in RequestCreator are primarily used to assign values to themselves and member variables in Request.Builder, supporting chained calls. Implementations of methods mostly follow two patterns: one is copied directly to its own members, and the other is proxyed to data members.

    Public RequestCreator placeholder(@drawableres int tablespace resid) { placeholderResId;returnthis; } public RequestCreator Transform (@nonnull Transformation Transformation) {// Give to Request.Builder data.transform(transformation);return this;
    }
    Copy the code
  • The into method is used to configure the image loading request. After calling into, the image loading will actually start. A normal image load request can be described in the following steps by analyzing the source code: Create a Request object using the Data member of RequestCreator. 2. Attempt to load an image from the cache (return if it is successfully loaded). 5. Submit the Action task through the Picasso object

    Public void into(ImageView target) {into(target, null); } public void into(ImageView target, Callback callback) { long started = System.nanoTime(); checkMain();if (target == null) {
        throw new IllegalArgumentException("Target must not be null."); } // The image URI or resource ID is emptyif(! data.hasImage()) { picasso.cancelRequest(target); // Cancel the requestif (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable()); // Set to a placeholder image}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); } // Create a Reqest object from the data member Request Request = createRequest(started); String requestKey = createKey(request); // Whether to obtain from cacheif (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; }} // Set it as a placeholderif (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable()); Action Action = new ImageViewAction(Picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); // Submit action Picason.enqueueandSubmit (action) through the Picason.enqueueandSubmit (action); }Copy the code

The cache

  • Cache interface

    get(String): Bitmap
    set(String, Bitmap): void size(): int maxSize(): int // Maximum cache size bytes clear(): void ckearKeyUri(String): voidCopy the code
  • The implementation class of LruCache Cache interface inherits LruCache in Android system directly

Action

Abstract class that describes an image loading task

  • Member variables

    final Picasso picasso; final Request request; // Final WeakReference<T> target; WeakReference<T> target; WeakReference<T> target; final boolean noFade; final int memoryPolicy; final int networkPolicy; final int errorResId; final Drawable errorDrawable; final String key; final Object tag; boolean willReplay; boolean cancelled;Copy the code
  • Abstract methods

    abstract void complete(Bitmap result, Picasso.LoadedFrom from);
    abstract void error(Exception e);
    Copy the code
  • ImageViewAction is a subclass of Action. The following is a rework of the two methods in the Action. The main logic is to set the ImageView image when the load is successful, and set the image as a placeholder when the load fails

    @Override public void complete(Bitmap result, Picas.loadedfrom from) {// get target ImageView target = this.target.get();if (target == null) {
        return; } Context context = picasso.context; boolean indicatorsEnabled = picasso.indicatorsEnabled; / / set the ImageView image PicassoDrawable. SetBitmap (target, the context, the result, the 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(); } // Set a placeholder for loading errorsif(errorResId ! = 0) { target.setImageResource(errorResId); }else if(errorDrawable ! = null) { target.setImageDrawable(errorDrawable); }if (callback != null) {
        callback.onError(e);
      }
    }
    Copy the code

picasso.enqueueAndSubmit(action)

This method eventually commits the Action through the Dispatcher

void enqueueAndSubmit(Action action) {
  Object target = action.getTarget();
  if(target ! = null && targetToAction.get(target) ! = action) { // This will also check we are on the main thread. cancelExistingRequest(target); targetToAction.put(target, action); } submit(action); } void submit(Action action) { dispatcher.dispatchSubmit(action); }Copy the code

Dispatcher

The class to which the Dispatcher member belongs in the Picasso object is mainly used to dispatch image loading tasks

  • The DispatcherThread inner class inherits HandlerThread and is a thread with Looper

    static class DispatcherThread extends HandlerThread {
      DispatcherThread() { super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND); }}Copy the code
  • DispatcherHandler inner class

    private static class DispatcherHandler extends Handler {
      private final Dispatcher dispatcher;
    
      DispatcherHandler(Looper looper, Dispatcher dispatcher) {
        super(looper);
        this.dispatcher = dispatcher;
      }
    
      @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); }}); }}}Copy the code
  • Initialization of DispatcherThread and DispatcherHandler

    this.dispatcherThread = new DispatcherThread(); / / start dispatcherThread this thread. DispatcherThread. Start (); / / handler is dispatcherThread handler enclosing handler = new DispatcherHandler (dispatcherThread. GetLooper (), this);Copy the code
  • The Action method mentioned above is finally submitted through dispatchSubmit of the Dispatcher. You can see that the main logic is to wrap the Action into a Message and send the Message through a handler to a DispatcherThread (a separate thread) for processing. Judging from the handling of REQUEST_SUBMIT messages by the DispatcherHandler above, it will eventually enter the performSubmit method

    void dispatchSubmit(Action action) {
      handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
    }
    
    
    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; } // Get BitmapHunter from hunterMap 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; } // Create BitmapHunter hunter =forRequest(action.getPicasso(), this, cache, stats, action); PicassoExecutorService Hunter. Future = service.submit(hunter); // Submit BitmapHunter, service is a thread pool object PicassoExecutorService Hunter. Huntermap.put (action.getKey(), hunter); // Save BitmapHunter to hunterMap hunterMap.put(action.getKey(), hunter);if (dismissFailed) {
        failedActions.remove(action.getTarget());
      }
    
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId()); }}Copy the code

BitmapHunter

Implement the Runnable interface

  • Run method

    @Override public void run() { try { updateThreadName(data); Result = hunt(); // The processing of the result involves thread switchingif(the result = = null) {/ / picture loaded dispatcher. The dispatchFailed (this); }else{/ / picture the dispatcher load failure. The dispatchComplete (this); }} the catch (NetworkRequestHandler ResponseException e) {/ / exception handling} finally { Thread.currentThread().setName(Utils.THREAD_IDLE_NAME); }}Copy the code
  • The core logic of the Hunt method is to load the image using RequestHandler

    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");
          }
          returnbitmap; } } networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy; Requesthandler. Result Result = requesthandler. load(data, networkPolicy);if(result ! = null) { loadedFrom = result.getLoadedFrom(); exifOrientation = result.getExifOrientation(); bitmap = result.getBitmap(); // If there was no Bitmapthen we need to decode it from the stream.
        if (bitmap == null) {
          Source source= result.getSource(); Try {// create a Bitmap object Bitmap = decodeStream(source, data);
          } finally {
            try {
              //noinspection ConstantConditions If bitmap is null then sourceis guranteed non-null. source.close(); } catch (IOException Ignored) {}}}} // Perform transformer related operationsif(bitmap ! = null) {if (picasso.loggingEnabled) {
          log(OWNER_HUNTER, VERB_DECODED, data.logId());
        }
        stats.dispatchBitmapDecoded(bitmap);
        if(data.needsTransformation() || exifOrientation ! = 0) { synchronized (DECODE_LOCK) {if(data.needsMatrixTransform() || exifOrientation ! = 0) { bitmap = transformResult(data, bitmap, exifOrientation);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;
    }
    Copy the code

RequestHandler

An abstract class, RequestHandler allows you to extend Picasso to load images in ways that are not supported by default in the library. RequestHandler must be subclassed to be used. You will have to override two methods canHandleRequest(Request) and load(Request, int) with your custom logic to load images. You should then register your RequestHandler using Picasso.Builder#addRequestHandler(RequestHandler)

  • NetworkRequestHandler

    An implementation class of RequestHandler
    • The load method

      NetworkRequestHandler load method using OkHttp to complete the image download by analyzing the source code
      @Override public Result load(Request request, Int networkPolicy) throws IOException {// Create an OkHttp Request object okHttp3. Request downloaderRequest = createRequest(request, networkPolicy); // The downloader creates an OkHttp3Downloader in the build method of Picas.Builder. // The OkHttp3Downloader contains an instance of OKHttpClient inside //returnclient.newCall(request).execute(); Response Response = downloader.load(downloaderRequest); ResponseBody body = response.body();if(! response.isSuccessful()) { body.close(); throw new ResponseException(response.code(), request.networkPolicy); } // Cache response is only null when the response comes fully from the network. Both completely // cached and conditionally cached responses will have a non-null cache response. Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK; // Sometimes response content length is zero when requests are being replayed. Haven't found // root cause to this but retrying the request seems safe to do so. if (loadedFrom == DISK && body.contentLength() == 0) { body.close(); throw new ContentLengthException("Received response with 0 content-length header."); } if (loadedFrom == NETWORK && body.contentLength() > 0) { stats.dispatchDownloadFinished(body.contentLength()); } return new Result(body.source(), loadedFrom); }Copy the code

OkHttp3Downloader

There are multiple overloaded constructors that eventually call the constructor with the OKHttpClient parameter, providing the load method to actually perform the image loading. The main logic is done through client.newCall(request).execute(), This is a typical OkHttp synchronous request method, since the request is already running in the thread pool and can therefore be executed synchronously without worrying about blocking the main thread.

PicassoExecutorService

Picasso is the pool of threads that perform image-loading tasks, inherited from ThreadPoolExecutor. This is created in the build method of Picas.Builder