Android开发经验谈Android技术知识Android开发

picasso详解及其源码简析

2019-01-08  本文已影响4人  仰简

一、前言

图片库作为开发中一个重要的基础组件,其重要性不言而喻。目前来说,比较流行的图片库分别是 Android-Universal-Image-LoaderPicassoGlideFresco。它们的一般特点是:

这篇文章要分析的是 square/picasso。没错,又是 square,谁中你那么优秀呢?不去管它的旧版本如何,这里直接分析其最新版本 2.71828 吧。

二、picasso 简介

1.关于 picasso

picasso,伟大的画家毕加索,这里想必也是在表达致敬之意。square 在 github 项目首页中只用了一句话解释这个库。

一个适用于Android的强大的图像下载和缓存库

image.png

除了 github 项目首页的介绍外,picasso 还另外提供了一个 github page。在这里,它进一步介绍了其具备的几个功能:

同时该页面也给出了一些使用例子,如在 Adapter 中使用,图像变换以及自定义变换、设置 placeholders 等。感兴趣的同学可以自行前往看一看,不过要注意 Picasso 的实例不再从 Picasso.with(context) 开始了,也不再是该页面所展示的 Picasso.get(),而封装了一个 PicassoProvider,然后通过它的静态方法 get() 来获取。

2.集成

gradle 中集成,so easy,每个整 android 的小伙伴都懂的。

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

三、源码分析

前面简要介绍了下 picasso,下面便开始进入正题源码分析。不变的原则,从最简单的主路径开始分析。分析之前先来看一看其框架图,是不是似曾相识?

Picasso.jpg

1.demo

picasso 工程里有提供 sample,作为典型的例子,这里选择了从 SampleListDetailAdapter 拿出一段用于 ListView 的 Adapter 中加载列表图片的代码来作为用于主路径分析的 demo。

PicassoProvider.get()
        .load(url)
        .placeholder(R.drawable.placeholder)
        .error(R.drawable.error)
        .resizeDimen(R.dimen.list_detail_image_size, R.dimen.list_detail_image_size)
        .centerInside()
        .tag(context)
        .into(holder.image);

链式调用的一大特点就在于不管多复杂的逻辑,看起来好像都是顺序执行的一样。嗯呵,你可能想到了 RxJava。下面就来逐个逐个分析上面的每一个方法的调用。

2.主路径分析

2.1 构造 Picasso 实例,初始化运行环境

public final class PicassoProvider {
  @SuppressLint("StaticFieldLeak")
  private static volatile Picasso instance;

  @Initializer
  public static Picasso get() {
    // 单例实现
    if (instance == null) {
      synchronized (PicassoProvider.class) {
        if (instance == null) {
          // 获取 context
          Context autoContext = PicassoContentProvider.context;
          if (autoContext == null) {
            throw new NullPointerException("context == null");
          }
          // 通过 Picasso.Builder 来构建 Picasso 实例
          instance = new Picasso.Builder(autoContext).build();
        }
      }
    }
    return instance;
  }

  private PicassoProvider() {
    throw new AssertionError("No instances.");
  }
}

PicassoProvider 通过单例设计模式的方式,在 get() 方法中为我们提供了 Picasso 的实例。也就是说,在应用中,一个进程内只有一个 Picasso 实例。

构建 Picasso 实例是需要 Context 的,这里是从 PicassoContentProvider 处获取。因此,进一步看看 PicassoContentProvider 又是如何提供 Context。

public final class PicassoContentProvider extends ContentProvider {
  @SuppressLint("StaticFieldLeak")
  @Nullable static Context context;

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

PicassoContentProvider 继承了 ContentProvider,这是一个非常微妙的实现。其利用了 ContentProvider 会在 Application 初始化的时候同时被初始化的原理,从而初始化 context。不得不服!!!这就避免了开发者还需要对其进行全局初始化的步骤,典型是在应用的 Application#onCreate() 进行初始化。

先来看 Picasso 的构造方法,其主要目的是获取 ApplicationCotnext,从而避免引起内存泄漏。

public Builder(@NonNull Context context) {
      checkNotNull(context, "context == null");
      this.context = context.getApplicationContext();
    }

再来看 build() 方法。

public Picasso build() {
      Context context = this.context;
      // 初始化磁盘 Cache,也即初始化 OkHttp 的 Cache
      okhttp3.Cache unsharedCache = null;
      if (callFactory == null) {
        File cacheDir = createDefaultCacheDir(context);
        long maxSize = calculateDiskCacheSize(cacheDir);
        unsharedCache = new okhttp3.Cache(cacheDir, maxSize);
        callFactory = new OkHttpClient.Builder()
            .cache(unsharedCache)
            .build();
      }
      // 初始化内存 Cache
      if (cache == null) {
        cache = new PlatformLruCache(Utils.calculateMemoryCacheSize(context));
      }
      // 初始化线程池
      if (service == null) {
        service = new PicassoExecutorService(new PicassoThreadFactory());
      }
      // 初始化内存 Cache 统计器
      Stats stats = new Stats(cache);
      // 初始化分发器
      Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, cache, stats);
      // new 出 Picasso 实例
      return new Picasso(context, dispatcher, callFactory, unsharedCache, cache, listener,
          requestTransformers, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled,
          loggingEnabled);
    }

磁盘 Cache:对了,磁盘在手机上一般指的就是闪存 Flash 或者外置的 SD Card 了。Picasso 没有单独实现一个磁盘缓存,而是利用了 OkHttp 的缓存管理,使得缓存的有效期与 Http 协议同步。这样做的好处是保证了图片的更新的及时性,而不像其他磁盘缓存通过硬设一个缓存过期时间比如 7 天来判断其是否过期。当然,它可能也会增加多一些网络请求。另外,默认创建的磁盘 Cache 的路径为 App 的内部 data 目录下 cache/picasso-cache,而大小一般为磁盘空间总大小的 2%,但是也不能超过 50 M。

内存 Cache:PlatformLruCache,其内部通过 LruCache 来实现的内存 Cahce。而其大小被限制在应用可获得的最大 heap 内存的 15%。

/** A memory cache which uses a least-recently used eviction policy. */
final class PlatformLruCache {
  final LruCache<String, BitmapAndSize> cache;
  ......
}

线程池: PicassoExecutorService,其继承自ThreadPoolExecutor,在其构造函数中设置了其核心线程数以及最大线程数为 3 个,这就限制了其最多只能有 3 个线程能同时工作。

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

  PicassoExecutorService(ThreadFactory threadFactory) {
    super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
        new PriorityBlockingQueue<Runnable>(), threadFactory);
  }
  ......
}

Stats: 内存 Cache 统计器,统计 Cache 的各个指标,比如命中,未命中等。

分发器Dispatcher:似曾相识的概念,在 OkHttp 中有见到过。这里也是一样,主要管理任务的分发与调度。包括提交,取消,重试以及网络变化的监听策略。

Picasso及其构造函数:Picasso 的构造会使得其持有 Dispatcher,Cache,PlatformLruCache,RequestTransformer 队列,RequestHandler队列等,这些引用都会保存在 Picasso 内部。除此之外,其自身也会初始化一些 RequestHandlers 用于处理已知的各种资源类型。当我们传递不同的资源类型的请求时,其便会选择相应的 RequestHandler 来进行处理。

另外,这里还注意到,Picasso 实现了 LifecycleObserver 接口。这意味着我们可以将其注册到 LifecycleOwner 的 LifecycleRegistry 中,从而实现生命周期的联动。

public class Picasso implements LifecycleObserver {
  ......
  Picasso(Context context, Dispatcher dispatcher, Call.Factory callFactory,
      @Nullable okhttp3.Cache closeableCache, PlatformLruCache cache, @Nullable Listener listener,
      List<RequestTransformer> requestTransformers, List<RequestHandler> extraRequestHandlers,
      Stats stats, @Nullable Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled,
      boolean loggingEnabled) {
    this.context = context;
    this.dispatcher = dispatcher;
    this.callFactory = callFactory;
    this.closeableCache = closeableCache;
    this.cache = cache;
    this.listener = listener;
    this.requestTransformers = Collections.unmodifiableList(new ArrayList<>(requestTransformers));
    this.defaultBitmapConfig = defaultBitmapConfig;

    // Adjust this and Builder(Picasso) as internal handlers are added or removed.
    int builtInHandlers = 8;
    int extraCount = extraRequestHandlers.size();
    List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);

    ......
    allRequestHandlers.add(ResourceDrawableRequestHandler.create(context));
    allRequestHandlers.add(new ResourceRequestHandler(context));
    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(callFactory, stats));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);

    this.stats = stats;
    this.targetToAction = new LinkedHashMap<>();
    this.targetToDeferredRequestCreator = new LinkedHashMap<>();
    this.indicatorsEnabled = indicatorsEnabled;
    this.loggingEnabled = loggingEnabled;
  }
  ......
}

到这里,Picasso 的运行环境已经基本初始化好了,接下来就是等待用户的请求发送了。

2.2 参数设置,从 RequestCreator 到 Request

前面通过 PicassoProvider.get() 构建了 Picasso 实例,同时也初始化了 Picasso 的运行环境,接下来就是一系列参数的设置。通过 Picasso 的 load() 方法便可得到 Request 的创建器 RequestCreator,我们将需要的参数都送进 RequestCreator 以便能最终构建出我们所需要的那个 Request。

load() 重载了 4 个不同的方法,分别对应了加载不同的数据源,4 个方法如下。

public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
}
public RequestCreator load(@Nullable String path) {
    .......
    return load(Uri.parse(path));
}
public RequestCreator load(@Nullable File file) {
   ......
    return load(Uri.fromFile(file));
}
public RequestCreator load(@DrawableRes int resourceId) {
   ......
    return new RequestCreator(this, null, resourceId);
}

可以看出前 3 个其实都是同一个,形参都将转成 Uri。而从上面的代码也可以看出,最终都是 new 出一个 RequestCreator 实例。并且这里 Uri 与 resourceId 是互斥的,一定只是其中的一个。再来看看 RequestCraetor 的构造方法。

RequestCreator(Picasso picasso, @Nullable Uri uri, int resourceId) {
    ......
    this.picasso = picasso;
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}

这里最主要就是构造了 Request.Builder 实例,并存储在 data 属性中。其主要是用于在设置好参数后,进一步构造出 Request 实例。

public RequestCreator placeholder(@DrawableRes int placeholderResId) {
   ......
    this.placeholderResId = placeholderResId;
    return this;
}
public RequestCreator error(@DrawableRes int errorResId) {
    ......
    this.errorResId = errorResId;
    return this;
}

就是简单的记录下 placeholderResId 以及 errorResId。

resizeDeimen() 其实主是将参数 dimen 所表达的数值转成实际大小,再调用 resize() 方法,所以这里直接看 resize() 方法即可。

  public RequestCreator resize(int targetWidth, int targetHeight) {
    data.resize(targetWidth, targetHeight);
    return this;
 }
  public RequestCreator centerCrop() {
    data.centerCrop(Gravity.CENTER);
    return this;
 }

这里的 data 是 Request.Builder 实例,所以这里是将参数送进了 RequestBuilder 中去了。和上面的 2 个方法比较一下,为什么参数会被存储在不同的地方?

  public RequestCreator tag(@NonNull Object tag) {
    data.tag(tag);
    return this;
  }

tag 用来标记一个 Request ,可以是任意一个对象,如 String,Context,Activity,Fragment等。我们可以通过 tag 来 pause,resume 以及 cancel 具备相同 tag 的 Request。但要注意的是 tag 与 Request 的生命周期是一样长的,一定程度上可能会存在内存泄漏的风险。

关于 RequestCreator 的参数还有很多可以设置的,这里就不一一详细进行讲解了。仅通过类图将其列举出来如下。


RequestCreator

2.3 提交请求 into(ImageView)

其实 into() 也是有很多重载的,比如into(BitmapTarget),into(RemoteViews, int viewId, int notificationId,Notification notification) 等。也就是说它还可以将图片显示到 Notification 中以及 BitmapTarget 接口。关于 BitmapTarget 这个接口是什么意思呢?这里看一看其在注释中举的一个例子就可以明白了。

 public class ProfileView extends FrameLayout implements BitmapTarget {
    Override 
    public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
        setBackgroundDrawable(new BitmapDrawable(bitmap));
      }
    Override 
    public void onBitmapFailed(Exception e, Drawable errorDrawable) {
     setBackgroundDrawable(errorDrawable);
      }
    Override 
    public void onPrepareLoad(Drawable placeHolderDrawable) {
       setBackgroundDrawable(placeHolderDrawable);
     }
  }

也就是说我们只实现这个接口,然后在 onBitmapLoaded() 的回调中自己选择应该如何显示该图片。

而与 into() 相同级别的还有另一个方法 fetch(),这个方法主要是只下载图片而不展示到 target 上去。这也是一个极为有用的方法,典型的场景就是预加载。

关于 into() 之外的东西就了解这么多,下面将重要放到 into(ImageView) 上来。into(ImageView) 其只是进一步调用了 into(ImageView,Callback),因此直接来看后面这个重载方法。

方法有点长,但其实很简单,不要畏难,耐着性子看完,增加了比较详细的注释了。

public void into(@NonNull ImageView target, @Nullable Callback callback) {
    long started = System.nanoTime();
    // 检查是否为主线程,Picasso 要求请求必须从主线程发起。
    checkMain();
    // target 合法性检查
    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }
    // 如果没有设置 url 或者  resId,则取消当前 ImageView 的 Request,并直接返回
    if (!data.hasImage()) {
      picasso.cancelRequest(target);
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }
    // 如果调用了 fit() ,也就是使得图片大小为适应 ImageView 的大小
    if (deferred) {
      if (data.hasSize()) {
        // fit() 与 resize() 不能同时调用,否则会抛出异常
        throw new IllegalStateException("Fit cannot be used with resize.");
      }
      int width = target.getWidth();
      int height = target.getHeight();
      if (width == 0 || height == 0) {
        // ImageView 没有具体的高度或者还没被渲染出来的情况下
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));
        return;
      }
      data.resize(width, height);
    }
    // 构建 Request
    Request request = createRequest(started);

    if (shouldReadFromMemoryCache(request.memoryPolicy)) {
      // 允许从内存缓存读取则优先从内存缓存读取
      Bitmap bitmap = picasso.quickMemoryCacheCheck(request.key);
      if (bitmap != null) {
        // 缓存中存在有需要的图片,则取消并返回结果
        picasso.cancelRequest(target);
        RequestHandler.Result result = new RequestHandler.Result(bitmap, MEMORY);
        setResult(target, picasso.context, result, noFade, picasso.indicatorsEnabled);
        if (picasso.loggingEnabled) {
          log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
        }
        if (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }
   // 设置 PlasceHolder
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
   // 创建 ImageViewAction 并提交
    Action action = new ImageViewAction(picasso, target, request, errorDrawable, errorResId, noFade,
        callback);
    picasso.enqueueAndSubmit(action);
  }

方法里的代码都加了详细的注释,并且总结流程图如下。

Into.jpg

流程图上看也确实步骤比较多,但其实关键步骤就 2 个。其一,先判断是否内存缓存是否已经存储图片了,如果有就用内存缓存的图片。其二,如果没有就构造一个 ImageViewAction 来提交到 Picasso 的请求队列。说到缓存,这里有一个重要的知识点,关于缓存的 key,它是在 Request 被 build 出来后一起被创建的,其在 Request 的 createKey() 方法中。

private String createKey(StringBuilder builder) {
    Request data = this;
    // key 的主要部分
    if (data.stableKey != null) {
      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
      builder.append(data.stableKey);
    } else if (data.uri != null) {
      String path = data.uri.toString();
      builder.ensureCapacity(path.length() + KEY_PADDING);
      builder.append(path);
    } else {
      builder.ensureCapacity(KEY_PADDING);
      builder.append(data.resourceId);
    }
   // key 的分隔线
    builder.append(KEY_SEPARATOR);
    // 参数
    if (data.rotationDegrees != 0) {
      builder.append("rotation:").append(data.rotationDegrees);
      if (data.hasRotationPivot) {
        builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
      }
      builder.append(KEY_SEPARATOR);
    }
    if (data.hasSize()) {
      builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
      builder.append(KEY_SEPARATOR);
    }
    if (data.centerCrop) {
      builder.append("centerCrop:").append(data.centerCropGravity).append(KEY_SEPARATOR);
    } else if (data.centerInside) {
      builder.append("centerInside").append(KEY_SEPARATOR);
    }

    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, count = data.transformations.size(); i < count; i++) {
      builder.append(data.transformations.get(i).key());
      builder.append(KEY_SEPARATOR);
    }
    return builder.toString();
  }

代码看起来也有点长,但简单理解下,cache key 主要由主体部分 + 参数部分构成。主体部分一般是指 url 或者 resId,而参数部分主要就是其是否有旋转,resize 等。那这样一来就得到一个结论,就是同一个图片在内存中可能因为要显示的参数不一样会有不同的缓存。这样做的好处自然是提高了显示速度,而坏处自然就是占用的内存会较多。

2.4 入队请求,分发请求Dispatcher,执行请求 BitmapHunter

在 into(ImageView) 中,如果没有命中 Cache,最后一步就是封装 ImageViewAction,并提交给 picasso,由其进一步处理。

Action action = new ImageViewAction(picasso, target, request, errorDrawable, errorResId, noFade,
        callback);
picasso.enqueueAndSubmit(action);

先来看一看 enqueueAndSubmit

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

Picasso 中用 targetToAction 来保存了 target 与 action 之间的关系。这里首先从里面找 target 是否已经关联了其他的 action,如果已经关联了则先要将其取消。这里的场景就是特别适合 Adapter 了。在 Adapter 中由于 ImageView 是会被重用的,那么就一定会存在一个 target 对应不同的 action 的情况。

这里先来看一下任务的取消 cancelExistingRequest() 的实现,然后再去看 submit()。

void cancelExistingRequest(Object target) {
    checkMain();
    Action action = targetToAction.remove(target);
    if (action != null) {
      action.cancel();
      // 从 dispatcher 中取消
      dispatcher.dispatchCancel(action);
    }
    if (target instanceof ImageView) {
      ImageView targetImageView = (ImageView) target;
     // 如果有延迟的 request,也要将其取消,这里假设没有吧。
      DeferredRequestCreator deferredRequestCreator =
          targetToDeferredRequestCreator.remove(targetImageView);
      if (deferredRequestCreator != null) {
        deferredRequestCreator.cancel();
      }
    }
  }

所以主要是将 Dispatcher 中的 action 取消。Dispatcher 中主要用了一个 HandlerThread 来管理,并且所有关于请求的提交,取消等都是通过消息来执行的。如下所示。

  static final int REQUEST_SUBMIT = 1;
  static final int REQUEST_CANCEL = 2;
  static final int HUNTER_COMPLETE = 4;
  static final int HUNTER_RETRY = 5;
  static final int HUNTER_DECODE_FAILED = 6;
  static final int NETWORK_STATE_CHANGE = 9;
  static final int AIRPLANE_MODE_CHANGE = 10;
  static final int TAG_PAUSE = 11;
  static final int TAG_RESUME = 12;
  static final int REQUEST_BATCH_RESUME = 13;

消息如何执行,这里就不详细说明了。Dispatcher 中的 action 取消明显就是通过 REQUEST_CANCEL 来执行,而 REQUEST_CANCEL 对应的就是 performCance()。

void performCancel(Action action) {
   // 取消对应的 hunter
    String key = action.request.key;
    BitmapHunter hunter = hunterMap.get(key);
    if (hunter != null) {
      hunter.detach(action);
      if (hunter.cancel()) {
        hunterMap.remove(key);
        if (action.picasso.loggingEnabled) {
          log(OWNER_DISPATCHER, VERB_CANCELED, action.request.logId());
        }
      }
    }
    // 如果在 pause 队列中,则移除
    if (pausedTags.contains(action.getTag())) {
      pausedActions.remove(action.getTarget());
      if (action.picasso.loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_CANCELED, action.request.logId(),
            "because paused request got canceled");
      }
    }
    // 失败队列中包含了,也移除
    Action remove = failedActions.remove(action.getTarget());
    if (remove != null && remove.picasso.loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_CANCELED, remove.request.logId(), "from replaying");
    }
  }

hunter 是什么呢?hunter 的中文意思是猎人或者搜寻者的意思。这里就是负责执行实际请求的类 BitmapHunter,它实现了 Runnable 接口,是 Dispatcher 中 ExecuteService 线程池的实际调度单位。这里先看一下它的 cancel(),后面还会讲它的 submit()。

  boolean cancel() {
    return action == null
        && (actions == null || actions.isEmpty())
        && future != null
        && future.cancel(false);
  }

future 是 Future<?> 类型,它是 ExecuteService.submit() 的返回值,这里我们可以通过其 cancel() 方法来取消 BitmapHunter 的执行。

回到 Picasso 的 enqueueAndSubmit() 中来,下一步就是 submit(action)。submit() 将 action 通过 Dispatcher.dispatchSubmit()提交到 Dispatcher ,Dispatcher 将其转化成消息 REQUEST_SUBMIT 发送到消息队列中,而 REQUEST_SUBMIT 所对应的处理方法是 performSubmit(),因此从 performSubmit() 的代码开始分析 Request 的提交。

void performSubmit(Action action, boolean dismissFailed) {
    ......
    // 已经提交了
    BitmapHunter hunter = hunterMap.get(action.request.key);
    if (hunter != null) {
      hunter.attach(action);
      return;
    }
    // 线程池已经关闭
    if (service.isShutdown()) {
      ......
      return;
    }
   // 创建一个新的 BitmapHunter
    hunter = forRequest(action.picasso, this, cache, stats, action);
   // 将 hunter 提交给线程池
    hunter.future = service.submit(hunter);
   // 已经提交的 hunter 保存在 hunterMap 中
    hunterMap.put(action.request.key, hunter);
    ......
  }

代码中关键的逻辑是创建 hunter 并提交给线程 ExecuteService。这里需要关注一下 forRequest 的实现,其里面有一个关键步骤就是为 Request 选择一下合适的 Handler。

static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher,
      PlatformLruCache cache, Stats stats, Action action) {
    Request request = action.request;
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
    // 选择合适的 Handler 并创建 BitmapHunter
    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);
  }

假设这里给的是 url 地址,且是以 http / https 开头的,那被选中的就是 NetworkRequestHandler。

当一个 Runnable 被提交到线程池 ExecuteService 中去之后,接下来就是等待其被度到,也就是执行其方法 run()。

@Override public void run() {
    try {
      ......
      result = hunt();
      ......
    } catch (NetworkRequestHandler.ResponseException e) {
     ......
    } finally {
      Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
    }
  }

精简一下代码,关键就是调用 hunt()。

Result hunt() throws IOException {
    // 1.先判断缓存中是否存在,如果存在则直接返回
    if (shouldReadFromMemoryCache(data.memoryPolicy)) {
      Bitmap bitmap = cache.get(key);
      if (bitmap != null) {
        ......
        return new Result(bitmap, MEMORY);
      }
    }
    ......
    final AtomicReference<Result> resultReference = new AtomicReference<>();
    final AtomicReference<Throwable> exceptionReference = new AtomicReference<>();
    final CountDownLatch latch = new CountDownLatch(1);
    try {
      // 2. 通过 NetworkRequestHandler 加载图片,并等待其返回。
      requestHandler.load(picasso, data, new RequestHandler.Callback() {
        @Override public void onSuccess(@Nullable Result result) {
          resultReference.set(result);
          latch.countDown();
        }

        @Override public void onError(@NonNull Throwable t) {
          exceptionReference.set(t);
          latch.countDown();
        }
      });

      latch.await();
    } catch (InterruptedException ie) {
      ......
    }
    ......
    Bitmap bitmap = result.getBitmap();
    if (bitmap != null) {
      ......
      // 3.执行所有的 Transformation,获取最终的 result
      List<Transformation> transformations = new ArrayList<>(data.transformations.size() + 1);
      if (data.needsMatrixTransform() || result.getExifRotation() != 0) {
        transformations.add(new MatrixTransformation(data));
      }
      transformations.addAll(data.transformations);
      result = applyTransformations(picasso, data, transformations, result);
     .....
    }
    return result;
  }

关键也就是注释中 3 个步骤:
(1) 先判断缓存中是否存在,如果存在则直接返回。这个在 into() 中也有判断。
(2) 通过 NetworkRequestHandler 加载图片,并等待其返回。关于 NetworkRequestHandler 的 load() 方法的实现,其实就是 OkHttp 的请求过程,这里就没有必要展开了。同时这里使用了 CountDownLatch 类来使得当前线程可以等待异步线程执行完成。这里简要了解一下其原理,即当 CountDownLatch.await() 使得当线程进入 awaiting 状态后,只有通过 countDown() 使得其计数变成 0 后才会唤醒当前的线程。具体原理图如下。

CountDownLatch工作原理-图片来自网络

另外还使用了 AtomicReference,类似的还有 AtomicInteger 等,主要作用便是在并发编程中保证数据操作的原子性。这些都是并发编程的内容,这里只作了解即可。

(3) 执行所有的 Transformation,获取最终的 result。这里的 Transformation 就是通过 RequestCreator 的 transform() 方法所添加的。Transformation 是一个接口,一般我们可在其方法 transform() 中进行自定义处理,处理完后再将新的图片返回。

到这里,请求的提交到执行就分完成了。至此,整个从 Picasso 的初始化到构建 Request 设置参数,再到提交 request 以及 执行 request 都分析完毕了。

四、总结

Picasso 总体来说,其源码难度较小,分析起来也比较轻松。其中最重要的两个细节点在于:

下面再以 Picasso 的框架图来完成这篇文章的全部总结。

Picasso.jpg

最后,感谢你能读到并读完此文章。受限于作者水平有限,如果存在错误或者疑问都欢迎留言讨论。如果我的分享能够帮助到你,也请记得帮忙点个赞吧,鼓励我继续写下去,谢谢。

上一篇 下一篇

猜你喜欢

热点阅读