Picasso 是 Square 公司开源的一个 Android 平台优秀图片加载框架,易用、代码简洁、可读性高。自己接触的第一个开源图片加载框架也是 Picasso,以前只停留在会用阶段,根本不知道是如何实现的,最近花了点时间看了 Picasso 源码,学到的东西还是蛮多;大概理解实现基本流程。
对于看源码这事,一开始真不知从哪里下手。于是 Google 了,发现很多都是从使用方法入手,理清方法间是如何调用,最后形成自己的线索。本文也是按照这种方式来分析 Picasso 源码。
Picasso 使用方法
- 使用 Picasso 加载一张图片很简单,一行代码就搞定
Picasso.whth(context)
.load(R.mipmap.ic_default)
.placeholder(R.mipmap.ic_default)
.error(R.mipmap.ic_default)
.into(imageView);
- 加载一张图片并且按照指定尺寸以 centerCrop 形式对图片进行缩放
Picasso.with(this)
.load(R.mipmap.ic_default)
.resize(200, 200)
.centerCrop()
.into(imageView);
- 加载一张图片并且按照指定尺寸以 centerInside 形式图片进行缩放(注:对图片进行处理时,centerCrop 或 centerInside 只能选择一种方式,并且必须调用方法 resize(targetWidth, targetHeight) 或者 resizeDimen(targetWidthResId, targetHeightResId) 设置大小)
Picasso.with(this)
.load(R.mipmap.ic_default)
.resizeDimen(R.dimen.width, R.dimen.height)
.centerInside()
.into(imageView);
- 加载一张图片并且按照一定角度对其进行旋转
Picasso.with(this)
.load(R.mipmap.ic_launcher)
.rotate(20)
.into(imageView);
- 加载一张图片自适应目标视图(由于调整大小适应目标视图,结果导致请求被延迟,直到调整完毕才会发送请求;目标视图只能是 ImageView)
Picasso.with(this)
.load(R.mipmap.ic_launcher)
.fit()
.into(imageView);
- 加载一张图片并设置回调接口
Picasso.with(this).load(R.mipmap.ic_launcher).into(mImageView, new Callback() {
@Override
public void onSuccess() {
}
@Override
public void onError() {
}
});
以上只是 Picasso 简单的用法,至于其它用法看API;接下来分析下源码。
Picasso 源码解析
Picasso.with() 方法解析
为了探究 Picasso.with() 方法如何实现,唯独从源代码找答案。代码如下:
/**
* The global default {@link Picasso} instance.
* <p>
* This instance is automatically initialized with defaults that are suitable to most
* implementations.
* <ul>
* <li>LRU memory cache of 15% the available application RAM</li>
* <li>Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only
* available on API 14+ <em>or</em> if you are using a standalone library that provides a disk
* cache on all API levels like OkHttp)</li>
* <li>Three download threads for disk and network access.</li>
* </ul>
* <p>
* If these settings do not meet the requirements of your application you can construct your own
* with full control over the configuration by using {@link Picasso.Builder} to create a
* {@link Picasso} instance. You can either use this directly or by setting it as the global
* instance with {@link #setSingletonInstance}.
*/
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
从注释中可以得到以下几点:
全局默认实例,也就是说只有一个实例存在,从使用单例模式可以看出。
Picasso 采用两级缓存:内存缓存和磁盘缓存。
LRU 内存缓存大小占整个应用可用 RAM 容量的 15%。
磁盘缓存大小占存储空间的 5%,不少于 2 MB,不超过 50 MB。(注:仅适于 API 14+ 或者所使用的第三方库包含 API,比如 OkHttp)。
为磁盘访问和网络访问提供 3 个线程。
这些配置是 Picasso 默认配置的,如果不满足自己的需求,可以自己定制。通过 Picasso.Builder 创建 Picasson 实例,根据自己需要配置相关属性,并调用方法 setSingletonInstance(picasso) 设置为全局实例。
很明显可以看到,使用单例模式来创建 Picasso 实例,保证全局只有一个实例存在。简单说下 with() 方法的实现,singleton 为 null 时调用 Builder 类中 build() 方法创建 singleton 实例并返回,那么接下来就来看 Builder 类中 build() 方法是如何实现的?
Picasso.Builder 中 build() 方法解析
源码如下:
public Picasso build() {
Context context = this.context;
if (downloader == null) {
downloader = Utils.createDefaultDownloader(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);
}
Builder 类是 Picasso 这个类中的静态内部类,而 build() 方法是 Builder 类中的成员方法,可以看出采用建造者模式。那么 build() 方法实现的功能主要有:
创建默认下载器 Downloader
创建默认内存缓存 LruCache (由于接口 Cache 支持多线程访问,所以实现该接口时需确保线程安全)
创建默认线程池 PicassoExecutorService
创建默认请求转发器 RequestTransformer
创建默认统计 Stats
创建默认调度器 Dispatcher
创建 Picasso 实例
那么这些实例是如何创建的,下面主要通过流程图来解析(主要是方法间的调用以及方法内部部分细节)。
- Downloader 是一个接口,无法实例化,需要通过实现类创建实例,该接口的功能主要从磁盘缓存加载图片或网络下载图片。OkHttpDownloader 和 UrlConnectionDownloader 分别实现该接口,那么创建实例也是通过这两个实现类来创建的,那么来看下实例化 Downloader 流程。
- LruCache 是内存缓存类,实现接口 Cache,采用最近最少使用算法。
PicassoExecutorService 继承 ThreadPoolExecutor,是线程池,供图片下载,线程数根据不同网络类型设置,默认的线程数是 3 个,直接通过 new 操作符实例化对象。
RequestTransformer 是一个接口,功能是在发送请求之前对图片进行转换处理。从源码中可以看出,这是一个测试功能,在后续版本可能不兼容,使用该功能时得谨慎。
对于 Stats 实例的创建,直接 new 一个对象,那么主要来看该构造方法做了哪些操作?代码如下:
Stats(Cache cache) {
this.cache = cache;
// statsThread 是子线程,注意记得调用 start() 方法
this.statsThread = new HandlerThread(STATS_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
this.statsThread.start();
Utils.flushStackLocalLeaks(statsThread.getLooper());
// 注意该 handler 是在子线程里的
this.handler = new StatsHandler(statsThread.getLooper(), this);
}
主要是实例化对象,结合注释应该不难理解。
Dispatcher 实例的创建与 Stats 类似,代码如下:
Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
Downloader downloader, Cache cache, Stats stats) {
// dispatcherThread 是子线程,注意记得调用 start() 方法
this.dispatcherThread = new DispatcherThread();
this.dispatcherThread.start();
Utils.flushStackLocalLeaks(dispatcherThread.getLooper());
this.context = context;
this.service = service;
this.hunterMap = new LinkedHashMap<String, BitmapHunter>();
this.failedActions = new WeakHashMap<Object, Action>();
this.pausedActions = new WeakHashMap<Object, Action>();
this.pausedTags = new HashSet<Object>();
// 注意该 handler 是在子线程的
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
this.downloader = downloader;
// 在主线程
this.mainThreadHandler = mainThreadHandler;
this.cache = cache;
this.stats = stats;
this.batch = new ArrayList<BitmapHunter>(4);
this.airplaneMode = Utils.isAirplaneModeOn(this.context);
this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);
this.receiver = new NetworkBroadcastReceiver(this);
receiver.register();
}
最后,也是最重要的一点,那就是 Picasso 实例的创建,先看下代码吧
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
this.context = context;
this.dispatcher = dispatcher;
this.cache = cache;
this.listener = listener;
this.requestTransformer = requestTransformer;
this.defaultBitmapConfig = defaultBitmapConfig;
int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List<RequestHandler> allRequestHandlers =
new ArrayList<RequestHandler>(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(new ResourceRequestHandler(context));
if (extraRequestHandlers != null) {
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(dispatcher.downloader, stats));
requestHandlers = Collections.unmodifiableList(allRequestHandlers);
this.stats = stats;
this.targetToAction = new WeakHashMap<Object, Action>();
this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();
this.indicatorsEnabled = indicatorsEnabled;
this.loggingEnabled = loggingEnabled;
this.referenceQueue = new ReferenceQueue<Object>();
this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
this.cleanupThread.start();
}
除了对一些对象赋值外,重要的一点就是创建和添加 RequestHandler,代码主要在 19 ~ 29 行,比如文件、网络、资源等处理器。至此,Picasso 实例也就创建完毕了,那么接下来看 load() 方法是如何实现的。
Picasso.load() 方法解析
load() 有多个重载方法,可以传入 resourceId、string、file、uri,但是最后都是返回 RequestCreator 实例,接下来就来看该方法如何实现?代码如下:
public RequestCreator load(int resourceId) {
if (resourceId == 0) {
throw new IllegalArgumentException("Resource ID must not be zero.");
}
return new RequestCreator(this, null, resourceId);
}
很明显地看出,调用 load() 方法后返回的是 RequestCreator 实例,那么来看下 RequestCreator 构造方法是咋样的,代码如下:
RequestCreator(Picasso picasso, 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);
}
创建 Request.Builder 对象,并将需要加载图片信息封装到该对象里,采用建造者模式。对于一些操作比如 rotate、centerCrop、centerInside、resize 等,其实只是修改其状态,真正执行操作是方法 into,该方法是核心方法,以下重点分析。
into() 方法解析
into() 有 5 个重载方法,每个方法实现的主要功能基本相同,但是稍微还是有点不一样的。以常用 into(ImageView target) 这个方法为例子来讲解下内部是如何实现的?由于个人比较喜欢画流程图来理清方法之间调用关系,于是就有下面的流程图:
结合流程图再来看源码应该会比较好理解,代码如下:
public void into(ImageView target) {
into(target, null);
}
该方法所持有的 ImageView 实例是一个弱引用,内存不足情况下可以自动被垃圾回收器回收。很明显,该方法调用重载方法 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) {
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);
参数 Callback 是一个强引用,将会阻止 Activity 或者 Fragment 被回收,从而导致内存泄漏。如果使用该方法,在释放资源时最好调用 Picasso 类 cancelRequest(android.widget.ImageView) 方法取消请求,以免造成内存泄漏。从源码可以清晰地看出该方法实现的主要功能:
检查当前线程是否为主线程;如果是主线程,则继续执行;否则抛出异常。
判断目标组件 ImageView 是否为 null;如果不为 null,则继续执行;否则抛出异常。
检查发送请求是否包含要加载图片 uri 或 resourceId;如果没有,则调用Picasso 类 cancelRequest(android.widget.ImageView) 方法取消请求(后面讲解该方法),并检查是否设置默认图片;否则继续执行。
检查是否调用方法 fit(),即 deferred 是否为 true,true 表示调用,false 表示调用 unfit() 方法;调用该方法意味着延迟加载图片,并且不能与方法 resize() 同时使用。
为加载图片创建请求。
为每个请求创建 key,主要是为了方便存储。
根据缓存策略判断是否从内存缓存读取。
是否设置默认图片。
创建对应的 Action 实例,在这里是 ImageViewAction。
入队和提交 action。
以上是总体概括,接下来逐一看具体实现。
那么先来看下 cancelRequest(android.widget.ImageView) 具体实现,代码如下:
public void cancelRequest(ImageView view) {
cancelExistingRequest(view);
}
只有一行代码,调用 cancelExistingRequest(view) 方法,看下具体实现:
private 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();
}
}
}
按照惯例,先列出该方法实现的主要功能:
检查当前线程是否为主线程;如果是主线程,则继续执行;否则抛出异常。
通过 key 从 targetToAction 移除对应 action。
如果 action 不为 null,执行一些取消操作,相当于释放资源。
如果目标组件是 ImageView,则检查是否调用 fit() 方法,是的话就执行一些取消操作。
接着来看下取消操作方法 dispatcher.dispatchCancel(action) 具体实现:
void dispatchCancel(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));
}
很明显是通过 Handler 消息机制来处理的。即 Dispatcher 类中 DispatcherHandler 发送消息 REQUEST_CANCEL,并在其回调方法 handleMessage(final Message msg) 实现具体逻辑,注意是在子线程执行的。
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_CANCEL: {
Action action = (Action) msg.obj;
dispatcher.performCancel(action);
break;
}
default:
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new AssertionError("Unknown handler message received: " + msg.what);
}
});
}
}
}
接着来看下 dispatcher.performCancel(action) 具体实现:
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");
}
}
从以上代码很清晰地看出该方法主要做一些取消、移除操作,即释放资源。以上就是 cancelExistingRequest(view) 内部实现解析。
回到 into(target, null) 内部实现,看下创建请求 createRequest(started) 具体实现:
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;
}
其实实现逻辑很简单,对要加载图片的具体信息封装成 Request;通过 Request 创建对应 key,那么来看下是如何创建的,即 createKey(request) 方法具体实现:
static String createKey(Request data) {
String result = createKey(data, MAIN_THREAD_KEY_BUILDER);
MAIN_THREAD_KEY_BUILDER.setLength(0);
return result;
}
逻辑很简单,调用方法 createKey(data, MAIN_THREAD_KEY_BUILDER),看下具体实现:
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();
}
很简单,根据 Request 设置的属性拼接为字符串,作为最终的 key 并返回。
当我们调用不同 into() 方法时,Picasso 就会实例化不同的 Action,而这里我们是以 into(ImageView) 为例子,因此会实例化 ImageViewAction,在 ImageView 有回调方法,供我们使用,后续会看到。一切都准备就绪,那么就可以入队和提交 action。那么是如何实现的呢?来看下具体实现:
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);
}
通过 key 从 targetToAction 获取 action,不存在的话就执行检查操作并将 action 插入到 targetToAction,最后调用 submit(action),具体实现如下:
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}
最后还是回到 Dispatcher,通过 DispatcherHandler 消息机制发送消息 REQUEST_SUBMIT,并在其回调方法 并在其回调方法 handleMessage(final Message msg) 实现具体逻辑,注意是在子线程执行的。
@Override
public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
default:
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new AssertionError("Unknown handler message received: " + msg.what);
}
});
}
}
}
接着来看下 dispatcher.performSubmit(action) 具体实现:
void performSubmit(Action action) {
performSubmit(action, true);
}
只有一行代码,调用方法 performSubmit(action, true),具体实现如下:
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());
}
}
简要概括下该方法实现的主要功能:
检查标记 tag 请求是否被取消,如果被取消,则将对应 action 插入到 pausedActions 并退出程序;否则继续执行。
通过 key 从 hunterMap 获取相应 BitmapHunter 并判断其是否为 null,如果不为 null,则调用其方法 attach(Action) 并结束退出程序;否则继续执行。简要说明下,BitmapHunter 实现接口 Runnable,意味着开启线程在后台执行任务。
检查线程池是否停止工作。
创建 BitmapHunter 实例,即调用方法 forRequest(action.getPicasso(), this, cache, stats, action)
将 hunter 提交到线程池并执行,即调用 service.submit(hunter)
看下 forRequest() 方法具体实现:
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
Action action) {
Request request = action.getRequest();
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
// Index-based loop to avoid allocating an iterator.
//noinspection ForLoopReplaceableByForEach
for (int i = 0, count = requestHandlers.size(); i < count; i++) {
RequestHandler requestHandler = requestHandlers.get(i);
if (requestHandler.canHandleRequest(request)) {
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}
通过调用 RequestHandler 中 canHandleRequest(Request) 方法逐一检查对应 RequestHandler,并创建 BitmapHunter 对象返回。然后将 hunter 提交到线程池并执行。submit(Runnable) 是接口 ExecutorService 里的方法,PicassoExecutorService 实现接口 ExecutorService,因此 submit(Runnable) 方法的实现逻辑在 PicassoExecutorService 类里面,具体实现如下:
@Override
public Future<?> submit(Runnable task) {
PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
execute(ftask);
return ftask;
}
execute(ftask) 执行任务,由于 BitmapHunter 实现接口 Runnable,意味着开启线程在后台执行任务;而在该方法里会调用线程 start() 方法,意味着 BtimapHunter 中 run() 会被调用,真正执行任务逻辑在该方法里面实现,接下来的重点肯定是来研究该方法内部实现逻辑,在研究源代码之前,先来看该方法内部实现流程图:
同样结合流程图来解析 run() 内部实现,代码如下:
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 (Downloader.ResponseException e) {
if (!e.localCacheOnly || e.responseCode != 504) {
exception = e;
}
dispatcher.dispatchFailed(this);
} catch (NetworkRequestHandler.ContentLengthException e) {
exception = e;
dispatcher.dispatchRetry(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);
}
}
简要概括下该方法实现的主要功能:
更新线程名字。
返回 Bitmap 对象并赋值给 result。
判断返回结果 result 是否为 null,如果为 null,则调用 dispatcher.dispatchFailed(this);否则调用 dispatcher.dispatchComplete(this)
各种异常处理。
从源代码可以看出,核心的逻辑在方法 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;
}
}
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifRotation = result.getExifOrientation();
bitmap = result.getBitmap();
// If there was no Bitmap then we need to decode it from the stream.
if (bitmap == null) {
InputStream is = result.getStream();
try {
bitmap = decodeStream(is, data);
} finally {
Utils.closeQuietly(is);
}
}
}
if (bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap);
if (data.needsTransformation() || exifRotation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifRotation != 0) {
bitmap = transformResult(data, bitmap, exifRotation);
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;
}
代码实现确实很复杂,没事,先概括下其实现的主要小功能,再逐一解析:
根据缓存策略判断是否从内存缓存读取 bitmap,true 表示直接读取并返回 bitmap;否则继续执行。
根据不同请求资源调用相应 RequestHandler 中 load() 方法下载图片,这里以网络请求资源为例子来讲解,即 NetworkRequestHandler。
从下载返回结果 RequestHandler.Result 获取 bitmap,判断是否为 null 做出相应的处理。
重点来 NetworkRequestHandler 类 load() 方法具体实现:
@Override
public Result load(Request request, int networkPolicy) throws IOException {
Response response = downloader.load(request.uri, request.networkPolicy);
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;
}
// Sometimes response content length is zero when requests are being replayed. Haven't found
// root cause to this but retrying the request seems safe to do so.
if (loadedFrom == DISK && response.getContentLength() == 0) {
Utils.closeQuietly(is);
throw new ContentLengthException("Received response with 0 content-length header.");
}
if (loadedFrom == NETWORK && response.getContentLength() > 0) {
stats.dispatchDownloadFinished(response.getContentLength());
}
return new Result(is, loadedFrom);
}
第 3 行代码实现图片下载,即客户端发送网络请求,服务端对客户端的请求作出响应,客户端根据服务端返回的结果作出处理。那么是如何实现下载的呢?downloader 是 Downloader 实例,而 Downloader 是接口,无法实例化,需要在子类实例化,而该接口有两个实现类:OkHttpDownloader 和 UrlConnectionDownloader。至于调用哪个类 load() 方法,取决于当前 sdk 最低版本是否在 API 14及以上。这里以 OkHttpDownloader 类 load() 方法为例来讲解是如何实现下载的,代码如下:
@Override
public Response load(Uri uri, int networkPolicy) throws IOException {
CacheControl cacheControl = null;
if (networkPolicy != 0) {
if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
cacheControl = CacheControl.FORCE_CACHE;
} else {
CacheControl.Builder builder = new CacheControl.Builder();
if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
builder.noCache();
}
if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
builder.noStore();
}
cacheControl = builder.build();
}
}
Request.Builder builder = new Request.Builder().url(uri.toString());
if (cacheControl != null) {
builder.cacheControl(cacheControl);
}
com.squareup.okhttp.Response response = client.newCall(builder.build()).execute();
int responseCode = response.code();
if (responseCode >= 300) {
response.body().close();
throw new ResponseException(responseCode + " " + response.message(), networkPolicy,
responseCode);
}
boolean fromCache = response.cacheResponse() != null;
ResponseBody responseBody = response.body();
return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
}
第 3 - 17 行代码主要是设置缓存策略;第 24 行通过调用 OkHttp 库 API 实现图片下载任务,并返回响应结果 com.squareup.okhttp.Response;最后对响应结果作出相应处理并创建 Response 对象返回。
再回到 NetworkRequestHandler 类 load() 方法,从返回 Response 对象调用 getBitmap() 方法获取 Bitmap 对象并判断其是否为 null,如果不为 null,则将 bitmap 和 loadedfrom 传入 Result 构造器方法中,创建 Result 对象并返回;否则调用 getInputStream() 方法获取输入流,不为 null 的话则创建 Result 对象并返回。
回到 hunt() 方法,从返回结果 result 获取数据类型为 Bitmap 对象 bitmap,并判断其是否为 null 作出不同的处理。如果 bitmap 不为 null,则判断原先发送加载图片请求 Request 是否需要对图片进行转换处理,即裁剪、缩放、重置大小等;不需要的话直接返回 bitmap;需要的话做转换处理后再返回 bitmap;如果 bitmap 为 null,则从 result 获取输入流 InputStream,并对 is 进行解析转换成 Bitmap 类型,将获取到的结果返回。
hunt() 方法解析完了,回到 run() 方法。根据调用方法 hunt() 返回结果 result,类型为 Bitmap,判断其是否为 null 并作出相应的处理。那么就来看下不为 null 的情况下又做了什么操作,即第 14 行代码,具体实现如下:
void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
很明显,又采用 Handler 消息机制处理,即 Dispatcher 类中 DispatcherHandler 发送消息 HUNTER_COMPLETE,其回调方法 handleMessage(final Message msg) 收到消息后并处理。说明一下,DispatcherHandler 所在的线程为子线程,即 DispatcherThread。
@Override
public void handleMessage(final Message msg) {
switch (msg.what) {
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
default:
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new AssertionError("Unknown handler message received: " + msg.what);
}
});
}
}
}
在其回调方法中,核心代码是第 6 行,具体实现如下:
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");
}
}
根据发送请求设置的内存缓存策略判断是否将其结果写入到内存中,并通过 key 从 hunterMap 移除 hunter,最后再对 hunter 作出处理,即 第 6 行代码,具体实现如下:
private void batch(BitmapHunter hunter) {
if (hunter.isCancelled()) {
return;
}
batch.add(hunter);
if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
}
}
看第 7 行代码,同时采用 Handler 消息机制发送消息,跟上面一样。最终会转到 Dispatcher 类 performBatchComplete() 方法,具体实现如下:
void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}
核心代码是第 4 行,同样才 Handler 消息机制发送消息,但是此时与之上有所不一样,即 mainThreadHandler 所在线程是主线程,也就是说,此时将任务从子线程切换到主线程,以便可以进行 UI 更新操作,那么赶快来看下在主线程是如何实现的?代码如下:
@Override
public void handleMessage(final Message msg) {
switch (msg.what) {
case HUNTER_BATCH_COMPLETE:
@SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = batch.size(); i < n; i++) {
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);
}
break;
default:
throw new AssertionError("Unknown handler message received: " + msg.what);
}
}
根据获取到 BitmapHunter 列表大小依次调用 Picasso 中 complete(BitmapHunter) 方法,那么又是怎样实现的呢,代码如下:
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 = hunter.getData().uri;
Exception exception = hunter.getException();
Bitmap result = hunter.getResult();
LoadedFrom from = hunter.getLoadedFrom();
if (single != null) {
deliverAction(result, from, single);
}
if (hasMultiple) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = joined.size(); i < n; i++) {
Action join = joined.get(i);
deliverAction(result, from, join);
}
}
if (listener != null && exception != null) {
listener.onImageLoadFailed(this, uri, exception);
}
}
从 hunter 获取单个 action、合并 actions 以及其它信息,比如 uri、exception 等。如果获取到的 action 不为空,则派发 action,即第 18 行代码,具体实现如下:
private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
if (action.isCancelled()) {
return;
}
if (!action.willReplay()) {
targetToAction.remove(action.getTarget());
}
if (result != null) {
if (from == null) {
throw new AssertionError("LoadedFrom cannot be null.");
}
action.complete(result, from);
if (loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
}
} else {
action.error();
if (loggingEnabled) {
log(OWNER_MAIN, VERB_ERRORED, action.request.logId());
}
}
}
从以上代码可以清晰地看出,根据 result 是否为空分别调用 Action 的回调方法,即 complete() 与 error()。先来看下 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();
}
}
逻辑很简单,前部分主要是做一些检查操作,真正核心代码是第 15 行代码,即将下载获取到的图片渲染到 UI 上,意味着成功地加载一张图片。那么 error() 方法又做了什么操作呢?具体实现如下:
public void error() {
ImageView target = this.target.get();
if (target == null) {
return;
}
if (errorResId != 0) {
target.setImageResource(errorResId);
} else if (errorDrawable != null) {
target.setImageDrawable(errorDrawable);
}
if (callback != null) {
callback.onError();
}
}
逻辑也很简单,如果我们有设置错误时显示图片的话,该方法就将错误时显示图片渲染出来。
那么这是单个 action 的处理逻辑,如果有合并 actions 的,执行的逻辑也一样,即对其进行遍历,获取单个 action,再派发 action,实现的代码在 complete(BitmapHunter) 方法第 21 - 27 行。
好吧, 使用 Picasso 开源框架成功加载一张图片的具体流程大概就这样了。由于自己能力水平有限,在讲解过程中难免有错误,如果您看到了,欢迎指正出来,大家一起学习,共同进步 !!!
参考资料
http://skykai521.github.io/2016/02/25/Picasso%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/