Picasso - 源码分析 (1)

前言

Picasso 作为 Android 平台上三大图片框架之一。代码非常的优雅、简单 。
为什么我不选择分析 Glide 或 Fresco 呢 ? 代码比 Picasso多,逻辑相对复杂 ,看的我脑阔疼 ! 先找软柿子捏,看看代码最为精炼的 Picasso !
数了一下,一共 36 个类,并不多。
接下来的一系列文章将从源码角度,看看一个优雅的框架是怎样写的 ? 我从这些框架中能学到什么 ?

Picasso

Picasso 基础用法 ,举个例子 :

      Picasso.get()
                .load(url)   //  支持 resourceId 、File  、URI 、path
                .placeholder(R.drawable.place_holder)  // 占位图
                .error(R.drawable.ic_error)  // 加载失败显示图片
                .centerCrop()       // 图片的 ScaleType ,支持 fit 、centerInside、rotate 
                .resize(100,100)  
                .into(imageView);

大家注意 resize(100,100) , 加载 100 * 100 像素的 Bitmap到 ImageView 当中。resize 参数的单位是 px 。
实际开发过程当中,为了适配不同分辨率的手机,要将 dp 换算成 px 。
强烈建议开发过程当中使用这个方法,尤其是在 ListView 或者RecyclerView 当中 。
没看源码前我看别人的博客说 Picasso 将整个 Bitmap对象 加载到了内存当中, 所以 Picasso 的性能不如 Glide 和 Fresco 。
这种说法是不严谨的 , 如果你不设置 resize 。 Picasso 会将整个 Bitmap 加载到内存当中 (除非你知道服务器返回图片的分辨率,否则最好设置 resize) 。
如果你设置了 resize ,那么会将指定大小的 Bitmap 加载到内存当中。 也就是说你实际上加载了 100 * 100 分辨率的图片到内存中。

延伸一下,举个例子 :

        Picasso.get().load("http://i.imgur.com/DvpvklR.png").resize(100, 100).into(ivSrc);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Picasso.get().load("http://i.imgur.com/DvpvklR.png").centerCrop().resize(100, 100).into(ivSrc2);
            }
        }, 10000);

两个 ImageView 加载图片地址相同, 第二个 ImageView 设置了 CenterCrop 。
加载第二个 ImageView 的时候我设置了 10 s 延迟,目的是首先加载第一张图片到内存当中。
那么问题来了 ? 第二张图片的加载是直接从内存中加载还是从磁盘中加载 ? (第二张图片不可能从网络中加载,因为 url 相同,加载第一张图片的时候下载了整个图片到磁盘。)
答案是从磁盘加载。因为内存缓存的 key 是不一样的 。Picasso 在内存缓存的 key , 是图片的 Uri 加上设置的参数,例如 rotation 、resize、centerCrop 等。
这里简单提一下,具体的代码在 Utils 类的 createKey 的方法中,之后也会具体分析。

源码分析之 Builder

上面扯了这么多,下面直接进入正题吧 。
正常情况引入第三方框架, 直接使用的不多。很多时候要根据项目来对第三方框架做额外的配置 。
Picasso 采用建造者模式进行额外的配置。
Picasso 的 Builder :
这里写图片描述

Context

建造者模式必须指定 Context 。这点没有什么好说的 。
但是你还记得旧版本 Picasso 是怎么调用的吗 ? 旧版本的 Picasso 每次调用都需要指定 Context 对象。

Picasso.with(this)

但是新版本不需要指定

Picasso.get()

这是为什么呢 ? 进源码看下, 原来 Context 使用的是 PicassoProvider.context 。

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

PicassoProvider 是什么呢 ? 原来是 ContentProvider 。PicassoProvider 继承 ContentProvider , onCreate 的时候声明了 Context 对象。
PicassoProvider 的 onCreate() 方法回调是要早于 Application 的 onCreate() 。
如果你是手动导入 Picasso 到工程中,记得在 AndroidManifest.xml 中声明 PicassoProvider , 否则当你调用 Picasso.get() 就会 Crash 。

public final class PicassoProvider extends ContentProvider {

  @SuppressLint("StaticFieldLeak") static Context context;

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

Downloader

顾名思义,下载器。从磁盘或网络加载图片。

public interface Downloader {
  Response load(okhttp3.Request request) throws IOException;
  void shutdown();
}

注意到接口中声明了两个方法。 第一个方法参数为 Request , 方法返回值为 Reponse 。
很明显如果我们要自己实现 Downloader , 需要使用同步方法。
第二个方法 shutdown 。正常开发中,几乎不使用这个方法。这个方法是用来关闭磁盘缓存和其他资源。(看的一头雾水吧 ! )
正常情况我们是使用 Picasso 是单例获取 Picasso 对象。 某些特殊情况下,你创建了多个 Picasso 的实例,当你不再需要使用其他 Picasso实例的时候,你可以调用此方法。
贴上代码看下 : 首先就判断了当前对象是否和单例是同一对象。也就是说这个方法是用来回收其他 Picasso 实例资源的。例如回收内存缓存、取消请求、关闭线程池等。

   public void shutdown() {
    if (this == singleton) {
      throw new UnsupportedOperationException("Default singleton instance cannot be shutdown.");
    }
    if (shutdown) {
      return;
    }
    cache.clear();
    cleanupThread.shutdown();
    stats.shutdown();
    dispatcher.shutdown();
    for (DeferredRequestCreator deferredRequestCreator : targetToDeferredRequestCreator.values()) {
      deferredRequestCreator.cancel();
    }
    targetToDeferredRequestCreator.clear();
    shutdown = true;
  }

如果你对 shutdown 还有疑问,可以访问这个链接 issue
Picasso 中Downloader 的唯一实现类为 OkHttp3Downloader 。也就是说 磁盘缓存和网络请求都交由 OkHttp 处理。

public final class OkHttp3Downloader implements Downloader {
  @VisibleForTesting final Call.Factory client;
  private final Cache cache;
  private boolean sharedClient = true;
  public OkHttp3Downloader(final Context context) {
    this(Utils.createDefaultCacheDir(context));
  }
  public OkHttp3Downloader(final File cacheDir) {
    this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
  }
  public OkHttp3Downloader(final Context context, final long maxSize) {
    this(Utils.createDefaultCacheDir(context), maxSize);
  }
  public OkHttp3Downloader(final File cacheDir, final long maxSize) {
    this(new OkHttpClient.Builder().cache(new Cache(cacheDir, maxSize)).build());
    sharedClient = false;
  }
  public OkHttp3Downloader(OkHttpClient client) {
    this.client = client;
    this.cache = client.cache();
  }
      public OkHttp3Downloader(Call.Factory client) {
    this.client = client;
    this.cache = null;
  }
  @NonNull @Override public Response load(@NonNull Request request) throws IOException {
    return client.newCall(request).execute();
  }

  @Override public void shutdown() {
    if (!sharedClient && cache != null) {
      try {
        cache.close();
      } catch (IOException ignored) {
      }
    }
  }
}

我们看到 OkHttp3Downloader 这个类基本做了三件事。
1. 初始化了 OkHttpClient 指定了磁盘缓存的目录。
2. 实现了 load 方法,同步请求。
3. 实现了 shutdown 方法,关闭磁盘缓存。
如果项目中需要对图片加载实现细粒度的监控,例如当加载失败的时候,记录访问失败的 IP 地址、统计加载失败、加载成功的次数等。
可以参照 OkHttp3Downloader 来实现。这实际上就涉及到了 OkHttp Interceptor 的相关知识了。
当然 Picasso 也有提供图片加载失败的回调 Listener,但是可定制程度不高,下面会介绍到 。
大家都知道磁盘缓存和网络请求是耗时任务,需要异步加载。但是这个接口 load() 方法是同步方法。那么 Picasso 当中怎么处理异步请求的呢。
Picasso 自定义了线程池来处理异步任务,官方解释这样做的一个好处是开发者可以提供自己的实例来替代 Picasso 提供的 PicassoExecutorService 。
仔细想想,这就是优秀的框架写法,高扩展性。满足开发者不同的业务需求。
接下来介绍 ExecutorService 。

ExecutorService

Picasso 中的实现类为 PicassoExecutorService 。乍一看代码有点多,脑壳有点疼。
仔细看下,去除 adjustThreadCount() 方法,不到 20 行代码。一下子就豁然开朗、神清气爽 !
adjustThreadCount 很简单,根据当前网络状态, 设置线程池最多线程数。虽然国内基本都是 4G 和 wifi ,不过优秀的框架就是严谨,各种情况都考虑到了,追求最优的性能。
我们可以看到线程池默认维护了三个核心线程,但是根据网络状态,又进行调整。wifi 下维护了四个线程, 4G网络状态维护了三个线程。

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

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

  private void setThreadCount(int threadCount) {
    setCorePoolSize(threadCount);
    setMaximumPoolSize(threadCount);
  }

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

  private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
      implements Comparable<PicassoFutureTask> {
    private final BitmapHunter hunter;

    PicassoFutureTask(BitmapHunter hunter) {
      super(hunter, null);
      this.hunter = hunter;
    }

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

在写法上还有一点可以借鉴,按优先级来执行任务。当指定 ThreadFactory 的时候,我们注意到使用了 PicassoThreadFactory 。
点进去看一下,值得注意的是 PicassoThread 。 在调用 run 方法的时候设置了线程优先级。
等等 ! 似乎和我平常设置线程优先级的方法不同。

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

....
  static class PicassoThreadFactory implements ThreadFactory {
    @SuppressWarnings("NullableProblems")
    public Thread newThread(Runnable r) {
      return new PicassoThread(r);
    }
  }

  private static class PicassoThread extends Thread {
    PicassoThread(Runnable r) {
      super(r);
    }

    @Override public void run() {
      Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
      super.run();
    }
  }

我之前设置线程优先级用的 Java 提供的 API 。

   Thread.currentThread().setPriority(NORM_PRIORITY + 1);

但是 Picasso 中通过 Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND), 来设置线程优先级,这个是 Android 提供的 API 。
那么问题来了 ? 两者之间具体的区别是什么呢 ?
stackoverflow 和网上搜索了一些资料,有了模糊的概念。
Process.setThreadPriority() 为 Android 提供 API,基于 linux 优先级设置线程优先级,从最高优先级 -20 到 最低优先级 19 。(看起来调度粒度更细,效果更好。)
Thread.currentThread().setPriority() 为 Java 提供API,从最高优先级 10 到最低优先级 1 。
Process.setThreadPriority() 并不影响 Thread.currentThread().getPriority() 的值。
同时还注意到了一点,不要轻易在主线程设置 Process.setThreadPriority(), 这个哥们遇到了个
对两者之间的区别掌握还是不够,如果你知道,烦请留言告诉我。

Cache

Picasso 中涉及到缓存的只有内存缓存和磁盘缓存。
磁盘缓存 : DiskLruCache ,直接交由 OkHttp 来管理。
内存缓存 : LruCahe 。
Picasso 中 LruCache 实现了 Cache 接口 。接口定义的挺好,以后定义其他类型的缓存接口,可以参考这个。

public interface Cache {
  Bitmap get(String key);

  void set(String key, Bitmap bitmap);

  int size();

  int maxSize();

  void clear();

  void clearKeyUri(String keyPrefix);
}

LruCache 内部实际上使用 android 的 LruCache 。 LruCache 原理这里就不提了,因为涉及的东西比较多。
以后还是单独开个文章来写 HashMap -> LinkedHashMap->LruCache-> DiskLruCache 。

public final class LruCache implements Cache {
  final android.util.LruCache<String, BitmapAndSize> 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));
  }

  /** Create a cache with a given maximum size in bytes. */
  public LruCache(int maxByteCount) {
    cache = new android.util.LruCache<String, BitmapAndSize>(maxByteCount) {
      @Override protected int sizeOf(String key, BitmapAndSize value) {
        return value.byteCount;
      }
    };
  }

  @Nullable @Override public Bitmap get(@NonNull String key) {
    BitmapAndSize bitmapAndSize = cache.get(key);
    return bitmapAndSize != null ? bitmapAndSize.bitmap : null;
  }

  @Override public void set(@NonNull String key, @NonNull Bitmap bitmap) {
    if (key == null || bitmap == null) {
      throw new NullPointerException("key == null || bitmap == null");
    }

    int byteCount = Utils.getBitmapBytes(bitmap);

    // If the bitmap is too big for the cache, don't even attempt to store it. Doing so will cause
    // the cache to be cleared. Instead just evict an existing element with the same key if it
    // exists.
    if (byteCount > maxSize()) {
      cache.remove(key);
      return;
    }

    cache.put(key, new BitmapAndSize(bitmap, byteCount));
  }

  @Override public int size() {
    return cache.size();
  }

  @Override public int maxSize() {
    return cache.maxSize();
  }

  @Override public void clear() {
    cache.evictAll();
  }

  @Override public void clearKeyUri(String uri) {
    // Keys are prefixed with a URI followed by '\n'.
    for (String key : cache.snapshot().keySet()) {
      if (key.startsWith(uri)
          && key.length() > uri.length()
          && key.charAt(uri.length()) == KEY_SEPARATOR) {
        cache.remove(key);
      }
    }
  }

  /** Returns the number of times {@link #get} returned a value. */
  public int hitCount() {
    return cache.hitCount();
  }

  /** Returns the number of times {@link #get} returned {@code null}. */
  public int missCount() {
    return cache.missCount();
  }

  /** Returns the number of times {@link #set(String, Bitmap)} was called. */
  public int putCount() {
    return cache.putCount();
  }

  /** Returns the number of values that have been evicted. */
  public int evictionCount() {
    return cache.evictionCount();
  }

  static final class BitmapAndSize {
    final Bitmap bitmap;
    final int byteCount;

    BitmapAndSize(Bitmap bitmap, int byteCount) {
      this.bitmap = bitmap;
      this.byteCount = byteCount;
    }
  }
}

通过这个类还可以学到获取最大可用内存的方法。Picasso 中获取了最大可用内存的七分之一来作为 LruCache 最大容量。这个方法果断收藏到自己的类库中。

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

Listener

Listener 所有图片加载失败都会走这个回调,注释也写的比较清楚,主要为了统计、分析使用。

  public interface Listener {
    /**
     * Invoked when an image has failed to load. This is useful for reporting image failures to a
     * remote analytics service, for example.
     */
    void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception);
  }

如果是想要监听单个图片加载失败后的调用。

Picasso.get().load("").into(imageView,new Callback())。

我上面的代码对吗 ? 写的有问题哦 ! 哪里有问题呢 ? 匿名内存类持有外部类的引用,导致 Activity 和 Fragment 不能被回收。
怎么解决呢 ? Activity 或 Fragment onDestroy 的时候取消请求。(事实上 OkHttp 回调也有类似内存泄露的问题。)
解决方案 Picasso 在方法上明确注释了, 所以阅读源码的时候,多看看注释还是有好处的。

Picasso.get().cancelRequest(); 
或者
Picasso.get().cancelTag();

有的时候我们不需要加载图片到 ImageView 当中, 仅仅需要获取 Bitmap 对象。你可以这样写,不要着急请往下看,
此处有坑 !

    Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(new Target() {
            @Override
            public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {

            }

            @Override
            public void onBitmapFailed(Exception e, Drawable errorDrawable) {

            }

            @Override
            public void onPrepareLoad(Drawable placeHolderDrawable) {

            }
        });

我上面的写法对吗 ? 也不对哦 ! 哪里不对呢 ! 赶紧点进去源码看注释,注释写了这个方法持有 Target 实例的弱引用。如果你不持有强引用的话,会被垃圾回收器回收。我里个龟龟哦 ! 这句话告诉我们,这样写可能永远都不会有回调。
那么办呢 ? 不要使用匿名内部类,使用成员变量吧。事实上我们传入的 ImageView 也是持有的弱引用。

private Target target = new Target() {
      @Override
      public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
      }

      @Override
      public void onBitmapFailed(Drawable errorDrawable) {
      }

      @Override
      public void onPrepareLoad(Drawable placeHolderDrawable) {
      }
}

private void someMethod() {
   Picasso.with(this).load("url").into(target);
}

@Override 
public void onDestroy() {  // could be in onPause or onStop
   Picasso.with(this).cancelRequest(target);
   super.onDestroy();
}

官方推荐在自定义 View 或者 ViewHolder 中实现 Target 接口。 例如 :

  public class ProfileView extends FrameLayout implements Target {
      {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
       setBackgroundDrawable(new BitmapDrawable(bitmap));
     }

      {@literal @}Override public void onBitmapFailed(Exception e, Drawable errorDrawable) {
        setBackgroundDrawable(errorDrawable);
      }

      {@literal @}Override public void onPrepareLoad(Drawable placeHolderDrawable) {
        setBackgroundDrawable(placeHolderDrawable);
      }
    }
   public class ViewHolder implements Target {
      public FrameLayout frame;
      public TextView name;

      {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
        frame.setBackgroundDrawable(new BitmapDrawable(bitmap));
     }

      {@literal @}Override public void onBitmapFailed(Exception e, Drawable errorDrawable) {
        frame.setBackgroundDrawable(errorDrawable);
      }

      {@literal @}Override public void onPrepareLoad(Drawable placeHolderDrawable) {
        frame.setBackgroundDrawable(placeHolderDrawable);
     }

上述的例子,点进源码看上边的注释就能看到。

对于 Target,如果你还有疑问 ,你可以参考这篇文章

RequestTransformer

在每个请求提交之前,可以对请求进行额外的处理。
注意这里的 Request 是 Picasso 中的 Request , 不是 OkHttp3 中的 Request 。
官方对这个方法的解释是 : 为了更快的下载速度,你可以更改图片的 hostname 为离用户最近的 CDN的 hostname。

  public interface RequestTransformer {
    Request transformRequest(Request request);
  }

但我们是否能利用这个接口来做些其他的事呢,我们看下这个接口什么时候调用。是在 RequestCreator 的 createRequest() 方法中调用。而 createRequest() 基本上在
RequestCreator 的 into 方法中调用, 可以说该方法调用是相当早的。从这个角度来说,除了更改 hostname 以外,我们并不能做太多的事。因此注释也说了, 这个功能是个测试的功能,之后可能会改变。所以大家不需要花太多精力在这个 API 上。

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

RequestHandler

RequestHandler 请求处理器, Picasso 中 重要的抽象类。

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;

  int getRetryCount() {
    return 0;
  }

  boolean shouldRetry(boolean airplaneMode, NetworkInfo info) {
    return false;
  }

  boolean supportsReplay() {
    return false;
  }
}

还有几个静态的方法,我没有帖上去,我觉得那几个方法单独抽取到 Utils 中是否会更好一点 ?
我们注意到 RequestHandler 有两个很重要的抽象的方法。
canHandleRequest() : 是否能够处理给定的 Request 。
load() : 如果 canHandleRequest() 返回值为 true 。那么之后就会执行 load () 方法 。

public abstract boolean canHandleRequest(Request data);
public abstract Result load(Request request, int networkPolicy) throws IOException;

我们知道 Picasso.get().load() 方法可以传入 File 、Uri、ResourceId 。
不同的参数是怎么处理的呢 ? 就是让对应的 RequestHandler 子类来实现 。
看下继承关系 :

这里写图片描述
可以看出扩展性好,结构清晰。嗯 ,如果让我这种菜鸟写这样的逻辑话,最初的代码如下: if-else 多重嵌套 , 所有的逻辑都放在一个类 ,扩展性和可读性都不好。嗯,又学到了一点 !

    if (resourceId != 0) {

        } else if(file != null){

        } else if (uri != null) {

        } else if () {

        }
....

那么简单介绍下,这些实现类分别是具体解决哪些问题 :
AssetRequestHandler : 从 assets 目录下加载图片。
这里写图片描述
有的同学说,我怎么没有这个目录 ? 你可以创建一个 asset 文件,然后放张图片进去。
这里写图片描述
具体加载的代码是这样的 : 其中 file:///android_asset/ 为固定格式 。。

Picasso.get().load("file:///android_asset/icon_flower.jpg").into(ivSrc);

有同学可能会说 ,我直接用系统 API 也能加载 ,干嘛用 Picasso 。嗯 , 你说的没错
使用 Picasso 的好处呢有两点 :
1. 内存缓存,如果需要重复加载的话,加载速度很快。
2. 一行代码,链式调用,非常的舒服 !

ResourceRequestHandler

加载 res下的图片,通过资源 Id 或者 资源 Id 的 URI 来加载图片 。

Picasso.get().load(R.drawable.test_img).into(ivSrc);
//两种写法效果相同,基本上都是用的是上边的方法。
Picasso.get().load("android.resource://" + getPackageName() + "/" + R.drawable.test_img).into(ivSrc);

ContactsPhotoRequestHandler

加载联系人头像,讲道理,我还从来没有加载过通讯录联系人头像,开发过程中用的确实不多。
测试起来太麻烦了,我就偷懒不写代码了。
如果大家对这个感兴趣的话,可以参考这篇文章

NetworkRequestHandler

重点来了, 从网络中加载图片 。我们简单来看下 load ()方法 ,只看大致的流程,具体的实现细节我们稍后再进行分析。
首先将 Picasso 中的 Request 对象转换为 OKHttp 中的 Request 对象 。
然后使用 OkHttp3Downloader 同步下载图片。
之后判断请求是否成功 , 文件 content-length 是否为 0 。
整个过程很简单。

 @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.");
    }
    if (loadedFrom == NETWORK && body.contentLength() > 0) {
      stats.dispatchDownloadFinished(body.contentLength());
    }
    return new Result(body.source(), loadedFrom);
  }

ContentStreamRequestHandler

这个不好翻译,有人翻译成从 ContentProvider 中加载图片,我觉得不太准确 。 虽然 ContentStreamRequestHandler 处理的 URI Scheme 为 content ,但是在设计上 FileRequestHandler 却继承自 ContentStreamRequestHandler , 处理的 URI Scheme 为 file 。
ContentStreamRequestHandler 及其子类 可以处理 File 、相册图片、视频缩略图。

Bitmap.Config

图片压缩质量参数
ARGB_8888 每个像素占用四个字节。
ARGB_4444 每个像素占两个字节 。已被废弃, API 19 以上 将被替换为 ARGB_8888 。
RGB_565 每个像素占两个字节。
ALPHA_8 每个像素占用1字节(8位),存储的是透明度信息。
Glide 默认使用 RGB_565 ,因此有些人说 Glide 性能优化好 !!! 其实把你只要设置 Picasso 的 Bitmap.Config , 同时也设置 Picasso 的 resize () 。从性能上,
我觉得并不必 Glide 差。

indicatorsEnabled

是否显示图片从哪里加载。
红色 : 网络加载。
蓝色 : 磁盘加载 。
绿色 : 内存中加载。
开发过程中,可以开启。便于我们 debug ,正式环境记得关闭。

loggingEnabled

是否显示 Picasso 执行过程中日志 ,也是便于我们 debug,正式环境记得关闭。

总结

  1. 写博客真的蛮累的。单单 Picasso 建造者模式 , 花了差不多两天的时间 , 自己一边看源码,一边写总结,总觉得写的不好,写的太浅或者词不达意,担心写出来被大家笑话。
    写完之后总觉得缺了点什么,后来想想是没能从设计模式的角度来分析框架。而数据结构这一块,乍一看,Picasso 好像没涉及到,但实际上 LruCache 、DikLruCache、Okio 内部都涉及到数据结构。
  2. 初读源码,千万不要过多纠结于方法细节,首先关注整个流程。

本文查阅的相关链接
https://stackoverflow.com/questions/5198518/whats-the-difference-between-thread-setpriority-and-android-os-process-setthr
https://blog.csdn.net/oujunli/article/details/51971247
https://droidyue.com/blog/2015/09/05/android-process-and-thread-schedule-nice/
https://stackoverflow.com/questions/20181491/use-picasso-to-get-a-callback-with-a-bitmap

猜你喜欢

转载自blog.csdn.net/stupid56862/article/details/80846342