另一个常用的图片库Glide的解析,可以参考这篇文章。
使用默认的内存缓存和磁盘缓存(可自定义)
可自定义图片的变换操作、解析格式等
可以选择裁剪方式、大小等
可以加载网络资源、本地资源等
可以选择内存缓存和磁盘缓存的策略
可以暂停、恢复、取消加载
可以设置加载成功失败的占位图及相应监听
Picasso.with(context)//拿到Picasso对象
.load(url)//load一个url
.resize(width, height)//重新裁剪大小
.memoryPolicy(MemoryPolicy.NO_STORE)//内存缓存策略(不存入内存)
.networkPolicy(NetworkPolicy.OFFLINE)//磁盘缓存策略(不通过网络请求)
.into(imageView);//显示到ImageView中
picasso默认的内存缓存实现是LruCache类,维护一个Lru规则的LinkedList存储key和bitmap
默认的缓存大小为堆内存的1/7,约15%
我们可以自己实现Cache类来设置到Picasso的全局环境中(下面会提到)
picasso提供了两个磁盘缓存类OkHttpDownloader、UrlConnectionDownloader,在引入OkHttp库时会使用OkHttpDownloader
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);
}
OkHttpDownloader里面的操作其实是靠OkHttpClient完成,其通过设置headers的一些属性来实现磁盘缓存,HttpUrlConnection也是通过类似方式实现
OkHttpClient可以设置其Cache对象,默认为一个DiskLruCache,提供get、put等方法,在使用OkHttpClient请求时,会根据其设置headers属性来决定DiskCache的存取
磁盘缓存默认路径为应用的cache文件夹下的picasso-cache文件夹,大小为可用磁盘空间的2%(5~50MB)
我们可以自己实现一个Downloader设置到picasso全局环境中(下面会提到),来进行磁盘缓存
下面就来按步骤分析一下picasso加载图片的流程,首先看Picasso.with(context)获取Picasso对象
由图可知,Picasso是通过builder模式构建的,Builder类提供一些方法设置全局环境,如BitmapConfig、Downloader、cache等,所以我们可以自己创建这样的builder,并设置相应属性,build后调用setSingletonInstance设置到单例对象上即可
默认创建Picasso对象时,使用的是默认的属性,如上面提到的OkHttpDownloader、LruCache等
在我们调用load(url)方法后,返回的是一个新的RequestCreator对象,这个对象是包装各种请求参数用的,其相关方法为链式调用
RequestCreator对象提供了各种占位图的设置方法以及缓存策略的方法
其内部封装了一个Request.Builder对象,用于设置图片相关参数,也是通过builder模式创建;如加载的uri、resize的大小、裁剪方式等,对我们其实是不可见的
由图可知,Action抽象类提供了加载完成后的一些回调方法,不同的实现类用于以不同方式处理加载完成的后续工作,在我们into(iv/target/…)后会构建相应的Action,然后开始加载的流程
ImageViewAction就是into(ImageView)时构建的action,它在加载完成后会将bitmap设置到ImageView上
TargetAction为我们自定义target对象的处理,会将Bitmap回调给相应的方法上供我们处理
dispatcher对象在Picasso对象创建时创建,用于调度派发整个加载流程
内部会创建新的线程及其handler,即任何的派发、调度的请求都会在这个新的线程的handler上进行
创建时会保存住主线程的handler,在结束加载后,将结果返回到主线程上进行
在发起加载请求时,找到相应处理的RequestHandler(下面会说),构建BitmapHunter对象(下面会说,实质是Runnable),并将其放入到ExecutorService线程池进行执行
上面说到的RequestHandler就是用语处理不同源数据的类,具体的实现类处理自己的源(如本地文件的、网络的),最终将流包装到一个内部的Result类对象中返回即可
canHandleRequest会根据load的uri的scheme判断是否是自己要解析的数据源,在dispatcher中构建BitmapHunter时,也是根据该方法找到正确的RequestHandler进行创建的
picasso默认提供一些RequestHandler类,并在创建picasso对象时注册进去,我们也可在上面说的Picasso.Builder中设置自定义的requestHandler
这里举两个RequestHandler的处理:
//本地源解析
@Override public Result load(Request request, int networkPolicy) throws IOException {
return new Result(getInputStream(request), DISK);//将流包装,构造Result对象
}
InputStream getInputStream(Request request) throws FileNotFoundException {
ContentResolver contentResolver = context.getContentResolver();
return contentResolver.openInputStream(request.uri);
}
//网络流解析
@Override public Result load(Request request, int networkPolicy) throws IOException {
Response response = downloader.load(request.uri, request.networkPolicy);//通过downloader加载数据
if (response == null) {
return null;
}
Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
Bitmap bitmap = response.getBitmap();
if (bitmap != null) {
return new Result(bitmap, loadedFrom);
}
InputStream is = response.getInputStream();
if (is == null) {
return null;
}
...
return new Result(is, loadedFrom);//将流包装,构造Result对象
}
可以看到,网络处理中,使用的就是指定的Downloader对象进行加载
上面有了请求类、调度类、加载类和处理类,那么实际的生成Bitmap的类是哪个呢,下面就来看看BitmapHunter这个类,它就是负责管理加载过程和生成bitmap的类
其实质为Runnable,即ExecutorService执行的真正处理对象
他会现根据内存策略决定是否从内存缓存中查找、内存中没有会调用相应requestHandller的load方法进行加载(本地缓存在内部进行)
拿到Result后,根据设置的图片相关参数进行decode,生成Bitmap,再根据默认的和自定义的变换规则进行变换,生成最终的bitmap对象
最终通知dispatcher完成加载,回调结果
这里有必要说下图片在内存缓存和磁盘缓存中对应的key值的形式
生成内存缓存key
static String createKey(Request data, StringBuilder builder) {
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);
}
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(KEY_SEPARATOR);
} else if (data.centerInside) {
builder.append("centerInside").append(KEY_SEPARATOR);
}
if (data.transformations != null) {
//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();
}
如代码所示,每个bitmap的key值就是uri加上各种参数(以KEY_SEPARATOR分割)组合成的String,这也就说明了,不同参数(比如resize不同大小的)请求到的bitmap的是不同的,都会存入cache中
而clear方法清空相应uri的cache时,是以第一个KEY_SEPARATOR前的String做比较,也就是会清空同一uri的所有bitmap
磁盘缓存key
//将Request的url转换为key即可
private static String urlToKey(Request request) {
return Util.md5Hex(request.urlString());
}
//MD5加密转换为16进制字符串
public static String md5Hex(String s) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8"));
return ByteString.of(md5bytes).hex();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
由代码可知,将url进行MD5加密转换为16进制字符串作为key,存放在本地磁盘中
以上把各个模块作用及实现做了简单分析,下面来看看picasso整体的运行流程,将这些模块串联起来
我们就以主流程Picasso.with(context).load(url).into(imageView);来分析加载过程
得到全局的Picasso对象,设置url等相关参数构建请求对象
先去内存缓存找(策略允许的话),如果有则直接返回,没有则进行派发
dispatcher派发时,寻找RequestHandler,并创建BitmapHunter,交由线程池处理
BitmapHunter处理时,先在内存缓存里找(策略允许的话),有的话直接返回,没有的话通过RequestHandler进行处理
RequestHandler由自己的实现类进行相应的处理,网络源时会根据其Downloader对象进行加载数据,根据磁盘缓存策略,从磁盘或者网络读取数据
将数据流封装到Result对象返回
BitmapHunter再等到Result的流后,根据请求时设置的参数进行decode和transformation,生成最终的bitmap对象,通知dispatcher进行最终处理派发
dispatcher将bitmap放入内存缓存(根据缓存策略),再将结果通知主线程做处理
主线程中调用Action的相应处理方法完成最终处理:ImageViewAction就是将Bitmap设置到ImageView上。至此结束加载过程