简谈源码-Picasso

对使用Picasso进行一次简单的图片展示进行其代码调用过程的追踪查看

作者:catface,发布时间:2015-01-01 Thursday

Picasso官网 Picasso

1
Picasso.get().load(url).into(iv);

Picasso的常见使用步骤很简单,下面我们概要的看看其实现。

1. 实例获取get()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Picasso.class */
@SuppressLint("StaticFieldLeak") static volatile Picasso singleton = null;

public static Picasso get() {
    if (singleton == null) {
        synchronized (Picasso.class) {
            if (singleton == null) {
                if (PicassoProvider.context == null) {
                    throw new IllegalStateException("context == null");
                }
                singleton = new Builder(PicassoProvider.context).build();
            }
        }
    }
    return singleton;
}

使用double checked locking单例模式创建Picasso实例,创建过程中使用建造者Builder。

1.1 context获取

通过ContentProvider,在AndroidManifest 中注册PicassoProvider,app启动后调用onCreate()即可获取所需context实例。

1
2
3
4
5
6
7
8
9
public final class PicassoProvider extends ContentProvider {

    @SuppressLint("StaticFieldLeak") static Context context;

    @Override public boolean onCreate() {
        context = getContext();
        return true;
    }
}

1.2 Picasso创建

使用Builder Pattern建造者模式,将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示,简单点说就是用多个简单的对象一步一步构建成一个复杂的对象。

1
2
3
4
5
6
7
/* Picasso.class */
public Builder(@NonNull Context context) {
    if (context == null) {
        throw new IllegalArgumentException("Context must not be null.");
    }
    this.context = context.getApplicationContext();
}

具体建造过程如下。

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
34
35
/* Picasso.class */
public Picasso build() {
    Context context = this.context;

    // 配置下载器,下载网络图片资源(默认使用OkHttp3Downloader)
    if (downloader == null) {
        downloader = new OkHttp3Downloader(context);
    }

    // 配置内存缓存,提高图片加载效率(默认使用LruCache[Least Recently Used])
    // 内存缓存大小为RAM的15%;磁盘缓存大小为ROM的2%且范围为5~50mb
    if (cache == null) {
        cache = new LruCache(context);
    }

    // 配置线程池
    // 默认3个线程;使用wifi为4个,4G为3个,3G为2个,2G为1个
    if (service == null) {
        service = new PicassoExecutorService();
    }

    // 配置请求转换器(默认不做处理,直接返回原请求)
    if (transformer == null) {
        transformer = RequestTransformer.IDENTITY;
    }

    // 统计:统计缓存命中数量、图片下载数量等
    Stats stats = new Stats(cache);

    // 分发器:由HANDLER分发图片请求、请求完成等等事件
    Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

    return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
            defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}

至此,Picasso.get()过程完成。

2. 获取请求load(url)

即通过请求创建器RequestCreator创建请求Request,通过Request可以执行placeholder(placeholderResId)方法设置占位图,error(errorResId)方法设置错误图,noFade()方法取消图片渐变效果等操作。

1
2
3
4
5
6
7
8
9
/* Picasso.class */
public RequestCreator load(@Nullable String path) {
    ...
    return load(Uri.parse(path));
}

public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
}

RequestCreator的构造方法如下,核心是通过建造者模式建造Request实例。

通过picasso可以发起请求、推迟(defer)请求、取消请求、检查缓存配置等操作;通过创建的Request实例data可以调用centerCrop()、centerInside()等方法。

1
2
3
4
5
6
7
8
9
10
11
boolean shutdown;
private final Picasso picasso;
private final Request.Builder data;

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);
}

3. 显示into(iv)

核心逻辑为创建Action,即一次图片请求的活动过程,然后分发该action。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/* RequestCreator.class */
/**
 * 异步请求图片资源至用户指定的ImageView
 * 使用弱引用关联ImageView,资源将会被自动回收
 */
public void into(ImageView target) {
    into(target, null);
}

/**
 * 若用户指定Callback,为了防止Activity/Fragment回收,会是一个强引用;推荐及时调用cancelRequest()方法防止内存泄露
 */
public void into(ImageView target, Callback callback) {
    // 记录开始时间,并检查线程是否安全
    long started = System.nanoTime();
    checkMain();

    if (target == null) {
        throw new IllegalArgumentException("Target must not be null.");
    }

    // 若Request实例为空,取消当前请求,设置占位图
    if (!data.hasImage()) {
        picasso.cancelRequest(target);
        if (setPlaceholder) {
            setPlaceholder(target, getPlaceholderDrawable());
        }
        return;
    }

    // deferred默认是false的
    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());
            }
            // 若获取不到目标ImageView的宽高,就延迟请求,直到获取宽高时再次调用into()方法
            picasso.defer(target, new DeferredRequestCreator(this, target, callback));
            return;
        }
        data.resize(width, height);
    }

    // 创建Request实例并标记
    Request request = createRequest(started);
    String requestKey = createKey(request);

    // 控制从缓存中读取,若命中requestKey对应的Bitmap则取消当前请求,并显示图片
    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实例,并提交至picasso
    Action action =
            new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
                    errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
}

3.1 action的创建

上述代码中用到的关键活动类ImageViewAction就是继承自Action,并重写了Action中的方法complete()、error()、cancel(),具体实现和方法注释说明如下。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class ImageViewAction extends Action<ImageView> {

    Callback callback;

    ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
                    int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
                    Callback callback, boolean noFade) {
        super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,
                tag, noFade);
        this.callback = callback;
    }

    // 图片请求成功,使用PicassoDrawable将图片显示到指定的ImageView控件中
    @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();
        }
    }

    // 图片请求失败,判断是否显示占位图
    @Override public void error(Exception e) {
        ImageView target = this.target.get();
        if (target == null) {
            return;
        }
        Drawable placeholder = target.getDrawable();
        if (placeholder instanceof Animatable) {
            ((Animatable) placeholder).stop();
        }
        if (errorResId != 0) {
            target.setImageResource(errorResId);
        } else if (errorDrawable != null) {
            target.setImageDrawable(errorDrawable);
        }

        if (callback != null) {
            callback.onError(e);
        }
    }

    // 取消本次请求
    @Override void cancel() {
        super.cancel();
        if (callback != null) {
            callback = null;
        }
    }
}

3.2 action的提交

picasso提交action,然后通过分发器dispatcher分发消息。

1
2
3
4
5
6
7
8
9
/* Picasso.class */
void enqueueAndSubmit(Action action) {
    ...
    submit(action);
}

void submit(Action action) {
    dispatcher.dispatchSubmit(action);
}

3.3 action的分发

dispatcher通过Handler发送action,交由图片捕获器BitmapHunter处理。

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
/* Dispatcher.class */
void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

@Override public void handleMessage(final Message msg) {
    switch (msg.what) {
        case REQUEST_SUBMIT: {
            Action action = (Action) msg.obj;
            dispatcher.performSubmit(action);
            break;
        ...
        }
    }
}

void performSubmit(Action action) {
    performSubmit(action, true);
}

void performSubmit(Action action, boolean dismissFailed) {
    ...
    // 剩下的工作将由BitmapHunter完成
    BitmapHunter hunter = BitmapHunter.forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);
    ...
}

3.3.1 BitmapHunter.forRequest()获取图片捕获器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* BitmapHunter.class */
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
        RequestHandler requestHandler = requestHandlers.get(i);
        // 轮询获取能够处理这次活动请求(action's request)的请求处理器
        if (requestHandler.canHandleRequest(request)) {
            return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
        }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

其中所有的请求处理器requestHandlers是在Picasso构造时初始化的,总共为7种。

1
2
3
4
5
6
7
8
9
10
11
12
Picasso(...) {
    ...
    allRequestHandlers.add(new ResourceRequestHandler(context));
    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));
    ...
}

看看网络请求处理器中的匹配规则,即Uri中包含”http”或”https”。

1
2
3
4
5
/* NetworkRequestHandler.class */
@Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return ("http".equals(scheme) || "https".equals(scheme));
}

3.3.2 service.submit(hunter)开启图片捕获器hunter线程,获取bitmap

图片捕获器hunter是一个Runnable,hunter获取后执行submit()方法开启hunter线程。

1
2
3
4
5
6
/* ExecutorService.class */
public Future<?> submit(Runnable task) {
    ...
    execute(ftask);
    return ftask;
}

hunter线程中的核心操作就是获取图片资源,然后分发获取结果

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
/* BitmapHunter.class */
@Override public void run() {

    Bitmap result = hunt();

    if (result == null) {
        dispatcher.dispatchFailed(this);
    } else {
        dispatcher.dispatchComplete(this);
    }
    ...
}

Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
    // 从缓存中读取图片资源
    if (shouldReadFromMemoryCache(memoryPolicy)) {
        bitmap = cache.get(key);
        ...
        return bitmap;
    }

    // 缓存未命中,使用请求处理器加载图片资源
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
        ...
        bitmap = result.getBitmap();
        ...
    }
    ...
    return bitmap;
}

本篇以请求网络图片为背景展开介绍,load()方法中使用下载器downloader来下载图片,当前Picasso版本中的网络下载器貌似只有OkHttp3Downloader,没有UrlConnectionDownloader了。

网络请求处理器NetworkRequestHandler中的load()方法如下,即使用下载器下载图片资源。

1
2
3
4
5
6
7
8
/* NetworkRequestHandler.class */
@Override public Result load(Request request, int networkPolicy) throws IOException {
    okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
    Response response = downloader.load(downloaderRequest);
    ResponseBody body = response.body();
    ...
    return new Result(body.source(), loadedFrom);
}

网络下载器OkHttp3Downloader中的load()方法如下,请求完成即可获取图片资源。

1
2
3
4
/* OkHttp3Downloader.class */
@NonNull @Override public Response load(@NonNull Request request) throws IOException {
    return client.newCall(request).execute();
}

至此图片资源获取bitmap完成,hunter执行dispatcher.dispatchComplete(this);方法来将图片获取结果分发出去,最终图片会显示至ImageView控件,下面看看这个分发过程。

3.3.3 bitmap获取后,消息分发

1
2
3
4
/* Dispatcher.class */
void dispatchComplete(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
1
2
3
4
5
6
/* Dispatcher.class */
case HUNTER_COMPLETE: {
    BitmapHunter hunter = (BitmapHunter) msg.obj;
    dispatcher.performComplete(hunter);
    break;
}
1
2
3
4
5
6
/* Dispatcher.class */
void performComplete(BitmapHunter hunter) {
    ...
    batch(hunter);
    ...
}
1
2
3
4
5
6
7
8
/* Dispatcher.class */
private void batch(BitmapHunter hunter) {
    ...
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
        handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
}
1
2
3
4
5
/* Dispatcher.class */
case HUNTER_DELAY_NEXT_BATCH: {
    dispatcher.performBatchComplete();
    break;
}
1
2
3
4
5
6
7
/* Dispatcher.class */
void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
}
1
2
3
4
5
6
7
8
9
/* Picasso.class */
case HUNTER_BATCH_COMPLETE: {
    @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
    for (int i = 0, n = batch.size(); i < n; i++) {
        BitmapHunter hunter = batch.get(i);
        hunter.picasso.complete(hunter);
    }
    break;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/* Picasso.class */
void complete(BitmapHunter hunter) {
    Action single = hunter.getAction();
    ...
    Exception exception = hunter.getException();
    Bitmap result = hunter.getResult();
    LoadedFrom from = hunter.getLoadedFrom();

    if (single != null) {
        deliverAction(result, from, single, exception);
    }
    ...
}
1
2
3
4
5
6
/* Picasso.class */
private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
    ...
    action.complete(result, from);
    ...
}

最后,本次action资源请求成功,调用其complete()方法,通过PicassoDrawable的setBitmap()方法来将图片显示至目标控件中。

1
2
3
4
5
6
/* ImageViewAction.class */
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    ...
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
    ...
}

PicassoDrawable的setBitmap()方法中,将获取到的bitmap转为drawable,然后设置进ImageView控件中,即可完成图片的显示。

1
2
3
4
5
6
/* PicassoDrawable.class */
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);
}

小结:上述流程即先在get()方法中创建Picasso,其中包含下载器downloader、缓存器cache、线程池service、请求转换器transformer、分发器dispatcher。然后在load(url)方法中创建请求Request实例,通过请求实例可以设置占位图,调整图片显示尺寸等操作。紧接着在into(iv)方法中创建一个请求图片的活动action,action被提交给了图片捕获器hunter线程,hunter启动后,先为action匹配合适的请求处理器requestHandler,然后requestHandler使用下载器downloader从网络下载图片资源,网络请求完成后,通过分发器dispatcher告知action本次图片资源获取成功。最后在action的complete()方法中使用PicassoDrawable将图片设置进目标控件,完成图片的显示。


4. 额外说明

下面对Picasso对象的各个核心内部对象简要的看一下。

4.1 下载器downloader

当前Picasso版本的下载器只有OkHttp3Downloader。

4.2. 缓存器cache

使用的是LruCache,具体的内存和磁盘缓存空间为RAM的15%和磁盘的2%且范围为5~50mb。

memory cache of 15% the available application RAM,Disk cache of 2% storage space up to 50MB but no less than 5MB。

4.3. 线程池service

默认池数为3,网络状态为wifi时为4,4G时为3,3G时为2,2G时为1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static final int DEFAULT_THREAD_COUNT = 3;

switch (info.getType()) {
    case ConnectivityManager.TYPE_WIFI:
    case ConnectivityManager.TYPE_WIMAX:
    case ConnectivityManager.TYPE_ETHERNET:
        setThreadCount(4);
        break;
    case ConnectivityManager.TYPE_MOBILE:
        switch (info.getSubtype()) {
            case TelephonyManager.NETWORK_TYPE_LTE:  // 4G
                setThreadCount(3);
                break;
            case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
                setThreadCount(2);
                break;
            case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
                setThreadCount(1);
                break;
        }
        break;
}

4.4. 请求转换器transformer

默认为不做任何转换处理,直接返回原请求。

1
2
3
4
5
RequestTransformer IDENTITY = new RequestTransformer() {
    @Override public Request transformRequest(Request request) {
        return request;
    }
};

4.5. 分发器dispatcher

主要就是通过Handler来分发各种消息。