Picasso是Square公司出品的一款非常优秀的图片加载库,它可以帮我们完成一些android中处理的图片,使用最小的内存来完成图片的过渡。
使用的方法如下:
Picasso.with(context).load(“image url”).into(imageView);
源码剖析
我们就根据图片显示的这一条流程下来,一步步探究。
先看上面一行代码
public static Picasso with(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("context == null");
}
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
在这里使用用了懒汉式创建了个实例,接着用new Builder(Context).build
初始化了Picasso,主要作用为提供自定义线程池、缓存、下载器等方法。
有了这个实例后,会接下来调用load函数
其中Picasso重载了几个不同的方法,来适应不同场合下加载图片。
public RequestCreator load(@Nullable Uri uri) {
.....
}
public RequestCreator load(@Nullable String path) {
.....
}
public RequestCreator load(@NonNull File file) {
.....
}
可以看到通过load会返回一个RequestCreator对象
,这个类是用来配置加载参数的,包括了placeHolder于error图片,加载图片的大小/旋转/居中等属性。
最后会调用into函数,将加载到的图片赋给一个ImageView控件。大部分实际工作都是在into里完成的
public void into(ImageView target) {
into(target, null);
}
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
if (!data.hasImage()) {
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
if (deferred) {
if (data.hasSize()) {
throw new IllegalStateException("Fit cannot be used with resize.");
}
int width = target.getWidth();
int height = target.getHeight();
if (width == 0 || height == 0 || target.isLayoutRequested()) {
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}
Request request = createRequest(started);
String requestKey = createKey(request);
if (shouldReadFromMemoryCache(memoryPolicy)) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
picasso.cancelRequest(target);
setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
if (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}
- 会检查是否在主线程上运行
- 如果没有一个图片资源的话并且有设置placeHolder,那么就会把我们设置的placeholder显示出来,并中断执行
- 接下来就是创建了一个Request对象,我们在前面做得一些设置都会被封装到这个Request对象里面。
- 之后由
stableKey
、uri
、rotationDegrees
、resize
、centerCrop
、``centerInside、
transformations`组成key - 检查我们要显示的图片是否可以直接在缓存中获取,如果有就直接显示出来好了
- 如果都没有最后通过一个Action,采用异步下载显示图片
为了保证图片不会错位,Picasso维护了Map
,每个ImageView均只对应一个Action。
若获取的图片Action与ImageView不符合,则丢弃,等待正确的Action执行完。
了解完Picasso加载的图片的过程后,我们要深入到Picasso里面,查看图片加载的具体实现方式:
之前了解到初始化Picasso,调用了new Builder(context).build()
方法
public Picasso build() {
Context context = this.context;
if (downloader == null) {
downloader = new OkHttp3Downloader(context);
}
if (cache == null) {
cache = new LruCache(context);
}
if (service == null) {
service = new PicassoExecutorService();
}
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
Stats stats = new Stats(cache);
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
}
- 重点看Dispatcher,在这里起到了一个调度器的作用,图片要不要开始下载及下载后Bitmap的返回都是通过这个调度器来执行的。
void enqueueAndSubmit(Action action) {
Object target = action.getTarget();
if (target != null && targetToAction.get(target) != action) {
// This will also check we are on the main thread.
cancelExistingRequest(target);
targetToAction.put(target, action);
}
submit(action);
}
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}
通过上面的分析我们知道,RequestCreator在into方法的最后会创建一个Action实例,然后调用Picasso的enqueueAndSubmit方法,而最终是调用了Dispatcher的dispatchSubmit方法,也就是我们前面说的,Dispatcher起到了调度器的作用。在Dispatcher内部,Dispatcher定义了DispatcherThread和DispatcherHandler两个内部类,并在Dispatcher的构造函数中对他们经行了实例化,所有的调度也都是通过handler异步的执行的。
Picasso维护了Map
,每个ImageView均只对应一个Action。
看一下Dispatcher内部实现
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
通过Handler最终调用了一个performSubmit()函数
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());
}
}
在这里获得了一个BitmapHunter ,这其实是个Runnable的一个实现,实例最终交给另外线程池来处理。
那最后图片是怎么呈现出来的呢?进到BitmapHunter ,看run()实现的方法。
@Override public void run() {
try {
updateThreadName(data);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
} catch (NetworkRequestHandler.ResponseException e) {
if (!NetworkPolicy.isOfflineOnly(e.networkPolicy) || e.code != 504) {
exception = e;
}
dispatcher.dispatchFailed(this);
} catch (IOException e) {
exception = e;
dispatcher.dispatchRetry(this);
} catch (OutOfMemoryError e) {
StringWriter writer = new StringWriter();
stats.createSnapshot().dump(new PrintWriter(writer));
exception = new RuntimeException(writer.toString(), e);
dispatcher.dispatchFailed(this);
} catch (Exception e) {
exception = e;
dispatcher.dispatchFailed(this);
} finally {
Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
}
}
可以看到执行了dispatcher.perComplete
方法,这个方法会自动处理缓存图片问题.
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");
}
}
把图片暂时放到了cache里,等空闲的时候再去处理来
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);
}
}
将操作放到了一个list数组(batch)里,做了一个延迟操作处理,发送了一个HUNTER_DELAY_NEXT_BATCH
,又回到了handler处理.由于一直在非主线程上操作,最后显示图片还是要回到主线程上来。这是接收到消息后的处理
void performBatchComplete() {
List copy = new ArrayList<>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}
这个mainThreadHandler是在Dispatcher实例化时由外部传递进来的,我们在前面的分析中看到,Picasso在通过Builder创建时会对Dispatcher进行实例化,在那个地方将主线程的handler传了进来,我们回到Picasso这个类,看到其有一个静态成员变量HANDLER,这样我们也就清楚了。
static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case HUNTER_BATCH_COMPLETE: {
@SuppressWarnings("unchecked") List batch = (List) msg.obj;
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = batch.size(); i < n; i++) {
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);
}
break;
}
}
};
通过Hnadler回到了Picasso里,最终调用picasso.complete
方法.
void complete(BitmapHunter hunter) {
Action single = hunter.getAction();
List joined = hunter.getActions();
boolean hasMultiple = joined != null && !joined.isEmpty();
boolean shouldDeliver = single != null || hasMultiple;
if (!shouldDeliver) {
return;
}
Uri uri = hunter.getData().uri;
Exception exception = hunter.getException();
Bitmap result = hunter.getResult();
LoadedFrom from = hunter.getLoadedFrom();
if (single != null) {
deliverAction(result, from, single, exception);
}
if (hasMultiple) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = joined.size(); i < n; i++) {
Action join = joined.get(i);
deliverAction(result, from, join, exception);
}
}
if (listener != null && exception != null) {
listener.onImageLoadFailed(this, uri, exception);
}
}
Picasso使用了ImageViewAction来进行处理,也就是在ImageViewAction中的complete方法完成了最后的图片渲染工作。
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
if (result == null) {
throw new AssertionError(
String.format("Attempted to complete action with no result!\n%s", this));
}
ImageView target = this.target.get();
if (target == null) {
return;
}
Context context = picasso.context;
boolean indicatorsEnabled = picasso.indicatorsEnabled;
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
if (callback != null) {
callback.onSuccess();
}
}
到这里就把这个图片的渲染过程讲完了
参考:
- Picasso源码解析
- Android图片加载库Picasso源码分析