源码版本 : 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 卡图片的加载.