1、简介
Picasso是由Square公司推出的一个功能强大的Android图片加载和缓存库;有人经常拿它和glide相比,而且glide的使用频率更高;这时候肯定得有但是啊,下面来看看2.71828版本有哪些特点
- 默认采用三级缓存LRU策略
- 可以采取不同的图片格式进行内存缓存、并且按照图片期望大小来缓存
- 磁盘缓存采用okHttp的缓存策略,全尺寸缓存;如果不采用默认实现需要自己实现
- 可以取消暂停下载
和glide比较,明显的差异性,主要在以下方面
- picasso不能自动处理生命周期,但是却减少了glide因为生命周期而产生的crash;
- 内存溢出,picasso提供了图片格式选择,而且内存缓存期望大小图片,和glide并没有实质区别
- glide可以加载gif等, picasso默认实现不支持;但这种场景多吗?性能好吗?
- 包体积大小,picasso确实有优势,但其需要依赖okhttp库,也可以自定义实现
- 使用方便,glide确实使用的方便很不错;但是进行内存优化,同样都是门槛有点(不过我一直认为,图片按需请求、加载才是省流性能优化的王道,但是基本没有见服务器端会给做。。。)
- picasso默认实现不支持请求drawable的图;Drawable是一种可绘制在画布的图形,bitmap是jpg、png等格式的图片;drawable绘制更好,未看glide的源码,因为资源是位图,如果请求drawable,肯定是要转换的;
从现在这个版本来看,实质上没有多大区别了,但是glide已经抢先占领市场了,picasso只能在后面吃灰了,不过这只是作为吃瓜群众看看而已;使用选择,我是建议,如果使用了Okhttp库,picasso会是个不错的选择
2、基础知识
涉及的基础知识,主要介绍图片和缓存内容(内容均以Picasso中实现为模板)
- 图片的网络请求
- 图片解析
- 采样率压缩
- 图片平移缩放
- LRU缓存
2.1 图片网络请求
使用Okhttp请求时,不需要做额外的请求头处理,只需要添加缓存策略即可;okhttp会自动添加请求类型等信息;如果需要了解请求报文具体添加哪些信息,可以查看OkHttp手把手教你网络编程
2.2 图片解析
涉及两个类:BitmapFactory, BitmapFactory.Options;BitmapFactory中有一些解析图片方法,从流、文件、byte数组、资源id中;Options主要对解析做了以下限制
- inJustDecodeBounds:如果为true,不必全部解析图片内容,只需要解析图片相关信息即可
- inSampleSize:解析图片是,对图片进行缩放倍数
- inPreferredConfig:图片解析时预期格式,如果不能解析,则按照ARGB_8888格式
- inPurgeable:系统内存不足时是否可以回收这个bitmap,有点类似软引用,但是实际在5.0以后这两个属性已经被忽略
- inInputShareable:inPurgeable为true时才有效,且5.0以后被忽略;表示多个引用时是否深copy
2.3 采样率压缩
- 设置inJustDecodeBounds=true解析图片,解析图片实际大小
- 求出缩放因子,从 预期宽度/实际宽度 和 预期高度/实际高度中取
- inJustDecodeBounds=false解析图片
其实图片有时候还需要进行压缩,经典的压缩写法,利用ByteArray输入输出流和bitmap的类方法compress
ByteArrayOutputStream baos = new ByteArrayOutputStream();
wallpaperBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
InputStream inputStream = new ByteArrayInputStream(baos.toByteArray());
compress中参数介绍:
- android中有三种格式,jpeg,png,webp
- 压缩质量,从0-100, 100表示无损压缩
- 输出流
使用compress压缩,不能改变图片的宽高,也就是改变不了内存中大小,只是可以改变文件中大小
2.4 图片平移缩放
利用Bitmap的静态方法处理
createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter)
Matrix中包含缩放和平移操作
2.5 缓存逻辑
-
请求对象,使用RequestWeakReference来进行弱引用缓存,使用ReferenceQueue来清除无效的关联动作,以减少无效的对象大量堆积
-
内存缓存采用sdk中LruCache进行处理,磁盘缓存采用DiskLruCache缓存 LruCache:是通过HashMap结构+双链表结构,hashMap用来增删改查,双链表顺序,标记最近使用顺序
-
磁盘缓存,使用DiskLruCache,其存储一个journal操作日志文件,和一些以名字为key的文件,这些文件才是实际缓存;其中记录有四种类型DIRTY,CLEAN,REMOVE, READ;这里记录会临时存在LruCache中,用来达到阈值时进行移除操作;
- 读操作时,写入READ记录
- 写操作首先写入DIRTY记录,如果后面未有CLEAN记录,则是无效记录
- 删除写入REMOVE;
- 根据操作,LruCache增删改查并调整最近使用,同时保证磁盘读写,磁盘写成功,写入CLEAN记录;
3、源码分析
最常见使用方式:
Picasso.get()
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.resizeDimen(100, 100)
.centerInside()
.tag(context)
.transform(new GrayscaleTransformation(Picasso.get()))
.memoryPolicy(MemoryPolicy.NO_CACHE)
.networkPolicy(NetworkPolicy.OFFLINE)
.into(image);
这行代码进行了picasso库中常用的一些操作:设置网络缓存参数,默认图片,请求错误图片,期望大小,设置请求tag方便取消暂停恢复等操作
3.1 Picasso类
Picasso对象应该是单例的,包含处理必要元素以及可以设置一些通用设置;
- 默认通用配置,直接使用静态方法get
- 修改默认配置使用Picasso.Builder来处理,然后使用Picasso静态方法setSingletonInstance设置实例;使用时依然使用get方法即可
get静态方法很巧妙,利用了ContentProvider在应用创建时就会初始化,而获取应用上下文
private final Context context;
private Downloader downloader;
private ExecutorService service;
private Cache cache;
private Listener listener;
private RequestTransformer transformer;
private List<RequestHandler> requestHandlers;
private Bitmap.Config defaultBitmapConfig;
private boolean indicatorsEnabled;
private boolean loggingEnabled;
Picasso利用构建者模式,可以设置以上通用设置:
- 上下文环境
- 网络下载器,默认okhttp下载
- 执行线程,默认PicassoExecutorService(可以根据网络情况来改变线程池个数;仅有核心线程的线程池)
- 请求对象转换,默认不做任何处理
- 各种图片资源实际查找加载者:默认已实现文件、多媒体、Assets资源、http资源、联系人图片资源、contentProvider提供资源
- 内存缓存处理,默认使用LruCache
- 失败监听
- 图片解析时默认选项
- 是否显示log、指示器
普通处理者,基本不用个性化定制通用配置;特别注意,如果定制下载器,要注意实现磁盘缓存
必要元素:
private final CleanupThread cleanupThread;
final Dispatcher dispatcher;
final Stats stats;
final Map<Object, Action> targetToAction;
final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;
final ReferenceQueue<Object> referenceQueue;
- 请求填充对象的弱引用队列,和清理线程;(into方法传入参数,视为填充对象)
- 请求处理消息调度器
- 库中图片请求缓存等情况
- 填充对象--和请求动作信息集合
- ImageView为填充对象时,fit操作处理集合(fit操作的,必须是调用into方法,且是ImageView填充,否则会报错,请谨记使用)
Picasso 主要操作如下:方法如何实现,在后续会慢慢讲
- 暂停、取消、恢复 tag对应的下载:cancelRequest,cancelTag,pauseTag,resumeTag
- 清理某一些图片:invalidate
- 获取当前图片库对图片处理的情况:getSnapshot方法
3.2 RequestCreator类
如果Picasso中记录者通用配置,那么此类中的配置就是针对每个图片的请求了,同时也是请求处理启动的地方
可配置信息如下:
private final Request.Builder data;
private boolean noFade;
private boolean deferred;
private boolean setPlaceholder = true;
private int placeholderResId;
private int errorResId;
private int memoryPolicy;
private int networkPolicy;
private Drawable placeholderDrawable;
private Drawable errorDrawable;
private Object tag;
- 没有动画效果;图片加载完成之后,载入是无动画效果
- 是否延时处理,延时处理会在ImageView宽高测量结束后,去获取图片,并裁剪
- 是否设置过过默认图片
- 请求失败图片
- 内存策略
- 网络请求策略
- 请求tag标志
- 请求到图片的旋转、平移、缩放、其它转换效果信息
同步请求开始
public Bitmap get() throws IOException {
long started = System.nanoTime();
checkNotMain();
if (deferred) {
throw new IllegalStateException("Fit cannot be used with get.");
}
if (!data.hasImage()) {
return null;
}
Request finalData = createRequest(started);
String key = createKey(finalData, new StringBuilder());
Action action = new GetAction(picasso, finalData, memoryPolicy, networkPolicy, tag, key);
return forRequest(picasso, picasso.dispatcher, picasso.cache, picasso.stats, action).hunt();
}
Action记录请求的信息:包括请求结果的转换信息、网络策略、内存策略、tag、和按照request对象得到的key标志; forRequest方法,生成了请求处理图片的线程BitmapHunter对象,hunt() 方法可以得到图片
异步请求
fetch方法
public void fetch(@Nullable Callback callback) {
long started = System.nanoTime();
if (deferred) {
throw new IllegalStateException("Fit cannot be used with fetch.");
}
if (data.hasImage()) {
if (!data.hasPriority()) {
data.priority(Priority.LOW);
}
Request request = createRequest(started);
String key = createKey(request, new StringBuilder());
if (shouldReadFromMemoryCache(memoryPolicy)) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(key);
if (bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}
Action action =
new FetchAction(picasso, request, memoryPolicy, networkPolicy, tag, key, callback);
picasso.submit(action);
}
}
优先从缓存中获取,如果没有,则生成Action,并通过Picasso对象提交任务,进而通过Dispatcher来调度处理
into方法也是大同小异,只不过生成的Action不同,提交任务时需要在Picasso中放置集合中,以做到同一个taget,均请求最新的要求;
3.3 Dispatcher类
利用handler消息,进行调度处理
static final int REQUEST_SUBMIT = 1;
static final int REQUEST_CANCEL = 2;
static final int REQUEST_GCED = 3;
static final int HUNTER_COMPLETE = 4;
static final int HUNTER_RETRY = 5;
static final int HUNTER_DECODE_FAILED = 6;
static final int HUNTER_DELAY_NEXT_BATCH = 7;
static final int HUNTER_BATCH_COMPLETE = 8;
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;
请求提交消息、请求取消消息、请求取消包括延时任务、请求成功、请求失败、请求重试、暂停、恢复、飞行模式、网络变化
3.3.1 请求提交消息;
void performSubmit(Action action, boolean dismissFailed) {
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
}
return;
}
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
if (service.isShutdown()) {
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}
}
- 如果在暂停动作中,则不进行处理
- 如果key对应的执行BitmapHunter任务存在,动作依附到任务上,返回
- 任务线程关闭,则返回
- 生成任务实例,并加入任务集合中,并提交到线程池进行执行,失败动作集合去除此动作
3.3.2 取消消息
void performCancel(Action action) {
String key = action.getKey();
BitmapHunter hunter = hunterMap.get(key);
if (hunter != null) {
hunter.detach(action);
if (hunter.cancel()) {
hunterMap.remove(key);
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId());
}
}
}
if (pausedTags.contains(action.getTag())) {
pausedActions.remove(action.getTarget());
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId(),
"because paused request got canceled");
}
}
Action remove = failedActions.remove(action.getTarget());
if (remove != null && remove.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_CANCELED, remove.getRequest().logId(), "from replaying");
}
}
- 如果动作相关任务仍在运行,则取消依附,并停止任务
- 暂停、失败动作集合移除此动作
3.3.3 弱引用对象无效清除Action消息
void cancelExistingRequest(Object target) {
checkMain();
Action action = targetToAction.remove(target);
if (action != null) {
action.cancel();
dispatcher.dispatchCancel(action);
}
if (target instanceof ImageView) {
ImageView targetImageView = (ImageView) target;
DeferredRequestCreator deferredRequestCreator =
targetToDeferredRequestCreator.remove(targetImageView);
if (deferredRequestCreator != null) {
deferredRequestCreator.cancel();
}
}
}
进行取消处理,如果是ImageView的图片请求,去掉延迟请求任务
3.3.4 请求成功消息
void performComplete(BitmapHunter hunter) {
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
hunterMap.remove(hunter.getKey());
batch(hunter);
if (hunter.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
}
}
private void batch(BitmapHunter hunter) {
if (hunter.isCancelled()) {
return;
}
if (hunter.result != null) {
hunter.result.prepareToDraw();
}
batch.add(hunter);
if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
}
}
- 如果需要进行内存缓存,则进行缓存处理
- 移除任务;并加入结果队列中,进行延时处理
3.3.5 重试消息
void performRetry(BitmapHunter hunter) {
if (hunter.isCancelled()) return;
if (service.isShutdown()) {
performError(hunter, false);
return;
}
NetworkInfo networkInfo = null;
if (scansNetworkChanges) {
ConnectivityManager connectivityManager = getService(context, CONNECTIVITY_SERVICE);
networkInfo = connectivityManager.getActiveNetworkInfo();
}
if (hunter.shouldRetry(airplaneMode, networkInfo)) {
if (hunter.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_RETRYING, getLogIdsForHunter(hunter));
}
if (hunter.getException() instanceof NetworkRequestHandler.ContentLengthException) {
hunter.networkPolicy |= NetworkPolicy.NO_CACHE.index;
}
hunter.future = service.submit(hunter);
} else {
boolean willReplay = scansNetworkChanges && hunter.supportsReplay();
performError(hunter, willReplay);
if (willReplay) {
markForReplay(hunter);
}
}
}
- 检查任务、线程池状态
- 任务是否可以重试,可以重试,则重新在线程池中提交任务(网络请求,默认可以重试两次)
- 如果任务不可以重试,且当前网络发生变化时可重试,则把任务放入failed任务队列中;等待网络变化时,重新处理
3.3.6 失败消息
void performError(BitmapHunter hunter, boolean willReplay) {
if (hunter.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter),
"for error" + (willReplay ? " (will replay)" : ""));
}
hunterMap.remove(hunter.getKey());
batch(hunter);
}
移除任务,并加入结果处理集合,进行延时处理
3.3.7 结果队列继续处理消息
copy已经请求有结果的任务,并清除结果,发送结果处理消息
void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}
3.3.8 结果处理消息
case HUNTER_BATCH_COMPLETE: {
List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
for (int i = 0, n = batch.size(); i < n; i++) {
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);
}
break;
}
complete方法中最终会根据hunter任务结果,调用依附其的Action的complete方法或者error方法
3.3.9 网络变化消息
void performNetworkStateChange(NetworkInfo info) {
if (service instanceof PicassoExecutorService) {
((PicassoExecutorService) service).adjustThreadCount(info);
}
if (info != null && info.isConnected()) {
flushFailedActions();
}
}
如果是PicassoExecutorService,则会调节线程池中线程池个数;并重新处理failed任务集合中任务
3.3.10 飞行模式变化
void performAirplaneModeChange(boolean airplaneMode) {
this.airplaneMode = airplaneMode;
}
3.3.11 暂停任务消息
void performPauseTag(Object tag) {
// Trying to pause a tag that is already paused.
if (!pausedTags.add(tag)) {
return;
}
for (Iterator<BitmapHunter> it = hunterMap.values().iterator(); it.hasNext();) {
BitmapHunter hunter = it.next();
boolean loggingEnabled = hunter.getPicasso().loggingEnabled;
Action single = hunter.getAction();
List<Action> joined = hunter.getActions();
boolean hasMultiple = joined != null && !joined.isEmpty();
if (single == null && !hasMultiple) {
continue;
}
if (single != null && single.getTag().equals(tag)) {
hunter.detach(single);
pausedActions.put(single.getTarget(), single);
if (loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, single.request.logId(),
"because tag '" + tag + "' was paused");
}
}
if (hasMultiple) {
for (int i = joined.size() - 1; i >= 0; i--) {
Action action = joined.get(i);
if (!action.getTag().equals(tag)) {
continue;
}
hunter.detach(action);
pausedActions.put(action.getTarget(), action);
if (loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + tag + "' was paused");
}
}
}
if (hunter.cancel()) {
it.remove();
if (loggingEnabled) {
log(OWNER_DISPATCHER, VERB_CANCELED, getLogIdsForHunter(hunter), "all actions paused");
}
}
}
}
- 标签、任务、动作,放入相应的暂停集合中
- 动作脱离任务,取消任务
3.3.12 恢复任务
void resumeAction(Action action) {
Bitmap bitmap = null;
if (shouldReadFromMemoryCache(action.memoryPolicy)) {
bitmap = quickMemoryCacheCheck(action.getKey());
}
if (bitmap != null) {
deliverAction(bitmap, MEMORY, action, null);
if (loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + MEMORY);
}
} else {
enqueueAndSubmit(action);
if (loggingEnabled) {
log(OWNER_MAIN, VERB_RESUMED, action.request.logId());
}
}
}
- 尝试从内存获取图片,获取成功,则调用Action进行成功处理
- 否则,重新加入队列,并提交动作
3.4 请求动作Action
抽象类;存储了请求的信息;有如下抽象方法
abstract void complete(Bitmap result, Picasso.LoadedFrom from);
abstract void error(Exception e);
抽象方法包含了,对请求结果成功,失败的处理;有如下默认实现类:FetchAction、RemoteViewsAction、GetAction、ImageViewAction、TargetAction;RemoteViewsAction有通知栏和appwidget两种类别;
3.5 RequestHandler类
抽象类,主要有如下方法:
public abstract boolean canHandleRequest(Request data);
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;
}
- 两个抽象方法:是否处理请求,以及请求具体执行
- 重试机制方法控制,优先级如下
- 默认失败重试次数为0
- 飞行模式和网络情况下是否可重试,默认不可以
- 是否在网络发生变化后,可以重试
实现类有:AssetRequestHandler,ContactsPhotoRequestHandler,ContentStreamRequestHandler,MediaStoreRequestHandler,NetworkRequestHandler,ResourceRequestHandler; 也即可以处理Asset资源,通讯录中联系人图片信息,内容提供者中图片、多媒体中图片、http网络、id资源;也可以自实现处理
3.6 BitmapHunter类
线程任务类,run方法,调用hunt方法获取图片,并进行结果处理
public void run() {
try {
updateThreadName(data);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}
result = hunt();
。。。。。。。省略结果处理,利用调度器发送成功失败等消息
}
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.get(key);
if (bitmap != null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
}
return bitmap;
}
}
networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifOrientation = result.getExifOrientation();
bitmap = result.getBitmap();
if (bitmap == null) {
Source source = result.getSource();
try {
bitmap = decodeStream(source, data);
} finally {
try {
source.close();
} catch (IOException ignored) {
}
}
}
}
if (bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap);
if (data.needsTransformation() || exifOrientation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifOrientation != 0) {
bitmap = transformResult(data, bitmap, exifOrientation);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
}
}
if (data.hasCustomTransformations()) {
bitmap = applyCustomTransformations(data.transformations, bitmap);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
}
}
}
if (bitmap != null) {
stats.dispatchBitmapTransformed(bitmap);
}
}
}
return bitmap;
}
- 缓存获取
- RequestHandler的load方法获取图片
- 图片进行变换,进行统计等操作,最后返回图片
类中存在attach、detach方法,也即是多个action可以共用同一个图片请求
3.7 ImageView的延迟处理
这个主要在DeferredRequestCreator类中,其实现了OnPreDrawListener, OnAttachStateChangeListener接口; 通过view的生命周期onViewAttachedToWindow --- onViewDetachedFromWindow 注册/解注OnPreDrawListener监听,在这个监听内,重新进行请求
4、小结
除了介绍中关于Picasso的特性外,还有如下特点,开发过程需要留意
- 同一时间下载图片个数存在限制(wifi 4个, 4G 3个, 3G 2个, 2G 1个,其它为默认3个)
- 同一时间,同一个填充对象,只能有一个请求,以最新请求为准
- 同一时间,同一个图片请求,只能有一个,多个请求Action对象共用请求结果
- 磁盘缓存借助Okhttp请求缓存处理;若自定义下载器,需要对接实现磁盘缓存
- error或者placeholder图片实现Animatable接口,具有动画效果
- 图片不是从内存加载成功后,有可能会有填充的alpha动销,这个属性由noFade决定
- 在onCreate中imageView加载期望大小的图片时,使用fit处理,可以延时在宽高测量成功后,获取并裁剪图片
- 暂停恢复操作,并不是继续处理下载,而是取消下载、重新获取的机制;感觉很鸡肋,也就是能够停止UI的操作部分
- 缩放图片操作,只有CenterCrop和CenterInside两种,缩放后,由于可能存在一条边并不一定和期望大小一致,所以可以进行靠上、下、左、右四个方向或者剧中放置
技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!