介绍
Picasso(毕加索)是时下网上比较流行的开源图片加载库,性能不错,使用也方便,调用时只需要一行代码就可以:
Picasso.with(context).load(imageUrl).into(imageView);
with()
源码如下
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
构造了一个Bulder的单例,点进去Builder的构造方法和build()方法看看
Builder构造方法
public Builder(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
this.context = context.getApplicationContext();
}
可见毕加索为了防止内存泄漏,用了应用的上下文
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);
}
static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
@Override public void handleMessage(Message msg) {
...//处理图片下载结果,一会儿再说
}
};
再就是那个dispatcher,它负责消息的中转,具体使用也一会儿再说。
这样,我们就通过with()方法构造了一个毕加索对象
load()
load()方法代码如下
public RequestCreator load(String path) {
if (path == null) {
return new RequestCreator(this, null, 0);
}
if (path.trim().length() == 0) {
throw new IllegalArgumentException("Path must not be empty.");
}
return load(Uri.parse(path));
}
假设我们的path不为空,就进入了load(Uri uri)方法
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
进入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);
}
而后又是Builder的构造方法
Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
this.uri = uri;
this.resourceId = resourceId;
this.config = bitmapConfig;
}
原来就是一些属性的赋值,那我们接着看into()方法。不过此时,我们已经获取了一个RequestCreator对象,into()方法是他的方法
into()
into()方法是在RequestCreator类中的,这个方法代码很长,我先贴出来
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
checkMain(); // 必须是主线程调用
.. // uri和requestId合法性检查,一般可忽略
.. // RequestCreator.Builder的resize,一般用不到
Request request = createRequest(started);
String requestKey = createKey(request);
.. // 检查缓存映射,找到bitmap放入target中,并返回成功,
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable()); // 设置占位图
}
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}
enqueueAndSubmit()
里面最主要的是最后那几行,实例化一个Action对象(其构造方法也是一堆属性的赋值),并调用picasso.enqueueAndSubmit()方法,我们点进这个enqueueAndSubmit()方法:
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);
}
首先尝试从targetToAction这个映射中获取先前的action,如果能获取,就调用cancelExistingRequest()方法,这个方法用来取消前面的请求
而后把新的action保存到映射中
最后调用submit()方法
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}
调用dispatcher.dispatchSubmit()
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
给handler发送提交消息,这时dispatcher的调度作用就显示出来了
我们先看handler的定义,这是Dispatcher内部的一个handler,定义也是在构造方法中
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
我们看handler怎么处理REQUEST_SUBMIT信息
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
.. //其他case
}
void performSubmit(Action action, boolean dismissFailed) {
.. // 暂停、请求重复、service关闭判断并处理
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
.. // 更新failedActions,打日志
}
主要的就是中间那三行,第一行构造一个BitmapHunter对象,构造方法也是一堆属性的赋值;第二行submit这个hunter,第三行把这个hunter保存到映射中,防止重复请求
我们主要就看第二行那个submit的实现,这个在PicassoExecutorService类中
public Future<?> submit(Runnable task) {
PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
execute(ftask);
return ftask;
}
第一行的PicassoFutureTask类就是一个FutureTask类的子类,只不过多了一个BitmapHunter属性,构造方法也是为这个BitmapHunter赋值
第二行的execute(ftask),就是把这个ftask交给线程池调度,这个不在本文的范围,而线程池调度主要就是调用Runnable的run()方法,而BitmapHunter正是实现了Runnable接口,所以下面,我们将重心,放在BitmapHunter的run()方法中
@Override public void run() {
try {
.. // 更新线程名+打日志
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
}
.. // 异常处理
}
调用hunt()方法获取结果,而后让dispatcher分发结果
hunt()
我们先看hunt()方法
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
.. // 缓存处理
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
.. // 其他字段
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);
}
}
}
.. // 日志、转换、状态更新
return bitmap;
}
主要分三步:1、requestHandler.load()获取结果
2、根据result获取Bitmap。这里就是获取了mBitmap属性
3、如果我们传入的是图片网址,这里获取的位图就是空,需要调用decodeStream()解码输入流
我们这儿只看第一步
requestHandler的load()
这个requestHandler属性由构造方法而来,BitmapHunter的构造方法只在它自己的静态方法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);
}
canHandleRequest()方法是抽象方法,不同的RequestHandler子类有不同的实现,于是我们主要就在毕加索里找,他在哪里给requestHandlers赋值或添加内容,发现是在构造方法中
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...
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);
...
}
最主要的是load()方法,我们点进去
@Override public Result load(Request request, int networkPolicy) throws IOException {
Response response = downloader.load(request.uri, request.networkPolicy);
.. // 判空
Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
Bitmap bitmap = response.getBitmap();
.. // 如果用默认方式获取的话,这里的位图是空
InputStream is = response.getInputStream();
.. // 判空+结果状态更新
return new Result(is, loadedFrom);
}
注意,如果我们传入的是图片网址的话,这里我们只能获取图片的输入流。不过我们要先看看downloader.load()方法是怎么获取Response的
downloader的load()
downloader的默认最终来源是在Picasso.Builder的build()方法,代码在文章开始已经展示过了,这里我们只看downloader的赋值相关内容
public Picasso build() {
Context context = this.context;
if (downloader == null) {
downloader = Utils.createDefaultDownloader(context);
}
.. // 其他组件
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
调用了createDefaultDownloader()
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);
}
可见,先尝试构造okHttpClient做为downloader,如果没这个类,就用UrlConnectionDownloader做为downloader。
假设我们就没导入okHttp依赖,我们用的是UrlConnectionDownloader,它的load()方法代码如下:
@Override public Response load(Uri uri, int networkPolicy) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
installCacheIfNeeded(context);
}
HttpURLConnection connection = openConnection(uri);
connection.setUseCaches(true);
.. // 处理networkPolicy不是0的情况,而默认是0
int responseCode = connection.getResponseCode();
if (responseCode >= 300) {
.. // 断连接,抛异常
}
long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));
return new Response(connection.getInputStream(), fromCache, contentLength);
}
其实就是用了我们常用的URLConnection,代码很简单。我再把调用的openConnection()方法代码贴出来,就更清晰了
protected HttpURLConnection openConnection(Uri path) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(path.toString()).openConnection();
connection.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS);
connection.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS);
return connection;
}
好,返回到NetRequestHandler.load()方法中,这是我们已经获取了response对象,而后获取response中的输入流,赋值到Result对象中,就从NetRequestHandler.load()中返回到BitmapHunter.hunt()方法中,进行输入流解码,顺利的话,就返回到run()方法中了。
handler与dispatcher处理结果
run()方法中,获取的result对象不为空,就调用dispatcher.dispatchComplete()方法
void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
给handler发送完成消息,handler中相应处理如下:
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
调用了dispatcher.performComplete()方法
void performComplete(BitmapHunter hunter) {
.. // hunter写入缓存,同时移除请求
batch(hunter);
.. // 日志
}
调用了batch()方法
private void batch(BitmapHunter hunter) {
.. // 异常处理
batch.add(hunter);
if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
}
}
而handler对next_batch的处理如下:
case HUNTER_DELAY_NEXT_BATCH: {
dispatcher.performBatchComplete();
break;
}
调用了dispatcher.performBatchComplete()
void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}
主要是给主线程发送了batch_complete消息,并把bitmapHunter列表传了过去
主线程HANDLER对这个消息的处理如下:
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;
}
遍历BitmapHunter列表,调用每个的complete()方法
complete()
complete()方法代码如下:
void complete(BitmapHunter hunter) {
Action single = hunter.getAction();
List<Action> joined = hunter.getActions();
boolean hasMultiple = joined != null && !joined.isEmpty();
boolean shouldDeliver = single != null || hasMultiple;
.. // 异常判断+处理
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);
}
}
.. // 异常处理
}
不管是处理单个还是多个,都调用了deliverAction()方法
private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
.. // 异常判断+处理
if (result != null) {
.. // 对from的判空
action.complete(result, from);
.. // 日志
} else {
// .. 异常处理
}
}
主要就是action.complete()方法,这儿我们还是只分析ImageViewAction中的实现
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
.. // 异常判断+处理
ImageView target = this.target.get();
.. // 判空
Context context = picasso.context;
boolean indicatorsEnabled = picasso.indicatorsEnabled;
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
.. // 回调
}
调用了PicassoDrawable.setBitmap()来设置位图,代码如下:
static void setBitmap(ImageView target, Context context, Bitmap bitmap,
Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
.. // 占位图处理
PicassoDrawable drawable =
new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
target.setImageDrawable(drawable);
}
其实就是把bitmap包装成PicassoDrawable,这个PicassoDrawable就是一个BitmapDrawable,只不过多了一些渲染,但默认的话其实都没有
最后就是把drawable设置为imageView了。
结语
到此,Picasso的源码我们就简单分析过了,其中两个handler(一个实现图片的批处理,一个设置UI)实现的图片批处理、线程池的运用、dispatcher的调度作用等等内容,都值得我们细细回味。