Picasso 源码及流程

具体点说,图片显示到界面上这个过程中可能会遇到这些情况:

加载的图片可能有网络、本地等多种来源;
如果是网络的话,就得先下载下来;
下载过程中可能需要暂停、恢复或者取消;
下载后需要解码、对图片进行一些额外操作(比如裁剪、转变等);
最好还有个缓存系统,避免每次都去网络请求;
为了实现性能监控,最好再有个数据统计功能…

有了以上需求,根据职责分离的原则,我们可以定义一些核心类来完成上述功能:

请求信息类:其中包含了所有可以配置的选项,比如图片地址、要进行的操作等
图片获取类:根据不同的来源去不同地方获取,比如网络、本地、内存等
调度器类:实现图片获取的入队、执行、完成、取消、暂停等
图片处理类:图片拿到后进行解码、反转、裁剪等
缓存类:图片的内存、磁盘缓存控制
监控类:统计核心数据,比如当前内存、磁盘缓存的大小、某个图片的加载时间等

认识核心 API

图片.png

我给 Picasso 文件夹结构进行了调整,变成了这样:


图片.png

主要分为几个关键部分:

request 文件夹中的:请求信息相关的类
action 文件夹中的:加载行为相关的类
handler 文件夹中的:图片获取具体处理的类
Dispatcher:调度器
BitmapHunter:耗时任务执行者
Picasso:暴露给用户的类

请求信息相关的类


图片.png

上图中的 request 文件夹里放的是 Picasso 中构建图片请求信息相关的类,总共有五个,我们来分别了解下它们。

首先看 Request.java的成员变量(直接看它的 Builder ):

/** Builder for creating {@link Request} instances. */
public static final class Builder {
  private Uri uri;
  private int resourceId;
  private String stableKey;
  private int targetWidth;
  private int targetHeight;
  private boolean centerCrop;
  private int centerCropGravity;
  private boolean centerInside;
  private boolean onlyScaleDown;
  private float rotationDegrees;
  private float rotationPivotX;
  private float rotationPivotY;
  private boolean hasRotationPivot;
  private boolean purgeable;
  private List<Transformation> transformations;
  private Bitmap.Config config;
  private Priority priority;
  //...
}

可以看到,Request 中放的是一个图片的本地信息、要进行的转换操作信息、图片配置信息以及优先级等。

这里我们可以学习到的是:如果一个请求参数很多,我们最好用一个类给它封装起来,避免在传递时传递多个参数;如果经常使用的话,还可以创建一个对象池,节省开销。

接着看第二个类 RequestCreator:

public class RequestCreator {
  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;
  private int networkPolicy;
  private Drawable placeholderDrawable;
  private Drawable errorDrawable;
  private Object tag;
   //...
}

可以看到, RequestCreator 中包含了 Request.Builder,此外还有了些额外的信息,比如是否设置占位图、是否有渐变动画、是否延迟处理、以及占位图错误图资源 ID、内存使用策略、网络请求策略等。
RequestCreator 是相当重要的一个类,我们后面会进一步介绍它。

接着看第三个类 DeferredRequestCreator:

public class DeferredRequestCreator implements OnPreDrawListener, OnAttachStateChangeListener {
  private final RequestCreator creator;
  public @VisibleForTesting final WeakReference<ImageView> target;
  @VisibleForTesting
  public Callback callback;
  //...
}

可以看到, DeferredRequestCreator 中引用了 RequestCreator,此外还有一个要加载的 ImageView 弱引用对象,还有一个 Callback,它实现了 OnPreDrawListener 和 onAttachStateChangeListener 接口,这两个接口的作用如下:

OnPreDrawListener:当布局树将要绘制前,会回调这个借口的 onPreDraw() 方法
onAttachStateChangeListener:当布局绑定到一个 window 或者解除绑定和一个 window 时会调用

DeferredRequestCreator 中比较重要的就是这个 onPreDraw() 方法:


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

在加载网络图片后需要让图片的尺寸和目标 ImageView 一样大时(即调用 RequestCreator.fit() 方法),会使用到 DeferredRequestCreator。

剩下的两个枚举 MemoryPolicy 和 NetworkPolicy 就简单多了。
MemoryPolicy 指定了两种内存缓存策略:不去内存缓存里查找和不写入内存缓存。

public enum MemoryPolicy {
  //当请求图片时不去内存缓存里找
  NO_CACHE(1 << 0),
  //拿到图片后不写到内存缓存里,一般用于一次性请求
  NO_STORE(1 << 1);

  public static boolean shouldReadFromMemoryCache(int memoryPolicy) {
    return (memoryPolicy & MemoryPolicy.NO_CACHE.index) == 0;
  }

  public static boolean shouldWriteToMemoryCache(int memoryPolicy) {
    return (memoryPolicy & MemoryPolicy.NO_STORE.index) == 0;
  }
}

NetworkPolicy 指定了三种网络请求策略:

NO_CACHE: 跳过检查磁盘缓存,强制请求网络
NO_STORE: 拿到结果不写入磁盘缓存中
OFFLINE: 不请求网络,只能去磁盘缓存里查找
public enum NetworkPolicy {
  NO_CACHE(1 << 0),
  NO_STORE(1 << 1),
  OFFLINE(1 << 2);

  public static boolean shouldReadFromDiskCache(int networkPolicy) {
    return (networkPolicy & NetworkPolicy.NO_CACHE.index) == 0;
  }

  public static boolean shouldWriteToDiskCache(int networkPolicy) {
    return (networkPolicy & NetworkPolicy.NO_STORE.index) == 0;
  }

  public static boolean isOfflineOnly(int networkPolicy) {
    return (networkPolicy & NetworkPolicy.OFFLINE.index) != 0;
  }
}

上面介绍了 Picasso 中关于请求信息的五个类,小结一下,它们的作用如下:

Request:保存一个图片的本地信息、要进行的转换操作信息、图片配置信息以及优先级
RequestCreator:保存了一个图片加载请求的完整信息,包括图片信息、是否设置占位图、是否有渐变动画、是否延迟处理、以及占位图错误图资源 ID、内存使用策略、网络请求策略等
MemoryPolicy:定义了加载图片时的两种图片缓存策略
NetworkPolicy:定义了加载图片时的三种网络请求策略

加载行为相关的类

了解完请求信息相关的类后,我们再看看 action 文件夹下,关于加载行为的类(这里的 “加载行为” 是我临时起的名,可能不是很容易理解,稍后我再解释一下)。


图片.png
public abstract class Action<T> {

  public final Picasso picasso;
  public final Request request;
  public final WeakReference<T> target;
  public final boolean noFade;
  public final int memoryPolicy;
  public final int networkPolicy;
  public final int errorResId;
  public final Drawable errorDrawable;
  public final String key;
  public final Object tag;

  public boolean willReplay;
  public boolean cancelled;

  /**
   * 图片获取到后要调用的方法
   * @param result
   * @param from
   */
  public abstract void complete(Bitmap result, Picasso.LoadedFrom from);

  /**
   * 图片获取失败后要调用的方法
   * @param e
   */
  public  abstract void error(Exception e);
}

可以看到, Action 的成员变量里包含了一个图片的请求信息和加载策略、错误占位图,同时定义了两个抽象方法,这两个方法的作用是当图片加载成功后会调用 complete()(参数是拿到的图片和加载来源),加载失败后会调用 eror(),子类继承后可以实现自己特定的操作。

前面提到这些 action 表示的是加载行为,所谓“加载行为”简单点说就是“拿到图片要干啥”

发起一个图片加载请求的目的可能有多种,最常见的就是加载到图片上,对应 Picasso 里的 ImageViewAction(加载完成时它会把图片设置给 ImageView):

public class ImageViewAction extends Action<ImageView> {

  Callback callback;

    //加载成功,将图片设置给 ImageView
  @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();
    }
  }

    //失败时给 ImageView 设置错误图片
  @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);
    }
  }
}

除此外,Picasso 还提供了四种其他用途的加载行为类,源码比较容易理解,这里就直接贴出作用:

FetchAction: 拿到图片后会有个回调,除此外不会将图片显示到界面上
    Picasso.fetch() 方法会使用到它,这个方法在后台线程异步加载图片,只会将图片保存到硬盘或者内存上,不会显示到界面上。如果你不久之后就用这个图片,或者想要减少加载时间,你可以提前将图片下载缓存起来。
GetAction:拿到图片后不会有任何操作,不知道干啥的
    Picasso.get() 方法会使用到它,这个方法会同步加载图片并返回 Bitmap 对象,请确保你没有在Ui线程里面使用.get() 方法。这将会阻塞UI!
RemoteViewsAction: 拿到图片后设置给 RemoteView,有两个实现类 AppWidgetAction 和 NotificationAction,分别对应桌面插件和提醒栏
TargetAction:首先 Target 是 Picasso 中定义的一个接口,表示对图片加载的监听;TargetAction 在拿到图片后会调用 Target 接口的方法

接着介绍 handler 文件夹下的类,这个文件夹中类的功能就是:处理去不同渠道加载图片的请求。


图片.png

其中 RequestHandler 是基类,我们先来看看它。

public abstract class RequestHandler {
  /**
   * Whether or not this {@link RequestHandler} can handle a request with the given {@link Request}.
   */
  public abstract boolean canHandleRequest(Request data);

  /**
   * Loads an image for the given {@link Request}.
   *
   * @param request the data from which the image should be resolved.
   * @param networkPolicy the {@link NetworkPolicy} for this request.
   */
  @Nullable public abstract Result load(Request request, int networkPolicy) throws IOException;

RequestHandler 代码也比较简单,除了几个计算图片尺寸的方法外,最关键的就是上述的两个抽象方法:

boolean canHandleRequest(Request data) 表示当前获取类能否处理这个请求,一般子类会根据请求的 URI 来判断
Result load(Request request, int networkPolicy) 表示根据网络策略加载某个请求,返回加载结果

加载结果 Result 也比较简单:

  public static final class Result {
    private final Picasso.LoadedFrom loadedFrom;    //从哪儿加载的(网络、内存、磁盘)
    private final Bitmap bitmap;
    private final Source source;    //okio 中定义的数据流类
    private final int exifOrientation;    //图片的旋转方向

    public Result(@NonNull Bitmap bitmap, @NonNull Picasso.LoadedFrom loadedFrom) {
      this(checkNotNull(bitmap, "bitmap == null"), null, loadedFrom, 0);
    }

    public Result(@NonNull Source source, @NonNull Picasso.LoadedFrom loadedFrom) {
      this(null, checkNotNull(source, "source == null"), loadedFrom, 0);
    }

    Result(
        @Nullable Bitmap bitmap,
        @Nullable Source source,
        @NonNull Picasso.LoadedFrom loadedFrom,
        int exifOrientation) {
      if ((bitmap != null) == (source != null)) {
        throw new AssertionError();
      }
      this.bitmap = bitmap;
      this.source = source;
      this.loadedFrom = checkNotNull(loadedFrom, "loadedFrom == null");
      this.exifOrientation = exifOrientation;
    }
  }

RequestHandler 的子类实现都比较简单,这里我们就选常见的处理网络和文件请求的获取类来看看。

从名字就可以看出的从网络获取图片处理类 NetworkRequestHandler:


public class NetworkRequestHandler extends RequestHandler {
  private static final String SCHEME_HTTP = "http";
  private static final String SCHEME_HTTPS = "https";

  private final Downloader downloader;
  private final Stats stats;

    public NetworkRequestHandler(Downloader downloader, Stats stats) {
    this.downloader = downloader;
    this.stats = stats;
  }
   //根据请求 uri 的 scheme 判断是否为 http/https 请求
  @Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }

    //去网络加载一个图片
  @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);
    }

    // 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.");
    }
    return new Result(body.source(), loadedFrom);
  }
}

从上面的代码我们可以看到,Picasso 使用 okhttp3 来完成下载的功能,其中的下载器 downloader 就是通过构造一个 okhttp.Call 来完成同步下载文件:

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

从文件获取图片的请求处理类 FileRequestHandler:

public class FileRequestHandler extends ContentStreamRequestHandler {

  public FileRequestHandler(Context context) {
    super(context);
  }

  @Override public boolean canHandleRequest(Request data) {
    return SCHEME_FILE.equals(data.uri.getScheme());
  }

  @Override public Result load(Request request, int networkPolicy) throws IOException {
    Source source = Okio.source(getInputStream(request));
    return new Result(null, source, DISK, getFileExifRotation(request.uri));
  }

InputStream getInputStream(Request request) throws FileNotFoundException {
  ContentResolver contentResolver = context.getContentResolver();
  return contentResolver.openInputStream(request.uri);
}


  static int getFileExifRotation(Uri uri) throws IOException {
    ExifInterface exifInterface = new ExifInterface(uri.getPath());
    return exifInterface.getAttributeInt(TAG_ORIENTATION, ORIENTATION_NORMAL);
  }
}

也很简单是吧,根据 URI 获取输入流通过 ContentResolver.openInputStream( Uri uri) 可以实现,这个可以记一下以后可能会用到,拿到 IO 流后,接下来的的操作直接通过 Okio 实现了。

通过这几个图片请求处理类我们可以看到 Picasso 的设计多么精巧,每个类即精简又功能独立,我们在开发时最好可以参考这样的代码,先定义接口和基类,然后再考虑不同的实现。

分析完这些“大家族”后,剩下的就是一些单独的类了。

调度器 Dispatcher

调度器的角色在许多框架里可以看到,实际上在稍微复杂一点的业务逻辑,都需要这么一个调度器类,它负责业务逻辑在不同线程的切换、执行、取消。

我们来看看 Picasso 中的调度器,首先看它的成员变量:

public class Dispatcher {

  private static final String DISPATCHER_THREAD_NAME = "Dispatcher";
  private static final int BATCH_DELAY = 200; // ms

  final DispatcherThread dispatcherThread;    //HandlerThread,用于为子线程 Handler 准备 Looper
  final Context context;
  final ExecutorService service;    //线程池
  final Downloader downloader;    //下载器
  final Map<String, BitmapHunter> hunterMap;    //Action's key 和 图片猎人 的关联关系
  final Map<Object, Action> failedActions;  //失败的操作 map
  final Map<Object, Action> pausedActions;  //暂停的操作 map
  final Set<Object> pausedTags;    //暂停的 tag
  final Handler handler;    //子线程的 Handler
  final Handler mainThreadHandler;    //ui 线程的 Handler
  final Cache cache;    //缓存
  final Stats stats;    //数据统计
  final List<BitmapHunter> batch;    //后面介绍,获取图片最核心的类
  final NetworkBroadcastReceiver receiver;
  final boolean scansNetworkChanges;

  boolean airplaneMode;
}

可以看到,Dispatcher 的成员变量有 HandlerThread,两个 Handler、线程池,下载器、BitmapHunter(我叫它“图片猎手”,后面介绍它)、缓存、数据统计等等。

从 Picasso 的 Dispatcher 中,我们可以学到如何创建一个复杂业务的调度器。

复杂业务往往需要在子线程中进行,于是需要用到线程池;线程之间切换需要用到 Handler,为了省去创建 Looper 的功夫,就需要使用 HandlerThread;此外还需要持有几个列表、Map,来保存操作数据。

作为调度器,最重要的功能就是给外界提供各种功能的接口,一般我们都使用不同的常量来标识不同的逻辑,在开始写业务之前,最好先定好功能、确定常量。

我们来看看 Dispatcher 中定义的常量都代表着什么功能:

  private static final int RETRY_DELAY = 500;    //重试的延迟时间
  private static final int AIRPLANE_MODE_ON = 1;
  private static final int AIRPLANE_MODE_OFF = 0;

  public static final int REQUEST_SUBMIT = 1;    //提交请求
  public static final int REQUEST_CANCEL = 2;    //取消请求
  public static final int REQUEST_GCED = 3;    //请求被回收
  public static final int HUNTER_COMPLETE = 4;    //图片获取完成
  public static final int HUNTER_RETRY = 5;        //重试图片获取
  public static final int HUNTER_DECODE_FAILED = 6;    //图片解码失败
  public static final int HUNTER_DELAY_NEXT_BATCH = 7;   
  public static final int HUNTER_BATCH_COMPLETE = 8;    //图片批量获取成功
  public static final int NETWORK_STATE_CHANGE = 9;    //网络状态改变
  public static final int AIRPLANE_MODE_CHANGE = 10;    //飞行模式改变
  public static final int TAG_PAUSE = 11;
  public static final int TAG_RESUME = 12;
  public static final int REQUEST_BATCH_RESUME = 13;

上图中对大多数操作的功能做了注释。确定好功能后,就可以创建 Handler 了,它负责接收不同线程发出的请求。

Dispatcher 的内部类 DispatcherHandler 是在子线程中执行的 Handler:

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


然后在 Dispatcher 中创建接受请求的方法:

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

public void dispatchCancel(Action action) {
  handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));
}

public void dispatchPauseTag(Object tag) {
  handler.sendMessage(handler.obtainMessage(TAG_PAUSE, tag));
}

public void dispatchResumeTag(Object tag) {
  handler.sendMessage(handler.obtainMessage(TAG_RESUME, tag));
}

最后就是创建处理请求的方法了,比如提交图片获取操作的方法:

public void performSubmit(Action action, boolean dismissFailed) {
  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);
  hunter.future = service.submit(hunter);
  hunterMap.put(action.getKey(), hunter);
  if (dismissFailed) {
    failedActions.remove(action.getTarget());
  }
}

具体一些方法如何实现的,我们后面再看。这里了解调度器的基本信息,掌握如何写一个调度器的流程即可

最核心的 图片猎手 BitmapHunter
前面介绍了那么多 API,它们基本是各自实现一个单独的模块功能,Picasso 中的 BitmapHunter 是把这些组合起来,具体实现图片的获取、解码、转换操作的类。

public class BitmapHunter implements Runnable {
  //解码 bitmap 使用的全局锁,确保一次只解码一个,避免内存溢出
  private static final Object DECODE_LOCK = new Object();

  private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger();
  final int sequence;
  final Picasso picasso;
  final Dispatcher dispatcher;
  final Cache cache;
  final Stats stats;
  final String key;
  final Request data;
  final int memoryPolicy;
  int networkPolicy;
  final RequestHandler requestHandler;

  Action action;
  List<Action> actions;   //要执行的操作列表
  Bitmap result;
  Future<?> future;
  Picasso.LoadedFrom loadedFrom;
  Exception exception;
  int exifOrientation; // Determined during decoding of original resource.
  int retryCount;
  Priority priority;
}

可以看到, BitmapHunter 的成员变量有我们前面介绍的那些关键类。同时它实现了 Runnable 接口,在 run() 方法中执行耗时任务:

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

run() 方法非常简单,调用 hunt() 方法后就是一长串异常捕获和调度,这里可以看出自定义异常的重要性,在复杂的 IO、网络操作中,有很多产生异常的可能,在不同操作里抛出不同类型的异常,有助于最后的排查、处理。

我们来看看完成主要任务的 hunt() 方法:

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

  if (shouldReadFromMemoryCache(memoryPolicy)) {    //1.根据请求的缓存策略,判断是否要读取缓存
    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;
    }
  }

  //2.调用适当的 requestHandler 来处理图片加载请求
  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 (bitmap == null) {
      Source source = result.getSource();
      try {
        bitmap = decodeStream(source, data);  //解码操作
      } finally {
        try {
          source.close();
        } catch (IOException ignored) {
        }
      }
    }
  }

  //3.拿到图片加载结果后有解码好的 bitmap,进入下一步,转换
  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;
}

可以看到,BitmapHunter 中获取图片的 hunt() 方法的逻辑如下:

如果缓存策略允许去内存缓存读取,就去缓存里找,找到就返回
否则调用适当的 RequestHandler 去处理图片加载请求
如果 RequestHandler 加载成功但是这个图片数据还没有解码,就去解码
拿到解码后的图片就进入下一步,转换
转换有 Picasso 支持的转换(比如裁剪什么的),也有自定义的
最后返回转换后的图片

整体类:

图片.png

流程图:

图片.png

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271