Picasso 源码流程分析

前言

Picasso 是非常好用的图像加载框架,它清晰的图片加载流程:with->load->into,即使用当前上下文加载指定路径的图片资源,然后显示在 ImageView 中。所以,分析 Picasso 的源码,可以从它的流程着手。

用法回顾:Picasso.with(context).load(uri).into(mImageView);

整体流程

一. with() 创建必备的对象

  1. 创建 Downloader 对象,Utils.createDownloader(context);
  2. 创建 LruCache 对象
  3. 创建 ExecuteorService 线程池对象
  4. 创建 RequestTransfomer 对象
  5. 实例化 Diaptcher 对象,开启异步下载,开启缓存
  6. 实例化 Picasso 对象:执行监听、图形转换、调试模式等

二. load()

  1. 创建 RequestCreator对象

三. into()

  1. 创建 Request、Action 对象
  2. 交给 Dispatcher 执行
  3. 创建 BitmaHunter (用户捕获图片)

with 过程

首先在 with 方法内部使用 Builder 创建了单例的 Picasso 对象,而且这种单例是加上 volatile 关键字+双重检查加锁+ 懒汉式,懒汉式特点是:线程安全、高效、延迟加载(节约内存)。使用 volatile 关键字可以禁止 CPU 重排序优化、单例的修改对所有线程可见。此种写法,在 Android 系统不建议使用枚举的前提下,不失为一种较优解。

 static volatile Picasso singleton = null;

 public static Picasso with(Context context) {
        if (singleton == null) {
            synchronized (Picasso.class) {
                if (singleton == null) {
                    singleton = new Builder(context).build();
                }
            }
        }
        return singleton;
    }

接着,在 build 中初始化各种对象


 /** Create the {@link Picasso} instance. */
    public Picasso build() {
        Context context = this.context;

        // 创建默认下载器
        if (downloader == null) {
            downloader = Utils.createDefaultDownloader(context);
        }
        // 默认的内存缓存算法(通过Lru 最近最少原则,进行内存的管理)
        if (cache == null) {
            cache = new LruCache(context);
        }
        // 创建Picasso自有的线程池对象(通过多线程加载网络图片)
        if (service == null) {
            service = new PicassoExecutorService();
        }
        // 初始化自定义的Transmform对象(用户对图片的自定义转换)
        if (transformer == null) {
            transformer = RequestTransformer.IDENTITY;
        }

        // 创建了一个统计类
        Stats stats = new Stats(cache);

        // 初始化调度者(需要传递:上下文、线程池、handler,下载器,缓存目录和统计类)
        Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

        // 最终利用 builder 对象的各种属性、对象,创建Picasso对象(需要传递:上下文、调度者、缓存目录、监听器、handler等,默认图片参数、是否开启调试标志、是否开启日志标志)
        return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
                defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }
}

Picasso 是如何初始化默认的 Downloader 的?

答案是:在 createDefaultDownloader 中,首先通过反射查找是否存在okHttpClient 对象,如果存在则使用 okHttpDownloader,否则使用 UrlConnectionDownloader

    static Downloader createDefaultDownloader(Context context) {
        try {
            Class.forName("com.squareup.okhttp.OkHttpClient");
            return OkHttpLoaderCreator.create(context);
        } catch (ClassNotFoundException ignored) {
        }
        return new UrlConnectionDownloader(context);
    }

在 Picasso 类中,还能清晰的看到定义调试模式的颜色
(当设置 Picasso 属性 indicatorsEnabled(true),加载后的 Bitmap 左上角会根据不同颜色指示图片来源):

  1. 来自内存:绿色
  2. 来自硬盘:蓝色
  3. 来自红色:网络
    /** Describes where the image was loaded from. */
  public enum LoadedFrom {
        MEMORY(Color.GREEN),
        DISK(Color.BLUE),
        NETWORK(Color.RED);

        final int debugColor;

        private LoadedFrom(int debugColor) {
            this.debugColor = debugColor;
        }
    }

load 过程

load(Uri) 返回的 RequestCreator 是一个请求创建者,可以对其设置各种属性。Picasso.load() 方法可以加载:File、resId、String 以及其他Uri类型的地址,除了 resId 单独作为参数交给 Request.Builder,其他的都被转换成了 Uri。

RequestCreateor构造方法主要的作用:

  1. 传入了 Picasso 对象
  2. 使用 Request.Builder 创建了 Request 对象,后续对ReqestCreateor 的设置都作用在 Reqest 对象中
 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);
    }

RequestCreator 提供的各种设置方法:

public RequestCreator noPlaceholder() 不需要加载前显示的图片
public RequestCreator placeholder(int placeholderResId) 设置加载前显示的图片
public RequestCreator error(int errorResId) 加载出错时显示的图片
public RequestCreator tag(Object tag) 设置全局tag,以便对加载过程进行暂停、取消、继续的操作
public RequestCreator fit() 设置图片的显示模式为:填充ImageView控件宽高
public RequestCreator resize(int targetWidth, int targetHeight) 重新调整图片的大小
public RequestCreator centerCrop() 和 resize 方法配合使用,以resize前的尺寸作为参考,按比例修改图片尺寸
public RequestCreator centerInside() 和 resize 方法配合使用,以resize后的尺寸作为参考,将图片强制缩放到resize

into 过程

如果说 with、load 是 Picasso 图像加载的前戏, into 就是高潮:下载(hunt)、解析(decodeStream)。这个过程分散在以下几个区域。让我们一一解析。

一、RequestCreater类中

1 . checkMain 检查当前线程是否是主线程

为啥要检查是否在主线程,因为通过分析可以发现,BitmapHunter 类下载完图片、解析完成后,会通过 handler message 机制向主线程发送消息,所以必须检查是否在主线程

2 . 设置图片的占位符、调整图片大小、图片裁切等

就是之前介绍的 RequestCreator 中的系列设置方法

3 . 创建 Request 对象
4 . 从内存中获取图片,获取到则直接返回
5 . 创建 ImageViewAction 对象

Picasso.enqueueActionSubmit(action) 提交请求
将上面的action对象提交到Picasso中了

二、Picasso 类中

1 . Picasso中submit(action);
2 . dispatcher.dispatcherSubmit(action);

上面说到,RequestCreator 对象中通过Picasso.enqueueActionSubmit(action) 将 action 提交到 Picasso 中,而 Picasso 中通过 dispatcher.dispatcherSubmit(action) 又将对象放入调度者中

三、Dispatcher类中

1 . handler.sendMessage()
2 . handler 类回调 handleMessage
3 . 创建 bitmaoHunter

上面说到,Picasso 中通过 dispatch.dispatchSubmit(action) 方法,将 action 提交到 dispatcher 调度者中,而调度者通过一个 handler message,将 action又重新发到 Picasso 中,最后在 Picasso 的 handler 的 handlerMessage 中创建了 BitmapHunter 对象。

四、BitmapHunter

1 . hunt 获取图片
2 . decodeStream 对图片进行解析


上面说到,Picasso 的handler 的 handleMessgae 中创建了BitmapHunter,而 BitmapHunter 将具体通过线程池下载和解析图片(hunt and decodeStream)
另外,BitmapHunter 实现了Runnable接口(就是一个会被添加到线程池ExecutorService 的一个任务),所有的加载、解析图片的过程都发生在子线程

hunt 的全过程:

    Bitmap hunt() throws IOException {
        Bitmap bitmap = null;

        // 首先从内存缓存中加载图片(根据在RequestCreator中设置的memoryPolicy)
        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;
            }
        }

        // 网络加载图片(根据在RequestCreator中设定的NetworkPolicy)
        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) {
                    // 在这里根据用户自定义的图形转换规则,对Bitmap做进一步处理
                    stats.dispatchBitmapTransformed(bitmap);
                }
            }
        }

        // 加载、解析后返回被处理的btimap。至此,整个Picasso的异步加载过程完成
        return bitmap;
    }

至此,整个过程算是完成了

附录

参考:
1 . Android图片加载库Picasso源码分析

2 . picasso-强大的Android图片下载缓存库

你可能感兴趣的:(Android)