写点什么

Android 框架解析:Picasso 源码基本架构

作者:拭心
  • 2021 年 12 月 05 日
  • 本文字数:14802 字

    阅读完需:约 49 分钟

Android 框架解析:Picasso 源码基本架构

大家好,我是拭心,今天我们来了解下 Android 常用的图片框架 Picasso 的实现。

假如我们来写一个框架

在学习一个框架之前,我们最好先设想一下,如果让自己来写这样一个框架,会如何写呢?


就拿本篇文章要研究的图片加载框架来说,我们知道一个图片框架的核心功能就是:将图片显示到界面上


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


  • 加载的图片可能有网络、本地等多种来源;

  • 如果是网络的话,就得先下载下来;

  • 下载过程中可能需要暂停、恢复或者取消;

  • 下载后需要解码、对图片进行一些额外操作(比如裁剪、转变等);

  • 最好还有个缓存系统,避免每次都去网络请求;

  • 为了实现性能监控,最好再有个数据统计功能...


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


  1. 请求信息类:其中包含了所有可以配置的选项,比如图片地址、要进行的操作等

  2. 图片获取类:根据不同的来源去不同地方获取,比如网络、本地、内存等

  3. 调度器类:实现图片获取的入队、执行、完成、取消、暂停等

  4. 图片处理类:图片拿到后进行解码、反转、裁剪等

  5. 缓存类:图片的内存、磁盘缓存控制

  6. 监控类:统计核心数据,比如当前内存、磁盘缓存的大小、某个图片的加载时间等


OK,有了这些核心类,我们就可以画一个简单的图片加载框架流程图了:



画出图后逻辑就清晰多了,接着我们来看看 Picasso 的核心 API 以及它们如何实现的图片加载,和我们设想的有什么区别吧。

走近 Picasso

  • 本文分析代码基于 Picasso v2.71828

  • 下载地址:https://github.com/square/picasso/releases/tag/2.71828

认识核心 API

首先我们来认识下 Picasso 的核心 API。


下图是 Picasso 的项目结构(吐槽一下,怎么都不分几个文件夹,可能是为了少写些 public 吧哈哈):



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



主要分为几个关键部分:


  1. request 文件夹中的:请求信息相关的类

  2. action 文件夹中的:加载行为相关的类

  3. handler 文件夹中的:图片获取具体处理的类

  4. Dispatcher:调度器

  5. BitmapHunter:耗时任务执行者

  6. Picasso:暴露给用户的类

请求信息相关的类


上图中的 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,它实现了 OnPreDrawListeneronAttachStateChangeListener 接口,这两个接口的作用如下:


  • 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


剩下的两个枚举 MemoryPolicyNetworkPolicy 就简单多了。


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 指定了三种网络请求策略:


  1. NO_CACHE: 跳过检查磁盘缓存,强制请求网络

  2. NO_STORE: 拿到结果不写入磁盘缓存中

  3. 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 中关于请求信息的五个类,小结一下,它们的作用如下:


  1. Request:保存一个图片的本地信息、要进行的转换操作信息、图片配置信息以及优先级

  2. RequestCreator:保存了一个图片加载请求的完整信息,包括图片信息、是否设置占位图、是否有渐变动画、是否延迟处理、以及占位图错误图资源 ID、内存使用策略、网络请求策略等

  3. MemoryPolicy:定义了加载图片时的两种图片缓存策略

  4. NetworkPolicy:定义了加载图片时的三种网络请求策略

加载行为相关的类

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



这六个类里 Action 是基类,我们先看它。


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 还提供了四种其他用途的加载行为类,源码比较容易理解,这里就直接贴出作用:


  1. FetchAction: 拿到图片后会有个回调,除此外不会将图片显示到界面上


  • Picasso.fetch() 方法会使用到它,这个方法在后台线程异步加载图片,只会将图片保存到硬盘或者内存上,不会显示到界面上。如果你不久之后就用这个图片,或者想要减少加载时间,你可以提前将图片下载缓存起来。


  1. GetAction:拿到图片后不会有任何操作,不知道干啥的


  • Picasso.get() 方法会使用到它,这个方法会同步加载图片并返回 Bitmap 对象,请确保你没有在 Ui 线程里面使用.get() 方法。这将会阻塞 UI!


  1. RemoteViewsAction: 拿到图片后设置给 RemoteView,有两个实现类 AppWidgetActionNotificationAction,分别对应桌面插件和提醒栏

  2. TargetAction:首先 Target 是 Picasso 中定义的一个接口,表示对图片加载的监听;TargetAction 在拿到图片后会调用 Target 接口的方法


上面的 1 2 两点部分摘自:http://blog.csdn.net/u011337574/article/details/51588785

图片获取处理相关的类


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



其中 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 代码也比较简单,除了几个计算图片尺寸的方法外,最关键的就是上述的两个抽象方法:


  1. boolean canHandleRequest(Request data) 表示当前获取类能否处理这个请求,一般子类会根据请求的 URI 来判断

  2. 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() 方法的逻辑如下:


  1. 如果缓存策略允许去内存缓存读取,就去缓存里找,找到就返回

  2. 否则调用适当的 RequestHandler 去处理图片加载请求

  3. 如果 RequestHandler 加载成功但是这个图片数据还没有解码,就去解码

  4. 拿到解码后的图片就进入下一步,转换

  5. 转换有 Picasso 支持的转换(比如裁剪什么的),也有自定义的

  6. 最后返回转换后的图片

最终的门面 Picasso

终于该介绍我们的门面类 Picasso 了。


Picasso 类的存在就是“外观模式”(也成门面模式)的完美体现,它集成了前面提到的复杂的类,然后为我们提供了许多配置的方法,这样我们在使用时只需要调用 Picasso 的方法即实现目的,不用和更多类打交道:


Picasso.get() //获得 Picasso 单例    .load(url) //    .placeholder(R.drawable.placeholder) //    .error(R.drawable.error) //    .fit() //    .tag(context) //    .into(view);
复制代码


我们看看 Picasso 的成员属性:


static final Handler HANDLER = new Handler(Looper.getMainLooper()) {  @Override public void handleMessage(Message msg) {    switch (msg.what) {      case HUNTER_BATCH_COMPLETE: {    //批量获取成功        @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;        for (int i = 0, n = batch.size(); i < n; i++) {          BitmapHunter hunter = batch.get(i);          hunter.picasso.complete(hunter);        }        break;      }      case REQUEST_GCED: {    //请求被回收了,取消        Action action = (Action) msg.obj;        action.picasso.cancelExistingRequest(action.getTarget());        break;      }      case REQUEST_BATCH_RESUME:    //回复批量获取        for (int i = 0, n = batch.size(); i < n; i++) {          Action action = batch.get(i);          action.picasso.resumeAction(action);        }        break;      default:        throw new AssertionError("Unknown handler message received: " + msg.what);    }  }};
@SuppressLint("StaticFieldLeak") static volatile Picasso singleton = null;
private final Listener listener; private final RequestTransformer requestTransformer;private final CleanupThread cleanupThread; //清理线程private final List<RequestHandler> requestHandlers; //请求处理器列表
public final Context context;public final Dispatcher dispatcher; //调度器public final Cache cache;public final Stats stats;public final Map<Object, Action> targetToAction; //ImageView 和对应的请求public final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator; //ImageView 和对应的延迟请求public final ReferenceQueue<Object> referenceQueue; //引用队列public final Bitmap.Config defaultBitmapConfig;
public boolean indicatorsEnabled;public volatile boolean loggingEnabled;
public boolean shutdown;
复制代码


可以看到它集成了前面介绍的关键类,同时也持有了请求处理器列表、ImageView 和对应的 Action 哈希表等数据。


在它的构造方法中,这些成员变量进行了赋值和初始化:


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);
// ResourceRequestHandler needs to be the first in the 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);}
复制代码


上面的构造方法省去了其他元素,单独露出了 RequestHandler 列表的初始化,可以看到在 Picasso 构造时将我们见过的所有 RequestHandler 子类进行了实例化,后面在获取图片时,会遍历这个列表来找到能够处理请求的处理器类。


Picasso 中暴露了很多方法,我们在后面的具体业务逻辑时查看。


除了 Picasso ,其他比较知名的框架都会用到“外观模式” ,我们在编写复杂逻辑或者 SDK 时应该在完成各个子模块以后,在它们的上面增加一层,由这一层来和各个模块交互,给使用者提供统一、简单的调用接口,避免暴露太多内部模块。

发布于: 2 小时前阅读数: 6
用户头像

拭心

关注

Never Settle! 2017.11.30 加入

字节跳动高级 Android 工程师,主要从事性能优化相关工作

评论

发布
暂无评论
Android 框架解析:Picasso 源码基本架构