Picasso2.71源码解析之图片加载框架的那些事儿

上一系列我们讲述了Glide的简单使用和其源码解析,还没看的同学可以移步去浏览:

Glide4.9图片框架(一)之加载图片的常规使用方式

讲完了Glide及其源码,我们继续看看同样作为图片加载框架的Picasso,内部源码就是怎样的呢,这里就不去分析它的使用了,因为和Glide如出一辙,我们主要还是看内部源码的加载流程,本次源码版本为2.71828。

Picasso的简单使用

     Picasso.get()
            //相当于glide的with方法,初始化Picasso
            .load("url")
            //传入图片url,构建requestCreater
            .placeholder(R.drawable.error)
            //设置占位图
            .error(R.drawable.error)
            //设置加载错误占位图
            .resize(480,800)
            //设置加载图片的宽高
            .centerCrop()
             //设置scyletype
            .rotate(90)
            //设置旋转90度
            .priority(Picasso.Priority.HIGH)
            //加载的优先级,不是决定性的因素
            .tag("list view")
            //给加载添加tag
            .memoryPolicy(MemoryPolicy.NO_CACHE)
            //设置没有内存缓存
            .into(imageview);

Picasso.get()

 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;
  }

get方法采用的双重检查锁实现的单例,其中对PicassoProvider.context 判空,这里的context是什么呢,我们跟进去看一下:

public final class PicassoProvider extends ContentProvider {

  @SuppressLint("StaticFieldLeak") static Context context;

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

  @Nullable @Override
  public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
      @Nullable String[] selectionArgs, @Nullable String sortOrder) {
    return null;
  }

  @Nullable @Override public String getType(@NonNull Uri uri) {
    return null;
  }

  @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
    return null;
  }

  @Override public int delete(@NonNull Uri uri, @Nullable String selection,
      @Nullable String[] selectionArgs) {
    return 0;
  }

  @Override
  public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
      @Nullable String[] selectionArgs) {
    return 0;
  }
}

可以看到创建这个contentprovider就是为了获取context,从而不需要从get方法里每次再传入context,那么回到上面的get方法,我们继续看singleton = new Builder(PicassoProvider.context).build()中的Builder构造方法:

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

这里是直接获取ApplicationContext,因此图片的加载是跟application绑定的。继续看build()方法:

public Picasso build() {
      Context context = this.context;
      //创建下载器,这里直接封装了OkHttp3的请求框架作为下载器
      if (downloader == null) {
        downloader = new OkHttp3Downloader(context);
      }
      // 创建LruCache缓存
      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, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }

build()之一 OkHttp3Downloader

public OkHttp3Downloader(final Context context) {
    this(Utils.createDefaultCacheDir(context));
  }

  static File createDefaultCacheDir(Context context) {
    File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);
    if (!cache.exists()) {
      //noinspection ResultOfMethodCallIgnored
      cache.mkdirs();
    }
    return cache;
  }

public OkHttp3Downloader(final File cacheDir) {
    this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
  }

  static long calculateDiskCacheSize(File dir) {
    long size = MIN_DISK_CACHE_SIZE;
    try {
      StatFs statFs = new StatFs(dir.getAbsolutePath());
      //noinspection deprecation
      long blockCount =
          SDK_INT < JELLY_BEAN_MR2 ? (long) statFs.getBlockCount() : statFs.getBlockCountLong();
      //noinspection deprecation
      long blockSize =
          SDK_INT < JELLY_BEAN_MR2 ? (long) statFs.getBlockSize() : statFs.getBlockSizeLong();
      long available = blockCount * blockSize;
      // Target 2% of the total space.
      size = available / 50;
    } catch (IllegalArgumentException ignored) {
    }
    // Bound inside min/max size for disk cache.
    return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);
  }

  public OkHttp3Downloader(final File cacheDir, final long maxSize) {
    this(new OkHttpClient.Builder().cache(new Cache(cacheDir, maxSize)).build());
    sharedClient = false;
  }

先看OkHttp3Downloader的构造方法,这里的OkHttp3Downloader是默认基于okhttp封装的请求下载器,我们看下createDefaultCacheDir方法,根据命名其实可以猜出这个方法就是创建默认的缓存文件的目录,而这个目录的路径就是context.getApplicationContext().getCacheDir(),然后回到OkHttp3Downloader的this方法,继续跟下去调用了calculateDiskCacheSize方法并把缓存目录传进去,跟进去看,这个方法就是计算默认缓存文件的大小,通过获取本地存储空间来计算能缓存的最大文件空间长度是多少。然后继续看最下面的OkHttp3Downloader的this方法,通过新建OkHttpClient的Builder设置缓存的目录和size并完成初始化。

build()之二 LruCache内存缓存

public final class LruCache implements Cache {
  final android.util.LruCache cache;

  /** Create a cache using an appropriate portion of the available RAM as the maximum size. */
  public LruCache(@NonNull Context context) {
    this(Utils.calculateMemoryCacheSize(context));
  }
0
  /** Create a cache with a given maximum size in bytes. */
  public LruCache(int maxByteCount) {
    cache = new android.util.LruCache(maxByteCount) {
      @Override protected int sizeOf(String key, BitmapAndSize value) {
        return value.byteCount;
      }
    };
  }

 static int calculateMemoryCacheSize(Context context) {
    ActivityManager am = getService(context, ACTIVITY_SERVICE);
    boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
    int memoryClass = largeHeap ? am.getLargeMemoryClass() : am.getMemoryClass();
    // Target ~15% of the available heap.
    return (int) (1024L * 1024L * memoryClass / 7);
  }

这里我们还是只看简单的构造方法吧,其实内部持有了一个LruCache引用,相当于自己封装了一层LruCache,通过calculateMemoryCacheSize方法,计算能此初始化的内存缓存的大小并完成LruCache初始化。这里是获取app能用缓存的七分之一大小设置内存缓存,关于LRUCache后面我们会单独拿出来分析源码,只要理解作为最近最少的使用的缓存优先会被回收就行了,我们接着看下面:

build()之三 PicassoExecutorService线程池

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

class PicassoExecutorService extends ThreadPoolExecutor {
  private static final int DEFAULT_THREAD_COUNT = 3;

  PicassoExecutorService() {
    super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
            new PriorityBlockingQueue(), new Utils.PicassoThreadFactory());
  }
}

 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) {
        throw new RuntimeException("Stub!");
    }

下面初始化了一个自定义的线程池PicassoExecutorService,继承的还是自带的线程池ThreadPoolExecutor,那么构造方法里传入的五个参数分别表示什么意思呢

  • corePoolSize
    表示默认初始化的核心线程数
  • maximumPoolSize
    表示线程池能创建的最大线程个数,超过则不会继续创建
  • keepAliveTime
    线程最大空闲时间
  • TimeUnit
    时间单位
  • BlockingQueue
    线程等待队列
  • ThreadFactory
    线程创建工厂

build()之四 Dispatcher

class Dispatcher {

  Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
      Downloader downloader, Cache cache, Stats stats) {
    this.dispatcherThread = new DispatcherThread();
    this.context = context;
    this.service = service;
    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
    this.downloader = downloader;
    this.mainThreadHandler = mainThreadHandler;
    this.cache = cache;
    this.stats = stats;
    this.receiver = new NetworkBroadcastReceiver(this);
    receiver.register();
  }

Dispatcher是一个比较重要的类,这里只截出了几个比较关键的参数信息,dispatcher里包含了缓存信息,请求结果的调度,状态的切换,其中最重要的两个handler,DispatcherHandler和mainThreadHandler ,我们来看看内部的消息的处理是怎么样的:

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);
            }
          });
      }
    }
  }

可以看到DispatcherHandler里面全是对各种状态和结果的回调和处理,DispatcherHandler的构造函数传入的是dispatcherThread.getLooper(),那继续看看这个dispatcherThread是什么意思:

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

其实就是继承的HandlerThread,这里不去细看,dispatcher的构造方法中还有一个this.receiver = new NetworkBroadcastReceiver(this).那我们重点看下这个广播接收者的onReceive方法:

 @Override public void onReceive(Context context, Intent intent) {
      if (intent == null) {
        return;
      }
      final String action = intent.getAction();
      if (ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
        if (!intent.hasExtra(EXTRA_AIRPLANE_STATE)) {
          return; // No airplane state, ignore it. Should we query Utils.isAirplaneModeOn?
        }
        dispatcher.dispatchAirplaneModeChange(intent.getBooleanExtra(EXTRA_AIRPLANE_STATE, false));
      } else if (CONNECTIVITY_ACTION.equals(action)) {
        ConnectivityManager connectivityManager = getService(context, CONNECTIVITY_SERVICE);
        dispatcher.dispatchNetworkStateChange(connectivityManager.getActiveNetworkInfo());
      }
    }

其实在接收广播中可以看到对飞行模式进行了监听,ACTION_AIRPLANE_MODE_CHANGED,如果是飞行模式,就调用dispatcher.dispatchAirplaneModeChange方法,跟进去看一下:

 void dispatchAirplaneModeChange(boolean airplaneMode) {
    handler.sendMessage(handler.obtainMessage(AIRPLANE_MODE_CHANGE,
        airplaneMode ? AIRPLANE_MODE_ON : AIRPLANE_MODE_OFF, 0));
  }
 void performAirplaneModeChange(boolean airplaneMode) {
    this.airplaneMode = airplaneMode;
  }

这里最终就是记录一个是否为飞行模式的布尔值,我们继续看下面的判读网络状态链接的部分,dispatcher.dispatchNetworkStateChange:

  void performNetworkStateChange(NetworkInfo info) {
    if (service instanceof PicassoExecutorService) {
      ((PicassoExecutorService) service).adjustThreadCount(info);
    }
    if (info != null && info.isConnected()) {
      flushFailedActions();
    }
  }

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);
    }
  }

这里重点分析一下adjustThreadCount方法,当网络状态改变的时候,根据当前网络的类型,创建不同的核心线程数,因为网络的影响,可能线程池的核心线程数也受到影响,这个设计是相当有意思的
,也是Glide中没有去做的东西,对网络差的图片加载来说,提高了加载的效率。继续看flushFailedActions();方法:

  private void flushFailedActions() {
    if (!failedActions.isEmpty()) {
      Iterator iterator = failedActions.values().iterator();
      while (iterator.hasNext()) {
        Action action = iterator.next();
        iterator.remove();
        if (action.getPicasso().loggingEnabled) {
          log(OWNER_DISPATCHER, VERB_REPLAYING, action.getRequest().logId());
        }
        performSubmit(action, false);
      }
    }
  }

failedActions这里存储的是第一次请求失败需要重新请求的集合,那么这里是空的,我们不继续往下分析了,到这里整个Picasso的build方法就结束了,我们继续往下分析load方法:

Picasso.load()

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

  public RequestCreator load(@Nullable 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));
  }

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

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

Picasso的load方法,相对于Glide来说比较少,只有四个,分别是Uri、path、file、Drawable。Uri、path、file最中都是调用的load(@Nullable Uri uri) 方法,Drawable也简单,这里我们就看传入字符串的情况。最终调用的new RequestCreator(this, uri, 0)方法,我们跟进去看RequestCreator主要是有什么作用:

RequestCreator

  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);
  }
   Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
      this.uri = uri;
      this.resourceId = resourceId;
      this.config = bitmapConfig;
    }

看代码才发现RequestCreator其实是request的build的包装类,由它生成request的build类,然后build才能创建request,还有一个作用就是RequestCreator这里包装了一系列的链式调用,包括placeholder、error、fit、centerCrop等等。到这里load方法就结束了,最终我们直接看into方法:

RequestCreator.into(ImageView)

  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.");
    }
    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);
  }

    boolean hasImage() {
      return uri != null || resourceId != 0;
    }

     public RequestCreator fit() {
      deferred = true;
      return this;
     }

    boolean hasSize() {
      return targetWidth != 0 || targetHeight != 0;
    }

checkMain是检查时否在主线程,不是则抛出异常,我们直接看data.hasImage()方法,这里的data就是我们load时创建的request的build,而hasImage就是判读当前加载的资源是不是空,如果是空则调用picasso.cancelRequest取消请求并且设置占位图直接return。

接下来看deferred变量,这里只有我们设置了fit才会去执行,那么假设我们设了fit,data.hasSize实际上是判断我们是否设置过resize方法,如果设置了,那么不能再调用fit方法,因为这两者是冲突的。我们继续看通过target也就是传入的image view获取宽和高,如果获取到的宽或者高有一个为0,表示imageview还没能绘制成功,这里设置占位图,并且调用picasso.defer方法,我们跟进去看一下:

class DeferredRequestCreator implements OnPreDrawListener, OnAttachStateChangeListener {
  private final RequestCreator creator;
  @VisibleForTesting final WeakReference target;
  @VisibleForTesting Callback callback;
  DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) {
    this.creator = creator;
    this.target = new WeakReference<>(target);
    this.callback = callback;
    target.addOnAttachStateChangeListener(this);
    if (target.getWindowToken() != null) {
      onViewAttachedToWindow(target);
    }
  }

  @Override public void onViewAttachedToWindow(View view) {
    view.getViewTreeObserver().addOnPreDrawListener(this);
  }

  @Override public void onViewDetachedFromWindow(View view) {
    view.getViewTreeObserver().removeOnPreDrawListener(this);
  }

  @Override public boolean onPreDraw() {
    ImageView target = this.target.get();
    if (target == null) {
      return true;
    }
    ViewTreeObserver vto = target.getViewTreeObserver();
    if (!vto.isAlive()) {
      return true;
    }
    int width = target.getWidth();
    int height = target.getHeight();
    if (width <= 0 || height <= 0) {
      return true;
    }
    target.removeOnAttachStateChangeListener(this);
    vto.removeOnPreDrawListener(this);
    this.target.clear();
    this.creator.unfit().resize(width, height).into(target, callback);
    return true;
  }

这里其实大致看出来DeferredRequestCreator 实现了OnPreDrawListener和OnAttachStateChangeListener ,那么就是在image view绘制成功后,重新再去获取宽和高,并且调用creator.unfit().resize反法重新给build也就是data传递了新的图片宽高。并将这个DeferredRequestCreator存储到targetToDeferredRequestCreator集合中。那么回到上面的into方法,如果没有设置或fit,则开始调用Request request = createRequest(started)创建一个请求,我们跟进去看是如何创建的。

createRequest()

private Request createRequest(long started) {
    int id = nextId.getAndIncrement();
    Request request = data.build();
    request.id = id;
    request.started = started;
    boolean loggingEnabled = picasso.loggingEnabled;
    if (loggingEnabled) {
      log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());
    }
    Request transformed = picasso.transformRequest(request);
    if (transformed != request) {
      // If the request was changed, copy over the id and timestamp from the original.
      transformed.id = id;
      transformed.started = started;
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed);
      }
    }
    return transformed;
  }

public Request build() {
      ......
      if (priority == null) {
        priority = Priority.NORMAL;
      }
      return new Request(uri, resourceId, stableKey, transformations, targetWidth, targetHeight,
          centerCrop, centerInside, centerCropGravity, onlyScaleDown, rotationDegrees,
          rotationPivotX, rotationPivotY, hasRotationPivot, purgeable, config, priority);
    }

这里调用了data.build()创建Request,我们进去看一下,直看重点,直接new了一个Request对象,传入了很多参数,比如uri、资源id、key、转换、宽高、旋转、scaletype、优先级等。然后回到build方法,调用了transformRequest方法,其实如果没有设置transform,这里等于没有变化,所以回到createRequest方法,通过createKey(request)创建了一个请求key,然后调用了shouldReadFromMemoryCache方法,这里如果我们设置了可以存储到内存缓存,那么是会先去从内存缓存读取的,所以看看下面的Picasso的quickMemoryCacheCheck方法:

获取内存缓存

  Bitmap quickMemoryCacheCheck(String key) {
    Bitmap cached = cache.get(key);
    if (cached != null) {
      stats.dispatchCacheHit();
    } else {
      stats.dispatchCacheMiss();
    }
    return cached;
  }

  void dispatchCacheHit() {
    handler.sendEmptyMessage(CACHE_HIT);
  }
@Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        case CACHE_HIT:
          stats.performCacheHit();
          break;
        case CACHE_MISS:
          stats.performCacheMiss();
          break;
        case BITMAP_DECODE_FINISHED:
          stats.performBitmapDecoded(msg.arg1);
          break;
        case BITMAP_TRANSFORMED_FINISHED:
          stats.performBitmapTransformed(msg.arg1);
          break;
        case DOWNLOAD_FINISHED:
          stats.performDownloadFinished((Long) msg.obj);
          break;
        default:
          Picasso.HANDLER.post(new Runnable() {
            @Override public void run() {
              throw new AssertionError("Unhandled stats message." + msg.what);
            }
          });
      }
    }
 void performCacheHit() {
    cacheHits++;
  }
  void performCacheMiss() {
    cacheMisses++;
  }

这里很简单,直接从Picasso的cache中获取缓存的bitmap,如果没有则调用stats.dispatchCacheHit()方法,stats其实就是对各种结果和回调做一个通知的作用。也可以看一下,最终调用到performCacheHit,对成功获取的缓存数++;那么dispatchCacheMiss应该就是没拿到缓存的计数++了。

回到上面的quickMemoryCacheCheck方法返回的bitmap,,如果bitmap 不为空表示获取缓存成功,没必要继续请求了,因此调用cancelRequest,然后调用setBitmap方法:

  static void setBitmap(ImageView target, Context context, Bitmap bitmap,
      Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
    Drawable placeholder = target.getDrawable();
    if (placeholder instanceof Animatable) {
      ((Animatable) placeholder).stop();
    }
    PicassoDrawable drawable =
        new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
    target.setImageDrawable(drawable);
  }

直接看重点的一行,target.setImageDrawable(drawable),到这里就是将bitmap设置到imageview了,PicassoDrawable 其实是对bitmap进行的封装。再回到获取内存缓存并setbitmap的位置,这里我们第一次加载应该是没有缓存的,所以我们继续看下去,没有缓存的情况。

setPlaceholder就是设置占位符,这个不是重点,看下面的new ImageViewAction方法,传入了picasso, target, request, memoryPolicy, networkPolicy, errorResId,errorDrawable, requestKey, tag, callback, noFade一系列参数,生成了一个ImageViewAction,那么我们重点看这个action是干嘛的:

class ImageViewAction extends Action {

  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;
  }

  @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);
    }
  }

ImageViewAction 主要实现的方法就是complete和error方法,这两个的调用还是父类action中的,主要是对请求完成后的回调做处理,设置成功的图片或者设置加载出错的图片。所以主要的功能还是再父类Action中。回到ImageViewAction创建完成后,into方法的最后一行,调用的是picasso.enqueueAndSubmit(action)执行这个action,我们跟进去看一下:

执行请求

  void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    if (target != null && targetToAction.get(target) != action) {
      cancelExistingRequest(target);
      targetToAction.put(target, action);
    }
    submit(action);
  }

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

  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) {
    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());
    }
  }

这里的if判断,就是判断如果之前存储过跟这次不一样的加载请求,那么取消掉上一次的加载,存储当前这一次的加载请求,然后调用submit方法,我们看submit方法通过dispatcher下发了提交请求任务的消息,最终调用到了performSubmit方法。这里我们看第一个if判断

在了解判断之前,来看看这个pausedTags是什么,怎么赋值的:

public class SampleScrollListener implements AbsListView.OnScrollListener {
  private final Context context;

  public SampleScrollListener(Context context) {
    this.context = context;
  }

  @Override
  public void onScrollStateChanged(AbsListView view, int scrollState) {
    final Picasso picasso = Picasso.get();
    if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_TOUCH_SCROLL) {
      picasso.resumeTag(context);
    } else {
      picasso.pauseTag(context);
    }
  }
}

最终发现,pausedTags添加的地方,就是滚动监听,当屏幕暂停时,就将当前的context存储到暂停的tag中,否则就从暂停的pausedTags中移除。那么回到上面的performSubmit方法我们就明白了,意思就是只有当屏幕没有滑动的时候,才允许去加载当前的请求,那么继续往下看,hunterMap.get方法获取到了BitmapHunter,这里的action.getKey()就是前面我们获取内存缓存的key,那么bitmapHunter是什么呢,他说一个实现了Runnable接口的类,所有的耗时操作包括请求图片,解码图片,图片转换等都在bitmapHunter中执行。这里获取BitmapHunter 后去判断是否为空,如果不为空表示当前正在执行请求,那么就不会再去执行直接return,否则我们看下面的forRequest方法:

  static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
      Action action) {
    Request request = action.getRequest();
    List requestHandlers = picasso.getRequestHandlers();
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }
    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
  }

这里没有BitmapHunter ,那么就去创建一个BitmapHunter 并且返回,通过找到对应的requestHandlers 去生成对应的BitmapHunter,这里我们地方requestHandler应该就是请求相关的NetworkRequestHandler,我们回到返回值的地方,下面调用service.submit(hunter)表示去执行请求,请求的结果存储在hunter.future中,完成后缓存到hunterMap中,我们跟进去看一下:

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

@Override public void run() {
    try {
      updateThreadName(data);
      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
      }
      result = hunt();
      if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
        dispatcher.dispatchComplete(this);
      }
    } catch (NetworkRequestHandler.ResponseException e) {
      if (!NetworkPolicy.isOfflineOnly(e.networkPolicy) || e.code != 504) {
        exception = e;
      }
      dispatcher.dispatchFailed(this);
    } catch (IOException e) {
      exception = e;
      dispatcher.dispatchRetry(this);
    } catch (OutOfMemoryError e) {
      StringWriter writer = new StringWriter();
      stats.createSnapshot().dump(new PrintWriter(writer));
      exception = new RuntimeException(writer.toString(), e);
      dispatcher.dispatchFailed(this);
    } catch (Exception e) {
      exception = e;
      dispatcher.dispatchFailed(this);
    } finally {
      Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
    }
  }

这里直接执行execute方法,那么我们就看BitmapHunter的run方法了,直接看result = hunt();这一行,可以看到通过hunt方法拿到请求的返回值了,result是一个Bitmap类型,那么我们就重点看hunt方法了。

hunt()

  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;
      }
    }

    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 Bitmap then we need to decode it from the stream.
      if (bitmap == null) {
        Source source = result.getSource();
        try {
          bitmap = decodeStream(source, data);
        } finally {
          try {
            //noinspection ConstantConditions If bitmap is null then source is guranteed non-null.
            source.close();
          } catch (IOException ignored) {
          }
        }
      }
    }

    if (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;
  }

这里我们又调了一次shouldReadFromMemoryCache,这里再次获取内存缓存也没有冲突,防止前面请求的时候获取缓存不及时,这里我们直接看requestHandler.load返回RequestHandler.Result的方法,跟进去看一下:

  @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();
    if (!response.isSuccessful()) {
      body.close();
      throw new ResponseException(response.code(), request.networkPolicy);
    }
    Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
    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);
  }

  @NonNull @Override public Response load(@NonNull Request request) throws IOException {
    return client.newCall(request).execute();
  }

到这里就是直接通过okhttp3请求去获取结果了,可能你奇怪为什么看到这里一直都是内存缓存,没有看到过磁盘缓存,因为磁盘缓存是在okhttp3请求框架里面自己去做了,所以这个代码里面是没有的。看downloader.load方法内部其实还是使用的OK HTTP的newCall创建请求并执行的,那么回调结果,返回上一层通过result.getBitmap()获取到bitmap对象,如果为空,那么就再去解码拿到Source并转换成bitmap。后面就是回调了,返回到BitmapHunter的Run方法,拿到bitmap后调用dispatcher.dispatchComplete(this)方法:

拿到bitmap,回调结果

  void performComplete(BitmapHunter hunter) {
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    hunterMap.remove(hunter.getKey());
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
    }
  }

在拿到hunter后,立马通过判断是否需要内存缓存来决定是否将结果缓存到内存缓存,而下面的hunterMap也在请求成功后移除对应的请求缓存,然后调用batch方法:

  private void batch(BitmapHunter hunter) {
    if (hunter.isCancelled()) {
      return;
    }
    if (hunter.result != null) {
      hunter.result.prepareToDraw();
    }
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
      handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
  }

拿到hunter.result后调用bitmap的prepareToDraw方法准备填充到image view,然后存储到batch中。方便后面对bitmapHunter同意管理:

case HUNTER_BATCH_COMPLETE: {
          @SuppressWarnings("unchecked") List batch = (List) msg.obj;
          //noinspection ForLoopReplaceableByForEach
          for (int i = 0, n = batch.size(); i < n; i++) {
            BitmapHunter hunter = batch.get(i);
            hunter.picasso.complete(hunter);
          }
          break;
        }

  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, exception);
    }
    if (hasMultiple) {
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, n = joined.size(); i < n; i++) {
        Action join = joined.get(i);
        deliverAction(result, from, join, exception);
      }
    }
    if (listener != null && exception != null) {
      listener.onImageLoadFailed(this, uri, exception);
    }
  }

第一行的single也就是表示是否只有这一个,如果是则获取到bitmap后直接回调,否则就遍历每一个Action都调用deliverAction回调,我们继续看deliverAction方法:

private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
    if (action.isCancelled()) {
      return;
    }
    if (!action.willReplay()) {
      targetToAction.remove(action.getTarget());
    }
    if (result != null) {
      if (from == null) {
        throw new AssertionError("LoadedFrom cannot be null.");
      }
      action.complete(result, from);
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
      }
    } else {
      action.error(e);
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_ERRORED, action.request.logId(), e.getMessage());
      }
    }
  }

重点还是关注在action.complete(result, from)方法:

  @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();
    }
  }

 static void setBitmap(ImageView target, Context context, Bitmap bitmap,
      Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
    Drawable placeholder = target.getDrawable();
    if (placeholder instanceof Animatable) {
      ((Animatable) placeholder).stop();
    }
    PicassoDrawable drawable =
        new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
    target.setImageDrawable(drawable);
  }

最终调用action.complete实际上还是调用的image viewAction,因为一开始的into方法,我们创建的action就是image viewAction,因此在complete中可以看到target.get()获取我们传进来的imageview,然后调用PicassoDrawable的setBitmap方法设置图片到view,最终调用callback.onSuccess()回调到我们使用Picasso加载图片到地方,前提是我们设置了这个callback。

总结

Picasso通过get方法创建单例实例,然后通过build方法创建请求下载器、内存缓存、线程池和Dispatcher ,并最终生成Picasso对象。磁盘缓存和内存缓存都是根据手机容量和内存来判断的,内存为app可用内存的七分之一。Dispatcher则在全程中起到的一个调度的作用,回调和传递各种信息,内部有一个网络状态的广播监听,根据手机网络的情况2G-4G来设置线程池不同的核心线程数,然后还可以监听飞行模式。load方法则是通过RequestCreator 创建Request的build对象,最终调用into方法通过build对象创建Request对象并生成一个缓存key,通过缓存key在内存缓存中查到是否存在缓存,不存在则创建一个Action对象,通过action对象找到对应NetworkRequestHandler 并生成BitmapHunter,BitmapHunter本身是一个线程,最终调用PicassoExecutorService线程池的submit方法执行BitmapHunter,最终调用requestHandler的load方法通过OK HTTP请求做请求和磁盘缓存的操作获得Response ,然后解码拿到bitmap回调给dispatcher,然后缓存到内存,并且回调到image viewAction的complete方法,最终通过PicassoDrawable的setBitmap方法设置图片资源到image view中。

到这里Picasso的源码分析就结束了,在看过了Glide源码之后,再看Picasso可能突然觉得轻松了许多,毕竟Glide的功能还是很丰富的,类有比较复杂,所以阅读起来相对比较麻烦。那么对比我们之前分析的Glide源码,我们看看他们的区别在哪:

  • 加载数据类型的区别
    glide支持gif图加载,Picasso不支持。
  • 缓存的区别
    glide内存缓存和磁盘缓存都做了二级缓存,Picasso只有一步内存缓存,磁盘缓存还是OkHttp做的。
  • Picasso对飞行模式做了单独的监听,并对网络状态的234G做了监听,根据不同的网络设置不同的线程池核心数
  • Picasso对在滑动状态的view不会去请求,只有当停止滑动的时候才会请求
  • Glide绑定了activity的生命周期,当activity销毁则取消对应的请求和回收资源避免内存泄漏。
  • Glide的默认图片格式是RGB_565,Picasso则是ARGB_8888,所以Picasso加载的图片缓存是Glide的2倍,但是清晰度比Glide高
  • Picasso会默认加载url对应的全尺寸图片,Glide则会根据imageview的大小缓存对应尺寸的图片,所以在内存方面,Glide优于Picasso

你可能感兴趣的:(Picasso2.71源码解析之图片加载框架的那些事儿)