Picasso 源码分析
Picasso 源码分析
这是一个新的系列,我们一起来阅读一下一些优秀开源库的源码。先从简单到复杂,首先先来看看 Picasso 的源码。
Picasso 是什么?
按照官网给的定义,Picasso 是
A powerful image downloading and caching library for Android
一个 Android 系统中的强大的图片下载和缓存库。
以下是两种常规用法,基本上可以看出是干嘛的了:
PicassoProvider.get().load(R.drawable.landing_screen).into(imageView1);
PicassoProvider.get()
.load(url)
.resize(50, 50)
.centerCrop()
.into(imageView)
简单地说就是一款图片加载框架,可以帮助我们快速的加载图片,还有缓存图片。这里我们主要分析它是如何实现图片加载和缓存的,以及如何设计架构的。
架构
Picasso 的架构,我个人理解可以分为三层
- Picasso:应用层,这一层主要实现是 Picasso 这一个类,这个类代表了一个图片资源下载缓存控制器,我们用户使用也是从这个类开始。这一层主要有两个重要方法:
load(url)
和into()
前一个是加载图片,第二个是将图片放到目标位置。 - Disapatcher:任务分发层,这一层主要实现是 Dispatcher 这个类,首先这个类接受应用层发来的 Action,然后再维护一个 Hunter 队列,Hunter 是资源获取器,主要是从网络获取。这里要注意,多个 Action 可能会放到同一个 Hunter 中,而一个 Hunter 会在一个线程中完成,这里采用了一种批处理的思维,等到 Action 达到一定数量或者缓冲时间到达后才会真正去获取资源。然后内部使用各种机制来实现资源获取,错误处理等。
- Data:数据层,这一层就比较抽象了,其实主要是三层数据,首先是 内存中的 Lru 缓存,其次是 磁盘中的图片缓存(这里会和 Okhttp 的缓存绑定,与 Http 协议同步),最后是网络请求,当前两个缓存都无法找到合适的数据,则会发起网络请求来请求资源,同时将数据缓存。
以下是架构图:
事实上,实际使用流程并不会按照箭头走,例如再 into 的时候如果 Lru 缓存中有该图片,则会直接返回而不是进入 Dipsatcher
Picasso
先来看看 Picasso,对于这个 Picasso 对象,该开源库已经默认帮我们实现了一个默认实例,当然你可以可以通过 Builder 自己创建。我们可以通过 PicassoProvider.get() 来获取默认实例,来看看:
// PicassoProvider.kt
object PicassoProvider {
private val instance: Picasso by lazy {
Picasso
.Builder(PicassoContentProvider.autoContext!!)
.addEventListener(StatsEventListener())
.build()
}
@JvmStatic
fun get() = instance
}
首先该类本身是 Object 的单例模式,然后通过懒加载创建了一个实例,注意这里的 PicassoContentProvider.autoContext
。
// PicassoContentProvider
class PicassoContentProvider : ContentProvider() {
override fun onCreate(): Boolean {
autoContext = context
return true
}
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
) = null
override fun getType(uri: Uri) = null
override fun insert(
uri: Uri,
values: ContentValues?
) = null
override fun delete(
uri: Uri,
selection: String?,
selectionArgs: Array<String>?
) = 0
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
) = 0
companion object {
@SuppressLint("StaticFieldLeak")
@JvmField
var autoContext: Context? = null
}
}
可以看到这个 PicassoContentProvider 继承了 ContentProvider,然后再 onCreate 方法里将 autoContext 赋值为 context。其实这是利用了四大组件中 ContentProvider 的机制。当 Application 启动的时候系统会去创建注册表中的 ContentProvider,而就会调用 onCreate 来更新 context 。
这样可以让我们不用在外界调用 init 的相关方法,可以直接在 Application 启动的时候拿到 context,不得不说妙啊。
这里 Picasso 使用了创建者模式,通过一个 Builder 来注入数据,先来看看这个构造方法:
//Picasso.Builder#Builder
public Builder(@NonNull Context context) {
checkNotNull(context, "context == null");
this.context = context.getApplicationContext();
}
这里获取的是 ApplicationContext,为了防止内存泄漏。
然后来看看 Build():
//Picasso.Builder#Build
/** Create the {@link Picasso} instance. */
@NonNull
public Picasso build() {
Context context = this.context;
// 初始化 okhttp 的磁盘缓存器
okhttp3.Cache unsharedCache = null;
if (callFactory == null) {
File cacheDir = createDefaultCacheDir(context);
long maxSize = calculateDiskCacheSize(cacheDir);
unsharedCache = new okhttp3.Cache(cacheDir, maxSize);
callFactory = new OkHttpClient.Builder()
.cache(unsharedCache)
.build();
}
// 初始化 Lru 内存缓存器
if (cache == null) {
cache = new PlatformLruCache(Utils.calculateMemoryCacheSize(context));
}
// 初始化线程池
if (service == null) {
service = new PicassoExecutorService(new PicassoThreadFactory());
}
// 初始化 Dispatcher
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, cache);
return new Picasso(context, dispatcher, callFactory, unsharedCache, cache, listener,
requestTransformers, requestHandlers, eventListeners, defaultBitmapConfig,
indicatorsEnabled, loggingEnabled);
}
首先是初始化 Okhttp 的磁盘缓存器,这里可以看看默认路径:
//Utils#createDefaultCacheDir
private static final String PICASSO_CACHE = "picasso-cache";
static File createDefaultCacheDir(Context context) {
File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);
if (!cache.exists()) {
//noinspection ResultOfMethodCallIgnored
cache.mkdirs();
}
return cache;
}
可以看到默认的缓存路径是 context.getCahceDir 路径加上 picasso-cache 文件夹,也就是 /data/data/<application package>/cache/picasso-cache
文件夹,该文件夹在 data 分区,一般的文件管理是看不见的。
然后是初始化 Lru 内存缓存器,这里主要是实例化了一个 PlatformLruCache 对象,这个对象之后会讲到,同时我们可以看看以下方法:
//Utils#calculateMemoryCacheSize
static int calculateMemoryCacheSize(Context context) {
ActivityManager am = ContextCompat.getSystemService(context, ActivityManager.class);
boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
int memoryClass = largeHeap ? am.getLargeMemoryClass() : am.getMemoryClass();
// Target ~15% of the available heap.
return (int) (1024L * 1024L * memoryClass / 7);
}
该方法表面 Lru 缓存的大小为可用的最大堆内存除以 7,大概为 15%。
然后是初始化线程池,这里是新建了一个 PicassoExecutorService,这其实继承于 ThreadPoolExecutor
,然后加上了任务优先级的问题。
同时,我们可以看看以下代码:
static class PicassoThreadFactory implements ThreadFactory {
@Override public Thread newThread(@NonNull Runnable r) {
return new PicassoThread(r);
}
}
private static class PicassoThread extends Thread {
PicassoThread(Runnable r) {
super(r);
}
@Override public void run() {
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
super.run();
}
}
这里的 PicassoThreadFactory 每次都会新建一个 PicassoThread 对象,而该对象只是表面该线程为 后台线程。
然后是新建 Dispatcher,这里的第三个参数 HANDLER 是一个 handler 对象,其 Looper 为 MainLooper,然后该变量是一个静态变量,之后也会涉及到。
最后就是构造 Picasso 对象并返回了。
首先来看看 Picasso 的构造方法:
// Picasso#Picasso
Picasso(Context context, Dispatcher dispatcher, Call.Factory callFactory,
@Nullable okhttp3.Cache closeableCache, PlatformLruCache cache, @Nullable Listener listener,
List<RequestTransformer> requestTransformers, List<RequestHandler> extraRequestHandlers,
List<? extends EventListener> eventListeners, @Nullable Bitmap.Config defaultBitmapConfig,
boolean indicatorsEnabled, boolean loggingEnabled) {
this.context = context;
this.dispatcher = dispatcher;
this.callFactory = callFactory;
this.closeableCache = closeableCache;
this.cache = cache;
this.listener = listener;
this.requestTransformers = Collections.unmodifiableList(new ArrayList<>(requestTransformers));
this.defaultBitmapConfig = defaultBitmapConfig;
// Adjust this and Builder(Picasso) as internal handlers are added or removed.
int builtInHandlers = 8;
int extraCount = extraRequestHandlers.size();
List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
// ResourceRequestHandler needs to be the first in the list to avoid
// forcing other RequestHandlers to perform null checks on request.uri
// to cover the (request.resourceId != 0) case.
allRequestHandlers.add(ResourceDrawableRequestHandler.create(context));
allRequestHandlers.add(new ResourceRequestHandler(context));
allRequestHandlers.addAll(extraRequestHandlers);
allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
allRequestHandlers.add(new MediaStoreRequestHandler(context));
allRequestHandlers.add(new ContentStreamRequestHandler(context));
allRequestHandlers.add(new AssetRequestHandler(context));
allRequestHandlers.add(new FileRequestHandler(context));
allRequestHandlers.add(new NetworkRequestHandler(callFactory));
requestHandlers = Collections.unmodifiableList(allRequestHandlers);
this.eventListeners = Collections.unmodifiableList(eventListeners);
this.targetToAction = new LinkedHashMap<>();
this.targetToDeferredRequestCreator = new LinkedHashMap<>();
this.indicatorsEnabled = indicatorsEnabled;
this.loggingEnabled = loggingEnabled;
}
可以看到 Picasso 最终会持有其他层的各种对象,包括 dispatcher 等。同时还提供了 BitmapConfig 和 requestTransformer ,前者可以自定义 Bitmap 的各种配置,后者则可以当成一个 请求重定向的功能,这两个都可以自定义。
最后还要注意 requestHandlers,这是一个 List,其实这里用了 责任链模式 来设计。
final List<RequestHandler> requestHandlers;
可以看到这里是一个 RequestHandler
的 List 。这个 RequestHandler
就是请求处理器,是一个抽象类,来看看:
// RequestHandler.kt
/**
* `RequestHandler` allows you to extend Picasso to load images in ways that are not
* supported by default in the library.
*
* <h2>Usage</h2>
* `RequestHandler` must be subclassed to be used. You will have to override two methods
* ([canHandleRequest] and [load]) with your custom logic to load images.
*
* You should then register your [RequestHandler] using
* [Picasso.Builder.addRequestHandler]
*
* **Note:** This is a beta feature. The API is subject to change in a backwards incompatible
* way at any time.
*
* @see Picasso.Builder.addRequestHandler
*/
abstract class RequestHandler {
/**
* [Result] represents the result of a [load] call in a [RequestHandler].
*
* @see RequestHandler
* @see [load]
*/
sealed class Result constructor(
/**
* Returns the resulting [Picasso.LoadedFrom] generated from a [load] call.
*/
@JvmField val loadedFrom: LoadedFrom,
/**
* Returns the resulting EXIF rotation generated from a [load] call.
*/
@JvmField val exifRotation: Int = 0
) {
class Bitmap @JvmOverloads constructor(
val bitmap: android.graphics.Bitmap,
loadedFrom: LoadedFrom,
exifRotation: Int = 0
) : Result(loadedFrom, exifRotation)
class Drawable @JvmOverloads constructor(
val drawable: android.graphics.drawable.Drawable,
loadedFrom: LoadedFrom,
exifRotation: Int = 0
) : Result(loadedFrom, exifRotation)
}
interface Callback {
fun onSuccess(result: Result?)
fun onError(t: Throwable)
}
/**
* Whether or not this [RequestHandler] can handle a request with the given [Request].
*/
abstract fun canHandleRequest(data: Request): Boolean
/**
* Loads an image for the given [Request].
* @param request the data from which the image should be resolved.
*/
@Throws(IOException::class)
abstract fun load(
picasso: Picasso,
request: Request,
callback: Callback
)
open val retryCount = 0
open fun shouldRetry(
airplaneMode: Boolean,
info: NetworkInfo?
) = false
open fun supportsReplay() = false
}
这里我们拆开看,首先是一个 密封类 Result
,它有两个实现,Bitmap
和 Drawable
,分别对应两种图片格式,这里统一抽象为 加载结果,Result。
这里 LoadedFrom 表面该资源从哪里加载,是一个枚举类,有三种来源:内存、外存 和 网络。
// Picasso.LoadedFrom
public enum LoadedFrom {
MEMORY(Color.GREEN),
DISK(Color.BLUE),
NETWORK(Color.RED);
final int debugColor;
LoadedFrom(int debugColor) {
this.debugColor = debugColor;
}
}
然后是一个接口 CallBack
,这个比较简单,直接看即可:
interface Callback {
fun onSuccess(result: Result?)
fun onError(t: Throwable)
}
然后是 RequestHandler
的抽象方法:
/**
* Whether or not this [RequestHandler] can handle a request with the given [Request].
*/
abstract fun canHandleRequest(data: Request): Boolean
/**
* Loads an image for the given [Request].
* @param request the data from which the image should be resolved.
*/
@Throws(IOException::class)
abstract fun load(
picasso: Picasso,
request: Request,
callback: Callback
)
open val retryCount = 0
open fun shouldRetry(
airplaneMode: Boolean,
info: NetworkInfo?
) = false
open fun supportsReplay() = false
- canHandleRequest :该 Handler 是否可以处理这个 Request
- load:加载图片
- retruCount:重试次数
- shouldRetry:是否需要重试,其中第一个参数为是否为飞行模式,第二个参数为当前网络状态
- supportsReplay:是否支持重新播放
可以看到在 Picasso 的构造方法里 向 requestHandlers
里加入了许多 RequestHandler
List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
// ResourceRequestHandler needs to be the first in the list to avoid
// forcing other RequestHandlers to perform null checks on request.uri
// to cover the (request.resourceId != 0) case.
allRequestHandlers.add(ResourceDrawableRequestHandler.create(context));
allRequestHandlers.add(new ResourceRequestHandler(context));
allRequestHandlers.addAll(extraRequestHandlers);
allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
allRequestHandlers.add(new MediaStoreRequestHandler(context));
allRequestHandlers.add(new ContentStreamRequestHandler(context));
allRequestHandlers.add(new AssetRequestHandler(context));
allRequestHandlers.add(new FileRequestHandler(context));
allRequestHandlers.add(new NetworkRequestHandler(callFactory));
requestHandlers = Collections.unmodifiableList(allRequestHandlers);
在 责任链
中,有消息处理,就有责任,而这里的责任是 Request
对象,表示对一个图片资源请求的抽象,这个类虽然是个实体类,但内容比较多。反正是对一个资源请求的抽象,其中有网络图片的 URL,也有本地图片的文件路径,还有 MediaStore 提供的图片,甚至还有 Assets 文件夹等各种来源。而当我们生成一个 Request 后,会被放入责任链中,责任链总从第一个开始,不断判断能处理该请求的处理器。
for (i in requestHandlers.indices) {
val requestHandler = requestHandlers[i]
if (requestHandler.canHandleRequest(request)) {
return BitmapHunter(picasso, dispatcher, cache, requestHandler, action)
}
}
从以上代码中就能看到,当找到一个可以处理该请求的消息处理之后就直接返回,这里 Hunter 对象是图片加载的一个抽象。
ImageView 图片加载流程
Picasso 的用法如下图,这里我们根据这个流程来一步一步看:
PicassoProvider.get()
.load(url)
.into(imageView)
首先,PicassoProvider.get()
拿到一个 Picasso 对象,然后我们调用 load 方法:
这个方法有三个重载,不过都是参数合法然后间接调用该方法:
/**
* Start an image request using the specified URI.
* <p>
* Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder,
* if one is specified.
*
* @see #load(File)
* @see #load(String)
* @see #load(int)
*/
@NonNull
public RequestCreator load(@Nullable Uri uri) {
return new RequestCreator(this, uri, 0);
}
可以看到实例化了一个 RequestCreator 对象,我们看看:
这个对象较大,我们只讲其中部分
private final Picasso picasso;
private final Request.Builder data;
private boolean noFade;
private boolean deferred;
private boolean setPlaceholder = true;
private int placeholderResId;
@DrawableRes private int errorResId;
private @Nullable Drawable placeholderDrawable;
private @Nullable Drawable errorDrawable;
RequestCreator(Picasso picasso, @Nullable Uri uri, int resourceId) {
if (picasso.shutdown) {
throw new IllegalStateException(
"Picasso instance already shut down. Cannot submit new requests.");
}
this.picasso = picasso;
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
可以看到这里持有了 Picasso 对象,同时还有一个 Requst.Builder 对象,同时还持有占位图片和加载错误图片的资源。重点在 into 方法:
/**
* Asynchronously fulfills the request into the specified {@link ImageView}.
* <p>
* <em>Note:</em> This method will automatically support object recycling.
*/
public void into(@NonNull ImageView target) {
into(target, null);
}
/**
* Asynchronously fulfills the request into the specified {@link ImageView} and invokes the
* target {@link Callback} if it's not {@code null}.
* <p>
* <em>Note:</em> The {@link Callback} param is a strong reference and will prevent your
* {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected. If
* you use this method, it is <b>strongly</b> recommended you invoke an adjacent
* {@link Picasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking.
*/
public void into(@NonNull ImageView target, @Nullable 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) {
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}
Request request = createRequest(started);
if (shouldReadFromMemoryCache(request.memoryPolicy)) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(request.key);
if (bitmap != null) {
picasso.cancelRequest(target);
Result result = new Result.Bitmap(bitmap, MEMORY);
setResult(target, picasso.context, result, 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, errorDrawable, errorResId, noFade,
callback);
picasso.enqueueAndSubmit(action);
}
最开始是参数合法检查,主要看这里:
Request request = createRequest(started);
首先创建要给 Requst 对象,这里看看 createRequest 方法:
/** Create the request optionally passing it through the request transformer. */
private Request createRequest(long started) {
int id = nextId.getAndIncrement();
Request request = data.build();
request.id = id;
request.started = started;
boolean loggingEnabled = picasso.loggingEnabled;
if (loggingEnabled) {
log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());
}
Request transformed = picasso.transformRequest(request);
if (transformed != request) {
// If the request was changed, copy over the id and timestamp from the original.
transformed.id = id;
transformed.started = started;
if (loggingEnabled) {
log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed);
}
}
return transformed;
}
首先是 id ,这个是自增原子计数器,然后加上开始时间。
回到 into 方法:
if (shouldReadFromMemoryCache(request.memoryPolicy)) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(request.key);
if (bitmap != null) {
picasso.cancelRequest(target);
Result result = new Result.Bitmap(bitmap, MEMORY);
setResult(target, picasso.context, result, noFade, picasso.indicatorsEnabled);
if (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}
这里是 LruChache 的判断,如果缓存中有,则直接从缓存中获取,要注意到这里缓存的唯一标识是 request.key,我们来看看:
@JvmField var key: String =
if (Looper.myLooper() == Looper.getMainLooper()) {
createKey()
} else {
createKey(StringBuilder())
}
可以看到这里 key 是在 Request 实例化的时候自动生成的,主要调用了这两个方法:
private fun createKey(builder: StringBuilder): String {
val data = this
if (data.stableKey != null) {
builder.ensureCapacity(data.stableKey.length + KEY_PADDING)
builder.append(data.stableKey)
} else if (data.uri != null) {
val 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 != 0f) {
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(data.centerCropGravity)
.append(KEY_SEPARATOR)
} else if (data.centerInside) {
builder
.append("centerInside")
.append(KEY_SEPARATOR)
}
for (i in data.transformations.indices) {
builder.append(data.transformations[i].key())
builder.append(KEY_SEPARATOR)
}
return builder.toString()
}
可以看到这里 key 还包括了参数信息,如果两张图片是同一张,但是其中一张旋转了,但是在 缓存中也会被视为两张图片。
回到 into:
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
Action action = new ImageViewAction(picasso, target, request, errorDrawable, errorResId, noFade,callback);
picasso.enqueueAndSubmit(action);
首先设置了占位符,然后实例化一个 action 对象,最终会来到 picasso.enqueueAndSubmit(Action)
方法,来看看这个方法:
void enqueueAndSubmit(Action action) {
Object target = action.getTarget();
if (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);
}
可以看到这里先做了一个 target 的判断,Picasso 里对 target 与 Action 做了缓存,一个 target 只能同时进行一个 Action,防止浪费资源,最终 Action 会来到 dispatcher.dispatchSubmit 方法,继续来到 dispatcher
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
而在 dispatcher 的构造方法中可以看到:
this.handler = new DispatcherHandler(dispatcherThreadLooper, this);
这个 handler 是一个 DispatcherHandler
对象,所以最终会来到这个 DispatcherHandler :
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
case REQUEST_CANCEL: {
Action action = (Action) msg.obj;
dispatcher.performCancel(action);
break;
}
case TAG_PAUSE: {
Object tag = msg.obj;
dispatcher.performPauseTag(tag);
break;
}
case TAG_RESUME: {
Object tag = msg.obj;
dispatcher.performResumeTag(tag);
break;
}
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
case HUNTER_RETRY: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performRetry(hunter);
break;
}
case HUNTER_DECODE_FAILED: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performError(hunter);
break;
}
case NETWORK_STATE_CHANGE: {
NetworkInfo info = (NetworkInfo) msg.obj;
dispatcher.performNetworkStateChange(info);
break;
}
case AIRPLANE_MODE_CHANGE: {
dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);
break;
}
default:
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new AssertionError("Unknown handler message received: " + msg.what);
}
});
}
}
我们发送的消息是 REQUEST_SUBMIT
,这里只看这部分:
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
可以看到还是来到 dispatcher.performSubmit 方法。
void performSubmit(Action action) {
performSubmit(action, true);
}
void performSubmit(Action action, boolean dismissFailed) {
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
if (action.picasso.loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
}
return;
}
BitmapHunter hunter = hunterMap.get(action.request.key);
if (hunter != null) {
hunter.attach(action);
return;
}
if (service.isShutdown()) {
if (action.picasso.loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}
hunter = forRequest(action.picasso, this, cache, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.request.key, hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}
if (action.picasso.loggingEnabled) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}
}
可以看到 最终是来到 performSubmit 方法,接下来我们一段一段看:
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
if (action.picasso.loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
}
return;
}
可以看到这里是暂停处理,如果该 Tag 已经被暂停了,就直接返回。
BitmapHunter hunter = hunterMap.get(action.request.key);
if (hunter != null) {
hunter.attach(action);
return;
}
这里是看有没有和该 action 一样 key 的 action 在当前队列中的某个 hunter 里,如果有就直接加入 hunter 队列,来看看 attach:
//Hunter#attach
fun attach(action: Action) {
val loggingEnabled = picasso.loggingEnabled
val request = action.request
if (this.action == null) {
this.action = action
if (loggingEnabled) {
if (_actions.isNullOrEmpty()) {
log(OWNER_HUNTER, VERB_JOINED, request.logId(), "to empty hunter")
} else {
log(OWNER_HUNTER, VERB_JOINED, request.logId(), getLogIdsForHunter(this, "to "))
}
}
return
}
if (_actions == null) {
_actions = ArrayList(3)
}
_actions?.add(action)
if (loggingEnabled) {
log(OWNER_HUNTER, VERB_JOINED, request.logId(), getLogIdsForHunter(this, "to "))
}
val actionPriority = action.request.priority
if (actionPriority.ordinal > priority.ordinal) {
priority = actionPriority
}
}
可以看到做了一些合法化处理,然后将 action 加入 _ations ,这是一个 Action 的 List 。同时 更新一下优先级,在此也可以看出 Hunter 的优先级取决于这个 Hunter 中 Action 队列中的最高优先级。
继续看回 performSubmit
if (service.isShutdown()) {
if (action.picasso.loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}
hunter = forRequest(action.picasso, this, cache, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.request.key, hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}
if (action.picasso.loggingEnabled) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}
来看看 forRequest,这是 BitmapHunter 中的一个静态构造方法:
//BitmapHunter#forRequest
@JvmStatic fun forRequest(
picasso: Picasso,
dispatcher: Dispatcher,
cache: PlatformLruCache,
action: Action
): BitmapHunter {
val request = action.request
val requestHandlers = picasso.getRequestHandlers()
// Index-based loop to avoid allocating an iterator.
for (i in requestHandlers.indices) {
val requestHandler = requestHandlers[i]
if (requestHandler.canHandleRequest(request)) {
return BitmapHunter(picasso, dispatcher, cache, requestHandler, action)
}
}
return BitmapHunter(picasso, dispatcher, cache, ERRORING_HANDLER, action)
}
这里可以说是责任链模式的主要实现位置,迭代一下找到能处理该 request 的 handler,然后直接将 handler 对象放入 Hunter 中。来直接看看这个 BitmapHunter:
这个类比较长,主要是持有了 Action,优先级,Picasso ,Dispatcher, RequestHandler 对象等,继承于 Runnable,先来看看 run 方法:
//BitmapHunter#run
override fun run() {
try {
updateThreadName(data)
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this))
}
result = hunt()
dispatcher.dispatchComplete(this)
} catch (e: IOException) {
exception = e
if (retryCount > 0) {
dispatcher.dispatchRetry(this)
} else {
dispatcher.dispatchFailed(this)
}
} catch (e: Exception) {
exception = e
dispatcher.dispatchFailed(this)
} finally {
Thread.currentThread().name = THREAD_IDLE_NAME
}
}
先来看一下整个 try 结构,在这里有失败和成功两种情况,而失败会根据 retruCount 情况决定要不要重试,这三种情况分别通过:
dispatcher.dispatchComplete(this)
dispatcher.dispatchRetry(this)
dispatcher.dispatchFailed(this)
来分发。
在 try 中,可以看到最主要的是 调用了 hunt() 这个方法,可以来看看:
// BitmapHunter#hunt()
@Throws(IOException::class)
fun hunt(): Bitmap? {
if (shouldReadFromMemoryCache(data.memoryPolicy)) {
cache[key]?.let { bitmap ->
picasso.cacheHit()
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache")
}
return Bitmap(bitmap, LoadedFrom.MEMORY)
}
}
if (retryCount == 0) {
data = data.newBuilder().networkPolicy(NetworkPolicy.OFFLINE).build()
}
val resultReference = AtomicReference<RequestHandler.Result?>()
val exceptionReference = AtomicReference<Throwable?>()
val latch = CountDownLatch(1)
try {
requestHandler.load(picasso, data, object : RequestHandler.Callback {
override fun onSuccess(result: RequestHandler.Result?) {
resultReference.set(result)
latch.countDown()
}
override fun onError(t: Throwable) {
exceptionReference.set(t)
latch.countDown()
}
})
latch.await()
} catch (ie: InterruptedException) {
val interruptedIoException = InterruptedIOException()
interruptedIoException.initCause(ie)
throw interruptedIoException
}
exceptionReference.get()?.let { throwable ->
when (throwable) {
is IOException, is Error, is RuntimeException -> throw throwable
else -> throw RuntimeException(throwable)
}
}
val result = resultReference.get() as? Bitmap
?: throw AssertionError("Request handler neither returned a result nor an exception.")
val bitmap = result.bitmap
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId())
}
picasso.bitmapDecoded(bitmap)
val transformations = ArrayList<Transformation>(data.transformations.size + 1)
if (data.needsMatrixTransform() || result.exifRotation != 0) {
transformations += MatrixTransformation(data)
}
transformations += data.transformations
val transformedResult =
applyTransformations(picasso, data, transformations, result) ?: return null
val transformedBitmap = transformedResult.bitmap
picasso.bitmapTransformed(transformedBitmap)
return transformedResult
}
比较长,但是也很好理解,首先是从 LruCache 中查找是否有该图片,有的话直接返回。如果没有的话将调用 requestHandler 的 load 方法。同时这里使用了一个 Latch 来等待 requestHandler 加载,同时还使用了两个原子暂存器来接受结果和异常。
然后将该图片做一些处理,首先是 picasso.bitmapDecoded(bitmap),该方法会将该 Bitmap 丢进 EventListener ,也就是事件监听器。我们如果自己创建自己的 Picasso 对象的时候可以加入自己的事件监听器,这里就是调用 bitmapDecoded 的事件。
然后是对图片的一些处理如拉伸,缩放等操作。这个是储存在 action 中。其中 applyTransformations 就是将变换列表作用到 bitmap 对象中。
最后还调用了一下 picasso.bitmapTransformed,该方法也是回调事件监听器的相关方法。
最后将 bitmap 返回。
看到这里,当 Hunter 放入线程池中后,会调用其 run 方法,会执行 result = hunt() 语句,最终将加载的图片赋值给给 result 变量。
回到 Dispatcher.performSubmit 方法:
hunter = forRequest(action.picasso, this, cache, action);
hunter.future = service.submit(hunter);
可以看到在构造完 hunter 之后,我们就将该 hunter 放入 service 中,这里的 service 是 ExecutorService
对象,而 submit 方法会返回一个 Future 对象,该对象可以判断当前该线程是否执行完毕。
至此我们在构造出 Hunter 对象后,会直接将其提交到线程池服务器中,然后保存其 future 对象,该对象主要是为了判断该 Hunter 的状态。
回到刚刚 Hunter 的 run 方法中,当请求完成之后,我们会根据结果调用三个方法,这里我们先看看 dispatcher.dispatchComplete(this),该方法是在请求成功的时候回调的:
// Dispatcher#dispatchComplete
void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
可以看到最终还是来到 handler,这里我直接看到 HUNTER_COMPLETE 事件的处理:
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
可以看到最终来到 performComplete 方法:
// Dispatcher#performComplete
void performComplete(BitmapHunter hunter) {
if (shouldWriteToMemoryCache(hunter.data.memoryPolicy)) {
Result result = hunter.getResult();
if (result != null) {
if (result instanceof Result.Bitmap) {
Bitmap bitmap = ((Result.Bitmap) result).getBitmap();
cache.set(hunter.getKey(), bitmap);
}
}
}
hunterMap.remove(hunter.getKey());
deliver(hunter);
}
这里主要是对结果合法化的判断,同时将结果加入缓存,从队列移除 Hunter,最后来到 deliver 方法:
private void deliver(BitmapHunter hunter) {
if (hunter.isCancelled()) {
return;
}
Result result = hunter.getResult();
if (result != null) {
if (result instanceof Result.Bitmap) {
Bitmap bitmap = ((Result.Bitmap) result).getBitmap();
bitmap.prepareToDraw();
}
}
Message message = mainThreadHandler.obtainMessage(HUNTER_COMPLETE, hunter);
if (hunter.priority == Picasso.Priority.HIGH) {
mainThreadHandler.sendMessageAtFrontOfQueue(message);
} else {
mainThreadHandler.sendMessage(message);
}
logDelivery(hunter);
}
这里会拿到 Bitmap 对象并调用 prepareToDraw 方法准备展示,最后会向 mainThreadHandler 发送 HUNTER_COMPLETE 的事件回到主线程。这里如果是高优先级会直接在消息队列队首加入,如果优先级不是最高则会在队尾加入。
来到 mainThreadHandelr,该变量是在 Dispatcher 构造方法中传入的,在 Picasso 中是一个静态变量的内部匿名类:
static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
hunter.picasso.complete(hunter);
break;
}
case REQUEST_BATCH_RESUME:
@SuppressWarnings("unchecked") List<Action> batch = (List<Action>) msg.obj;
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = batch.size(); i < n; i++) {
Action action = batch.get(i);
action.picasso.resumeAction(action);
}
break;
default:
throw new AssertionError("Unknown handler message received: " + msg.what);
}
}
};
可以看到最终会来到 picasso.complete(hunter);
void complete(BitmapHunter hunter) {
Action single = hunter.getAction();
List<Action> joined = hunter.getActions();
boolean hasMultiple = joined != null && !joined.isEmpty();
boolean shouldDeliver = single != null || hasMultiple;
if (!shouldDeliver) {
return;
}
Uri uri = checkNotNull(hunter.data.uri, "uri == null");
Exception exception = hunter.getException();
Result result = hunter.getResult();
if (single != null) {
deliverAction(result, single, exception);
}
if (joined != null) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = joined.size(); i < n; i++) {
Action join = joined.get(i);
deliverAction(result, join, exception);
}
}
if (listener != null && exception != null) {
listener.onImageLoadFailed(this, uri, exception);
}
}
可以看到这里对所有 action 都调用了一遍 deliverAction 方法。其中 single 是该 Hunter 最初的一个 action,joined 是 Hunter 中后来加入的 所有 aciton,这里应该是前期只做了单 action,后来扩展的 action 队列。
来到 deliverAction:
// Picasso#deliverAction
private void deliverAction(@Nullable Result result, Action action,
@Nullable Exception e) {
if (action.cancelled) {
return;
}
if (!action.willReplay) {
targetToAction.remove(action.getTarget());
}
if (result != null) {
action.complete(result);
if (loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + result.loadedFrom);
}
} else {
Exception exception = checkNotNull(e, "e == null");
action.error(exception);
if (loggingEnabled) {
log(OWNER_MAIN, VERB_ERRORED, action.request.logId(), exception.getMessage());
}
}
}
除去一堆参数合法,最终调用的是 action.complete(result) 这是一个抽象方法。回到我们最开始创建 action 的地方,其实现类是 ImageViewAction
// ImageViewAction#complete
override fun complete(result: Result) {
PicassoDrawable.setResult(target, picasso.context, result, noFade, picasso.indicatorsEnabled)
callback?.onSuccess()
}
// PicassoDrawable#setResult
static void setResult(ImageView target, Context context, Result result, boolean noFade,
boolean debugging) {
Drawable placeholder = target.getDrawable();
if (placeholder instanceof Animatable) {
((Animatable) placeholder).stop();
}
if (result instanceof Result.Bitmap) {
Bitmap bitmap = ((Result.Bitmap) result).getBitmap();
Picasso.LoadedFrom loadedFrom = result.loadedFrom;
PicassoDrawable drawable =
new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
target.setImageDrawable(drawable);
} else {
Drawable drawable = ((Result.Drawable) result).getDrawable();
target.setImageDrawable(drawable);
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
}
}
}
至此,图片加载完成。
其它 Target 的图片加载过程
可以看到,RequestCreater 的 into 方法有许多重载,这里其实过程和上面大同小异,主要是其 Action 对象不同,因此我们介绍一些 Action 对象的派生类:
BitmapTargetAction
对应 into(BitmapTarget)
先来看看这个 BitmapTarget,这是一个接口,因为这个接口的存在,我们可以在自己的任何类中使用 Picasso 来加载图片:
/**
* Represents an arbitrary listener for image loading.
*
* Objects implementing this class **must** have a working implementation of
* [Object.equals] and [Object.hashCode] for proper storage internally.
* Instances of this interface will also be compared to determine if view recycling is occurring.
* It is recommended that you add this interface directly on to a custom view type when using in an
* adapter to ensure correct recycling behavior.
*/
interface BitmapTarget {
/**
* Callback when an image has been successfully loaded.
*
* **Note:** You must not recycle the bitmap.
*/
fun onBitmapLoaded(
bitmap: Bitmap,
from: LoadedFrom
)
/**
* Callback indicating the image could not be successfully loaded.
*
* **Note:** The passed [Drawable] may be `null` if none has been
* specified via [RequestCreator.error] or [RequestCreator.error].
*/
fun onBitmapFailed(
e: Exception,
errorDrawable: Drawable?
)
/**
* Callback invoked right before your request is submitted.
*
*
* **Note:** The passed [Drawable] may be `null` if none has been
* specified via [RequestCreator.placeholder] or [RequestCreator.placeholder].
*/
fun onPrepareLoad(placeHolderDrawable: Drawable?)
}
还是比较好理解的,这里直接来看看 BitmapTargetAction
internal class BitmapTargetAction(
picasso: Picasso,
val target: BitmapTarget,
data: Request,
val errorDrawable: Drawable?,
@DrawableRes val errorResId: Int
) : Action(picasso, data) {
override fun complete(result: Result) {
if (result is Bitmap) {
val bitmap = result.bitmap
target.onBitmapLoaded(bitmap, result.loadedFrom)
check(!bitmap.isRecycled) { "Target callback must not recycle bitmap!" }
}
}
override fun error(e: Exception) {
val drawable = if (errorResId != 0) {
ContextCompat.getDrawable(picasso.context, errorResId)
} else {
errorDrawable
}
target.onBitmapFailed(e, drawable)
}
override fun getTarget(): Any {
return target
}
}
GetAction
对应 get 方法,该方法直接会返回 bitmap 对象。
internal class GetAction(
picasso: Picasso,
data: Request
) : Action(picasso, data) {
override fun complete(result: Result) = Unit
override fun error(e: Exception) = Unit
override fun getTarget() = throw AssertionError()
}
可以看到该 Action 方法为空实现,然后来看看 get 方法:
// RequestCreator#get
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 request = createRequest(started);
Action action = new GetAction(picasso, request);
Result.Bitmap result =
forRequest(picasso, picasso.dispatcher, picasso.cache, action).hunt();
if (result == null) {
return null;
}
Bitmap bitmap = result.getBitmap();
if (shouldWriteToMemoryCache(request.memoryPolicy)) {
picasso.cache.set(request.key, bitmap);
}
return bitmap;
}
可以看到这里没有使用 Dispatcher 对象来分发任务,直接同步构造 Hunter 对象并调用 hunt 方法。
FetchAction
对应 fetch 方法,该方法会使用 Dispatcher 对象来分发任务,但是图片不会加载到任何实体,但是你可以在 callback 中调用。可用于提前将图片加入缓存:
internal class FetchAction(
picasso: Picasso,
data: Request,
private var callback: Callback?
) : Action(picasso, data) {
override fun complete(result: Result) {
callback?.onSuccess()
}
override fun error(e: Exception) {
callback?.onError(e)
}
override fun getTarget() = this
override fun cancel() {
super.cancel()
callback = null
}
}
可以看到这里没有做任何操作,直接将结果回调给 callback,当然在这个过程中,还是会将图片加入缓存。
RemoteViewsAction
这个是用于 RemoteView 的,是一个抽象类,Picasso 里默认有两个派生类分别是:
- AppWidgetAction:用于桌面小部件
- NotifactionAction:用于通知上的 RemoteView
这里感兴趣可以去看源码,这里不做分析了
总结
Picasso 可以说是很优秀的开源框架了,其架构设计中有许多我们值得学习的地方,包括 责任链模式,Hunter 与 Action 分离的思想。希望之后也能写出这样舒服的架构。