Picasso(1)-配置

源码版本 : 2.71828 , 简单使用 :

        Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);

首先来看 get() 方法,单例模式获取对象.没什么特别的

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

同时 Picasso 也支持自定义 Picasso , 例如 :

        Picasso picasso = new Picasso.Builder(this)
                .downloader()
                .executor()
                .memoryCache()
                .defaultBitmapConfig()
                .indicatorsEnabled()
                .listener()
                .requestTransformer()
                .build();

        Picasso.setSingletonInstance(picasso);

下面简单介绍下常用配置 :

Downloader

图片下载器, Picasso 中默认实现为 OkHttp3Downloader 即 : OkHttp3 实现的同步下载 ,毕竟是同一家公司的开源项目,可以理解.看下源码.


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

从构造方法可以看出 . 主要配置了磁盘缓存目录和大小 .
默认缓存目录为 : context.getApplicationContext().getCacheDir()/picasso-cache
默认缓存大小为 : 可用空间的 2% , 最大50M ,最小5M
load方法 : OkHttp 同步请求 (网络请求当然要放在异步线程中, Picasso 使用的为 PicassoExecutorService ,稍后介绍)

如果是我自己的项目接入 Picasso 的话 ,从业务角度出发,用户加载图片失败的话 ,要打印相关的 url , 用户 ip 地址..这个很简单,Picasso 使用的
OkHttp ,在 OkHttpClient.Builder() 添加 一个 自定义的 LoggingInterceptor 即可.当然 Picasso 自身也提供了 Listener ,下载失败的回调.
Listener 见下方分析.

executor

picasso 中图片下载是耗时操作,交由 PicassoExecutorService 处理 : 通过下面源码可以看到 核心线程数为 3 ,最大线程数为 3, 使用无界优先级队列

其中 PicassoFutureTask 继承 FutureTask ,实现了 Comparable ;
大家可能注意到了 BitmapHunter ,先简单介绍下, BitmapHunter 实际上实现了 Runnable, BitmapHunter .getPriority(), 实际上就是获取 Request 的 Priority .
排序规则为 : 首先按照 Request 的优先级排列, 如果优先级相同, 则按照任务添加顺序排列 .
PicassoExecutorService 中有个 adjustThreadCount ()方法 ,根据当前 网络状态调整线程池数量. 具体是根据广播来监听的 Dispatcher.NetworkBroadcastReceiver , 在 Dispatcher 中动态注册.

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

Cache

Picasso 的内存缓存默认首先为 LruCache , 名字和 android.util.LruCache 一样 , 看下源码,非常简单

/** A memory cache which uses a least-recently used eviction policy. */
public final class LruCache implements Cache {
  final android.util.LruCache<String, LruCache.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, LruCache.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 实现了 Cache 接口 . 而 Cache 接口提供了对 Bitmap 的 增删查.

从上边源码中可以看到 picasso.LruCache 内部使用的还是 android.util.LruCache , 同时指定了内存缓存大小为
最大可用内存的15% ;

Picasso 磁盘缓存使用的为 OkHttp 的 DiskLruCache . DiskLruCache 稍后再说.

如果你不满意系统的 LruCache ,完全可以实现 Cache 接口 , 实现对应的方法.

indicatorsEnabled

indicatorsEnabled 设置为 true 后 ,标识图片来源. 图片左上角会出现一块颜色区域
红色 : 图片来源于网络
蓝色 : 图片来源于磁盘缓存
绿色 : 图片来源于内存缓存
Debug 的时候 挺好用的, 产品上线要设置为 false.

Listener

图片加载失败回调, Picasso 很贴心的提供了 Listener , 用于上报图片加载失败统计. 当然你也可以像我之前所说的
给 OkHttp 添加 Intercept . 两种方式选一种即可.

  public interface Listener {
    void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception);
  }

RequestTransformer

Request 转换器 , 在提交任务之前,可以对 Request 统一处理 , 注意这个 Request 不是 OkHttp 中的 Request 是
Picasso 中的 Request ,

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

RequestHandler

RequestHandler 请求处理的核心类 ,这里先简单介绍下 , 图片来源有很多 , 例如 本地资源, SD 卡图片, 网络图片等.
RequestHandler 就是用来支持加载各种各样的图片 .
这里的设计思想有点类似于 OkHttp 的 Intercept .
NetworkRequestHandler 处理网络图片的加载
ResourceRequestHandler 处理 app 内图片的加载
FileRequestHandler 处理 sd 卡图片的加载.

猜你喜欢

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