Picasso源码学习(一)

picasso_cover.png

从这里开始我会开始阅读一款来自于Square的大名鼎鼎的图片处理库Picasso,将这个过程以博客的形式记录下来,一方面是一种学习记录,另一方面也是一种督促啦。

准备

一切的第一步当然是首先把源码从github上clone下来。

1
git clone https://github.com/square/picasso.git

稍等片刻,Picasso的源码就被clone到了自己的电脑上。

当然,现在我们这里的就是Picasso的作者所commit的最新的代码,但是所要看的并不是这个内容,这里需要选择一个release版本。

picasso0.png

之所以选的是最早的1.0.0版本,是因为作为最早的版本,后面的所有版本都是基于这个版本进行编写的,阅读最早的版本可以更好地了解整个项目的构架。另外,作为最早的版本,这个版本肯定也是所有版本里最容易阅读的。我们接着将项目checkout到该版本。

1
git checkout dea2e24

接下来就是酥爽的源码阅读之旅了。

源码阅读

大致了解

根据Picasso的使用方法,首先我们会使用 Picasso.with(context),因此我们就从这里开始看看在我们使用Picasso的时候它都做了些什么。

1
2
3
4
5
6
7
public static Picasso with(Context context) {
if (singleton == null) {
//构造Picasso对象
singleton = new Builder(context).build();
}
return singleton;
}

这是一个Picasso中的静态方法,很明显是构造了一个Picasso的单例,并且在代码构架中使用了Build模式建立了这个对象。这里的关键是Builder对象的构造过程里都做了什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Picasso build() {
Context context = this.context;
//创建用于加载图片的对象loader
if (loader == null) {
loader = Utils.createDefaultLoader(context);
}
//创建用于缓存的对象memoryCache
if (memoryCache == null) {
memoryCache = new LruCache(context);
}
//创建用于请求的服务对象service
if (service == null) {
service = Executors.newFixedThreadPool(3, new Utils.PicassoThreadFactory());
}
//缓存状态对象stats
Stats stats = new Stats(memoryCache);
return new Picasso(context, loader, service, memoryCache, stats);
}

从几个属性的名字里可以看出来,Picasso对象中有几个很重要的属性loadermemoryservicestats。其他先放一边,首先从这个loader对象入手,来分析整个加载流程。

加载流程

创建完了Picasso对象之后一般我们一定会调用的代码会是这样的

1
2
3
Picasso.with(context)
.load(url)
.into(imageview);

我们可以推测到代码中的后两行做的事情就是将获取资源,然后将其加载进ImageView中。

1
2
3
4
public RequestBuilder load(String path) {
//···省略代码···
return new RequestBuilder(this, path, Type.STREAM);
}

load()方法实际上所做的也就是创建一个RequestBuilder对象,并返回这个对象,into()方法也是这个类里的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
RequestBuilder(Picasso picasso, String path, Request.Type type) {
this.picasso = picasso;
this.path = path;
this.resourceId = 0;
this.type = type;
}
public void into(ImageView target) {
//创建图片缓存索引key
String requestKey = createKey(path, resourceId, options, transformations);
//根据key从缓存中查询Bitmap对象
Bitmap bitmap = picasso.quickMemoryCacheCheck(target, requestKey);
if (bitmap != null) {
//如果存在已经存在于缓存中的Bitmap对象,则加载进ImageView中
PicassoDrawable.setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.debugging);
return;
}
//设置占位图
if (placeholderResId != 0 || placeholderDrawable != null) {
PicassoDrawable.setPlaceholder(target, picasso.context, placeholderResId, placeholderDrawable,
picasso.debugging);
}
//创建图片资源请求对象传入Picasso对象的submit方法中
Request request =
new Request(picasso, path, resourceId, target, options, transformations, type, skipCache,
noFade, errorResId, errorDrawable);
picasso.submit(request);
}

这里贴出了RequestBuilder的构造方法和into()方法,如果缓存中有图片资源,则直接取出加载,如果没有则新建一个Request(实际上是一个Runnable)对象,并通过调用submit()方法来加载图片。接下来我么看看submit()方法。

1
2
3
4
5
6
7
8
9
10
void submit(Request request) {
Object target = request.getTarget();
if (target == null) return;
cancelExistingRequest(target, request.path);
//将ImageView对象和Request对象以键值对的形式存入一个Map对象中
targetsToRequests.put(target, request);
//调用Executor对象的submit()方法继续加载过程
request.future = service.submit(request);
}

这里我们发现又调用了一个实例servicesubmit()方法,这个service实际上是一个Executor对象,这个对象也就是我们上面创建Builder的过程中构造的。我们知道Executorjava.util.concurrent类库中的一个类,这里开启了一个线程池用来处理图片请求的线程,由此我们知道这里实际上调用的就是Requestrun()方法。

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void run() {
try {
//···省略代码···
picasso.run(this);
} catch (final Throwable e) {
//···省略代码···
} finally {
//···省略代码···
}
}

点进去之后,我们发现这里又接着调用了Picassorun()方法。

1
2
3
4
5
6
7
8
9
void run(Request request) {
try {
Bitmap result = resolveRequest(request);
request.result = result;
handler.sendMessage(handler.obtainMessage(REQUEST_COMPLETE, request));
} catch (IOException e) {
}
}

到了这里我们终于发现实际上请求Bitmap资源的方法是resolveRequest()方法,并且在成功获取到了资源之后发送了一个请求成功的Message,请求成功后的部分稍后再看,这里先看看resolveRequest()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Bitmap resolveRequest(Request request) throws IOException {
//检查缓存中是否存有Bitmap资源对象
Bitmap bitmap = loadFromCache(request);
if (bitmap == null) {
stats.cacheMiss();
//如果没有则继续请求
bitmap = loadFromType(request);
//将请求到的资源存入缓存中
if (bitmap != null && !request.skipCache) {
cache.set(request.key, bitmap);
}
} else {
stats.cacheHit();
}
return bitmap;
}

接着Bitmap方法还要经过loadFromType()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private Bitmap loadFromType(Request request) throws IOException {
PicassoBitmapOptions options = request.options;
Bitmap result = null;
//根据资源类型的不同采取不同的请求方法
switch (request.type) {
case CONTENT:
Uri path = Uri.parse(request.path);
result = decodeContentStream(path, options);
break;
case RESOURCE:
Resources resources = context.getResources();
result = decodeResource(resources, request.resourceId, options);
break;
case FILE:
result = decodeFile(request.path, options);
break;
case STREAM:
Response response = null;
try {
response = loader.load(request.path, request.retryCount == 0);
result = decodeStream(response.stream, options);
} finally {
response.stream.close();
}
break;
default:
throw new AssertionError("Unknown request type: " + request.type);
}
return result;
}

这个方法的内容稍多,我也删去了部分,这里实际上是根据请求的资源类型的不同分别采取不同的方法去请求Bitmap资源,其中前三种不复杂,重头戏是最后一种——网络请求资源,也可以说是使用最为频繁的请求姿势。而这里的请求过程用到了我们在最开始所提到的Loader并调用了其中的loader()方法。Loader实际上是一个接口,loader的实际类型是由Builder创建的loader = Utils.createDefaultLoader(context);

1
2
3
4
5
6
7
8
static Loader createDefaultLoader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException e) {
return new UrlConnectionLoader(context);
}
}

从名字上可以看出这里使用了另一个Square大名鼎鼎的开源库OkHttp来处理网络请求。而这里的create()方法则创建了一个OkHttpLoader对象,我们来看看它的loader()方法。

1
2
3
4
5
6
7
8
9
10
11
@Override public Response load(String url, boolean localCacheOnly) throws IOException {
HttpURLConnection connection = client.open(new URL(url));
connection.setUseCaches(true);
if (localCacheOnly) {
connection.setRequestProperty("Cache-Control", "only-if-cached");
}
boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));
return new Response(connection.getInputStream(), fromCache);
}

到这里总算是完成了全部的资源请求流程。这里回到上面提到过的请求成功之后会发送一个请求成功的消息。然后我们看看对于这个消息是在哪里接受并且如何处理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
final Handler handler = new Handler(Looper.getMainLooper()) {
@Override public void handleMessage(Message msg) {
Request request = (Request) msg.obj;
if (request.future.isCancelled() || request.retryCancelled) {
return;
}
Picasso picasso = request.picasso;
switch (msg.what) {
case REQUEST_COMPLETE:
picasso.targetsToRequests.remove(request.getTarget());
request.complete();
break;
case REQUEST_RETRY:
picasso.retry(request);
break;
case REQUEST_DECODE_FAILED:
picasso.targetsToRequests.remove(request.getTarget());
request.error();
break;
default:
throw new AssertionError("Unknown handler message received: " + msg.what);
}
}
};

原来在Picasso类中,一开始就创建了一个Handler来处理各种请求状态,这个Handler被创建在主线程。在这里,对于请求成功的情况,调用了Request类的complete()方法。

1
2
3
4
5
6
7
8
void complete() {
ImageView target = this.target.get();
if (target != null) {
Context context = picasso.context;
boolean debugging = picasso.debugging;
PicassoDrawable.setBitmap(target, context, result, loadedFrom, noFade, debugging);
}
}

这里涉及到了一个工具类PicassoDrawable,调用其中的setBitmap方法来加载图片。

1
2
3
4
5
6
7
8
9
static void setBitmap(ImageView target, Context context, Bitmap bitmap, LoadedFrom loadedFrom,
boolean noFade, boolean debugging) {
PicassoDrawable picassoDrawable = extractPicassoDrawable(target);
if (picassoDrawable != null) {
picassoDrawable.setBitmap(bitmap, loadedFrom, noFade);
} else {
target.setImageDrawable(new PicassoDrawable(context, bitmap, loadedFrom, noFade, debugging));
}
}

最后

到这里算是彻底地完成了整个请求加载到流程。后面我还会去分析这个库的其他部分,所以说。。。。。。我就先去歇一会啦!!!