加载库源码浅析之 Picasso

2020-08-16  本文已影响0人  wavever

Picasso 作为 Android 上一个老牌的图片加载库,似乎近些年在 Glide 的 “打压” 下已经变的黯然失色,但作为 square 出品的优秀框架,其实现的架构和思想仍然有许多值得借鉴和学习的地方,本文所使用的 Picasso 版本号为 2.71828,在 gradle 中依赖如下:

implementation 'com.squareup.picasso:picasso:2.71828'

Picasso 加载图片的方式和 Glide 类似,都是链式调用:

Picasso.get().load("").into(target); // 异步加载并设置到 target 上
Picasso.get().load("").get(); //同步加载,不能在主线程调用
Picasso.get().load("").fetch(); // 异步加载

这里 Picasso 提供了三种方式:

使用 into 加载的过程大致上可以分为三个阶段:一是获取 Picasso 对象,对应 Picasso.get(),这一步是初始化如线程池、缓存等;二是请求封装,对应 load(url),这一步则是对资源请求的封装,例如占位图、加载失败图、tag、资源裁剪等;最后则是请求处理阶段,对应 into(ImageView) ,这一步处理的事情最为繁重,包括资源请求、资源处理等,下图为 into(ImageView) 流程的的序列图,接下来就通过围绕这个序列图来进行分析:

序列图.png

获取 Picasso 对象

这一节对应于序列图中的步骤 1、2、3。

静态方法 get() 是通过 DCL 实现的单例模式,返回一个全局的 Picass 对象:

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

这里 context 的获取是通过 PicassoProvider 实现的, PicassoProvider 继承自 ContentProvider,主要作用就是为 Picasso 提供 context 对象:

public final class PicassoProvider extends ContentProvider {

  @SuppressLint("StaticFieldLeak") static Context context;

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

并在自己的 AndroidManifest 文件中注册:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.picasso" >

    <uses-sdk android:minSdkVersion="14" />

    <application>
        <provider
            android:name="com.squareup.picasso.PicassoProvider"
            android:authorities="${applicationId}.com.squareup.picasso"
            android:exported="false" />
    </application>

</manifest>

由于在进程启动时 ContentProvider 的加载是优先于 Application 的加载,因此很多三方 SDK 也通常使用这种方式来自动进行初始化,通过在 aar 包中添加一个自定义的 ContentProvider,在其 onCreate() 方法中实现初始化逻辑。
除了通过 get() 方法,也可以通过 Picasso 的静态内部类 Builder 来自行构建,主要提供了如下方法:

// 设置 Bitmap.Config
public Builder defaultBitmapConfig(@NonNull Bitmap.Config bitmapConfig)
// 下载图片的方式,默认使用 OkHttp3Downloader
Builder downloader(@NonNull Downloader downloader)
// 线程池,默认为 PicassoExecutorService
public Builder executor(@NonNull ExecutorService executorService)
// 内存缓存,默认为 LruCache
public Builder memoryCache(@NonNull Cache memoryCache)
// 设置请求转换器,只能有一个
public Builder requestTransformer(@NonNull RequestTransformer transformer)
// 添加请求处理器,可以有多个
public Builder addRequestHandler(@NonNull RequestHandler requestHandler)

下载器

对下载行为的抽象,提供 load() 方法来加载图片并返回一个 okhttp3.Response 对象,shutdown() 方法用于对资源进行情理:

public interface Downloader {

  @NonNull Response load(@NonNull okhttp3.Request request) throws IOException; // 加载图片

  void shutdown();
}

默认只有 OkHttp3Downloader 一个实现,其内部通过 OkHttp 进行网络请求下载图片:

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

线程池

默认使用的线程池为 PicassoExecutorService,核心线程数和最大线程数数量相同,默认都为 3 个,但可根据网络状态进行动态调整:2G 网络为 1 个、3G 网络为 2 个、4G网络为 3 个、WIFI 网络为 4 个。工作队列为 PriorityBlockingQueue,是一个无界阻塞队列,可以通过自定义 compareTo() 来指定排序规则:

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<Runnable>(), new Utils.PicassoThreadFactory());
  }
    
   private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
      implements Comparable<PicassoFutureTask> {
      ...

    @Override
    public int compareTo(PicassoFutureTask other) {
      Picasso.Priority p1 = hunter.getPriority();
      Picasso.Priority p2 = other.hunter.getPriority();

      // High-priority requests are "lesser" so they are sorted to the front.
      // Equal priorities are sorted by sequence number to provide FIFO ordering.
      return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());
    }
  }
}

compareTo() 中会首先判断两个请求的优先级,如果优先级相同,则会接着判断添加的序号,sequence 是一个自增的 int 整型,也就是说先入队的请求先处理,是一种先进先出的方式。

内存缓存器

Cache 接口提供了基本的对于缓存内容的处理方法:

public interface Cache {
  Bitmap get(String key);
  void set(String key, Bitmap bitmap);
  int size();
  int maxSize(); //最大可缓存的字节数
  void clear();
  void clearKeyUri(String keyPrefix); //根据前缀移除对应的缓存
}

其有 2 个实现类,NONE 和 LruCache,其中 NONE 是空实现,LruCache 内部是对 android.util.LruCache 的封装,最大缓存大小为可用内存大小的 1/7。

请求转换器

RequestTransformer 可以理解为是 Picasso 向外提供的一个对请求进行转换的接口,用户可以通过自定义一个转换器来对生成的 Request 对象进行处理,只能设置一个,默认提供的实现为 IDENTITY 直接返回原 Request 对象,如下所示:

public interface RequestTransformer {

  Request transformRequest(Request request);
  
  RequestTransformer IDENTITY = new RequestTransformer() {
    @Override public Request transformRequest(Request request) {
      return request;
    }
  };
}

请求处理器

RequestHandler 是一个抽象类:

public abstract class RequestHandler {
    public abstract boolean canHandleRequest(Request data);
    public abstract Result load(Request request, int networkPolicy) throws IOException;
    ...
}

其作用就是在不同场景下加载图片,例如 Picasso 通过 AssetRequestHandler 来加载 asset 文件夹下的图片资源,用 NetworkRequestHandler 来加载网络图片资源等,通过遍历所有的 Handler,并调用它们的 canHandleRequest(Request data) 方法,如果返回 true,则表示对应 Handler 可以处理该请求,则该请求就会交由这个 Handler 处理。在 Picasso 的构造函数中会添加默认 Handler 和用户定义的 Handler:

Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
    RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
    Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
  ...
  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);
  // 优先添加用户自定义的 handler
  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));
  ...
}

请求封装

这一节对应于序列图中的步骤 4、5、6,主要作用就是封装 Request 对象。
Picasso 提供了 4 种图片来源的方法:

public RequestCreator load(@NonNull File file)
public RequestCreator load(@DrawableRes int resourceId)
public RequestCreator load(@Nullable Uri uri)
public RequestCreator load(@Nullable String path)

这里需要注意的是,传入的参数不能为空,不然会直接抛一个异常,因此使用前的判空非常有必要。load() 方法会返回一个 RequestCreator 对象,通过这个对象就可以来对请求做一些处理操作。RequestCreator 内部持有一个 Request 对象,所有关于请求的配置都在 Request 类中。

占位图和失败图

占位图支持 resId 和 drawable 两种,并且互斥,如果同时设置两个则会抛出异常:

public RequestCreator placeholder(@DrawableRes int placeholderResId)
public RequestCreator noPlaceholder() //不设置占位图,与设置占位图互斥

标签

public RequestCreator tag(@NonNull Object tag)

对一次请求进行标记,可用于对请求进行暂停、恢复、取消等操作。

图片处理

public RequestCreator fit() //调整图片大小为 ImageView 大小
public RequestCreator resize(int targetWidth, int targetHeight)
public RequestCreator centerCrop()
public RequestCreator centerInside()
public RequestCreator onlyScaleDown() //仅在图片大于 target 大小时对图片进行缩放处理
public RequestCreator rotate(float degrees) // bitmap 旋转角度
public RequestCreator config(@NonNull Bitmap.Config config) // bitmap config
public RequestCreator transform(@NonNull Transformation transformation)  // 对 bitmap 对象做自定义处理
public RequestCreator purgeable() // bitmap复用
public Builder transform(@NonNull Transformation transformation)

Transformation 用于对加载后的 bitmap 做处理:

public interface Transformation {
  Bitmap transform(Bitmap source);
  String key();
}

缓存策略

public RequestCreator stableKey(@NonNull String stableKey) // 设置缓存 key 值,拥有相同 key 值的资源将被视为是相同资源
public RequestCreator memoryPolicy(@NonNull MemoryPolicy policy, @NonNull MemoryPolicy... additional) // 内存缓存策略
public RequestCreator networkPolicy(@NonNull NetworkPolicy policy, @NonNull NetworkPolicy... additional) //磁盘缓存策略

MemoryPolicy 用于指定内存缓存的策略:

public enum MemoryPolicy {
  NO_CACHE(1 << 0), //请求时跳过内存缓存中查询
  NO_STORE(1 << 1); //请求后跳过缓存到内存中
  ...
}

NetworkPolicy 用于指定磁盘缓存的策略,而 Picasso 中磁盘缓存是基于 OkHttp 的缓存来实现:

public enum NetworkPolicy {
  NO_CACHE(1 << 0), //跳过从磁盘缓存查询,直接通过网络获取资源
  NO_STORE(1 << 1), //跳过写入到磁盘缓存
  OFFLINE(1 << 2); //跳过从网络获取,直接从磁盘缓存中获取
}

优先级

public RequestCreator priority(@NonNull Priority priority) // 请求优先级,用于对请求任务进行排序
public enum Priority {
  LOW,
  NORMAL,
  HIGH
}

用于在前文说到的线程池阻塞队列进行请求任务排序。

加载动画

public RequestCreator noFade() // 加载图片到 ImageView 时不显示渐变动画

// com.squareup.picasso.PicassoDrawable#draw
@Override public void draw(Canvas canvas) {
  if (!animating) {
    super.draw(canvas);
  } else {
    float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;
    if (normalized >= 1f) {
      animating = false;
      placeholder = null;
      super.draw(canvas);
    } else {
      if (placeholder != null) {
        placeholder.draw(canvas);
      }
      // setAlpha will call invalidateSelf and drive the animation.
      int partialAlpha = (int) (alpha * normalized);
      super.setAlpha(partialAlpha);
      super.draw(canvas);
      super.setAlpha(alpha);
    }
  }
}

PicassoDrawable 继承自 BitmapDrawable,通过重写 onDraw() 方法实现了渐变过渡动画。

请求处理

对应于序列图中步骤 7 以后。

请求入队

into() 中,首先会通过 createRequest() 来创建一个 Request 请求对象,然后通过 createKey() 来创建请求的 key 值,这里 key 值的创建通过 stableKey、uri、rotationDegrees、resize、centerCrop 等属性组合而成,因此即使请求的是同个资源,如果其中有任何一个属性有变化,由于生成的 key 值不同,也会重新去请求。

public void into(ImageView target, Callback callback) {
    ...
    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 (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }
    ...
    Action action = new ImageViewAction(...);
    picasso.enqueueAndSubmit(action);
}

如果没有从内存中读取到,会先创建一个 Action 对象,Action 是一个抽象类:

abstract class Action<T> {
    ...
    abstract void complete(Bitmap result, Picasso.LoadedFrom from);
    abstract void error(Exception e);
    ...
}

它有几个实现类:FetchAction、GetAction、ImageViewAction 等,分别对应了 fetch()get()into(ImageView) 方法。接着会将这个 action 对象通过 Picasso.enqueueAndSubmit() 重新又发送到了 Picasso 中:

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

请求发送

这里 action 对象最终会被传入到 Dispatcher#performSubmit() 中:

//com.squareup.picasso.Dispatcher#performSubmit()
void performSubmit(Action action, boolean dismissFailed) {
  //如果该请求已经暂停,则加入到 pausedActions 中
  if (pausedTags.contains(action.getTag())) {
    pausedActions.put(action.getTarget(), action);
    return;
  }
  BitmapHunter hunter = hunterMap.get(action.getKey());
  if (hunter != null) {
    hunter.attach(action);
    return;
  }
  //如果线程池已经关闭,则对该请求不做处理  
  if (service.isShutdown()) {
    return;
  }
  hunter = forRequest(action.getPicasso(), this, cache, stats, action);
  //将请求封装成为 Runnable 对象并发送到线程池中
  hunter.future = service.submit(hunter);
  hunterMap.put(action.getKey(), hunter);
  ...
}

forRequest() 会返回一个 BitmapHunter 对象,它继承自 Runnable,当 run() 执行时,会通过 hunt()来获取 Bitmap,这里就是真正来解析 Bitmap 的地方,首先还是会去内存里读取,接着会调用 RequestHandler 的 load() 来加载资源,然后对将加载得到的 bitmap 进行处理,具体如何处理则取决于之前在 RequestCreator 中所设置,这一步处理完成后,还要对用户自定义的 Transformation 来进行,简化后的代码流程如下:

Bitmap hunt() throws IOException {
  Bitmap bitmap = null;
  //从内存缓存读取
  if (shouldReadFromMemoryCache(memoryPolicy)) {
    bitmap = cache.get(key);
  }
  //从 requestHandler 加载
  RequestHandler.Result result = requestHandler.load(data, networkPolicy);
  if (bitmap != null) {
    // 对 bitmap 做处理,如 fit
    if (data.needsTransformation() || exifOrientation != 0) {
      synchronized (DECODE_LOCK) {
        if (data.needsMatrixTransform() || exifOrientation != 0) {
          bitmap = transformResult(data, bitmap, exifOrientation);
        }
        //用户自定义 bitmap 处理逻辑
        if (data.hasCustomTransformations()) {
          bitmap = applyCustomTransformations(data.transformations, bitmap);
        }
      }
    }
  }
  return bitmap;
}

看下 NetworkRequestHandler 中的 load() 逻辑:

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

private static okhttp3.Request createRequest(Request request, int networkPolicy) {
  CacheControl cacheControl = null;
  if (networkPolicy != 0) {
    if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
      cacheControl = CacheControl.FORCE_CACHE;
    } else {
      CacheControl.Builder builder = new CacheControl.Builder();
      if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
        builder.noCache();
      }
      if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
        builder.noStore();
      }
      cacheControl = builder.build();
    }
  }
  okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(request.uri.toString());
  if (cacheControl != null) {
    builder.cacheControl(cacheControl);
  }
  return builder.build();
}

首先会调用 createRequest() 来创建一个 Okhttp 的 Request 对象,会根据之前定义的磁盘缓存逻辑来设置 Request 的 CacheControl。接着会将这个创建好的 Request 对象传给 OkHttp3Downloader 来调用 okhttp3.Call.Factory#newCall() 发送网络请求。

请求结果缓存

通过 BitmapHunter.hunt() 获取到 Bitmap 后,会通过 Dispatcher#dispatchComplete() 来回调结果到 Dispatcher 中,在这里会对结果进行缓存处理:

//com.squareup.picasso.Dispatcher#performComplete
void performComplete(BitmapHunter hunter) {
  if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
    cache.set(hunter.getKey(), hunter.getResult());
  }
  hunterMap.remove(hunter.getKey());
  batch(hunter);
}

最终结果依次被回调到 ImageViewAction#complete() 或者是 ImageViewAction#error() 中来分别处理加载成功和失败的逻辑,加载成功则将 bitmap 封装成 PicassoDrawable 后设置给 ImageView,加载失败则显示设置的失败图资源。

总结

上一篇下一篇

猜你喜欢

热点阅读