Picasso源码分析

来源:互联网 发布:vm12虚拟机安装mac os 编辑:程序博客网 时间:2024/05/22 12:31

“A powerful image downloading and caching library for Android”
强大的图像下载和缓存库为Android所用!到底有多强大,我们一探究竟。

一、介绍

它简单粗暴的一行代码就搞定了你想要的从url加载图片:

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

Android上的图像加载的许多常见的陷阱是由Picasso自动处理:
1)、处理Adapter中的 ImageView 回收和取消已经回收ImageView的下载进程
2)、使用最少的内存完成复杂的图片转换,比如把下载的图片转换为圆角等
3)、自动添加磁盘和内存缓存

这里写图片描述

二、特性

1、Adapter中的使用
自动检测Adapter中的ImageView重用和取消不必要的下载。

@Override public void getView(int position, View convertView, ViewGroup parent) {  SquaredImageView view = (SquaredImageView) convertView;  if (view == null) {    view = new SquaredImageView(context);  }  String url = getItem(position);  Picasso.with(context).load(url).into(view);}

2、图片转换
转换图片以适合所显示的ImageView,来减少内存消耗。

Picasso.with(context)  .load(url)  .resize(50, 50)  .centerCrop()  .into(imageView)

还可以设置自定义转换来实现高级效果。

public class CropSquareTransformation implements Transformation {  @Override   public Bitmap transform(Bitmap source) {    int size = Math.min(source.getWidth(), source.getHeight());    int x = (source.getWidth() - size) / 2;    int y = (source.getHeight() - size) / 2;    Bitmap result = Bitmap.createBitmap(source, x, y, size, size);    if (result != source) {          source.recycle();    }    return result;  }  @Override   public String key() {       return "square()";   }}

通过实现Transformation 类来重写transform这个方法。

3、占位符图片
Picasso支持下载和加载错误占位符图片。

Picasso.with(context)    .load(url)    .placeholder(R.drawable.user_placeholder)    .error(R.drawable.user_placeholder_error)    .into(imageView);

这里提供两个占位符图片,一个下载错误的图片一个加载错误的图片;这里会重试3次。

4、支持本地资源加载
从 Resources, assets, files, content providers 加载图片都支持。

Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);Picasso.with(context).load(new File(...)).into(imageView3);

5、DEBUG支持
调用函数 setIndicatorsEnabled(true) 可以在加载的图片左上角显示一个 三角形 ,不同的颜色代表加载的来源
这里写图片描述

三、源码分析
从Picasso的简单使用我们可以看到,它的过程是with–>load–>into.
我们就从这三个过程来看:
1、with.
首先在 with 方法内部使用 Builder (建造者模式)创建了单例的 Picasso 对象,而且这种单例是加上 volatile 关键字+双重检查加锁+ 懒汉式 ,使用 volatile 关键字可以禁止 CPU 重排序优化、单例的修改对所有线程可见。双重检查加锁可以保证线程安全。懒汉式的方式延迟加载(节约内存)提高了效率。此种写法,在 Android 系统不建议使用枚举的前提下,不失为一种较优解。

static volatile Picasso singleton = null;... ...public static Picasso with(@NonNull Context context) {    if (context == null) {      throw new IllegalArgumentException("context == null");    }    if (singleton == null) {      synchronized (Picasso.class) {        if (singleton == null) {          singleton = new Builder(context).build();        }      }    }    return singleton;  }

接着就是它的build过程:

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对象。      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,defaultBitmapConfig, indicatorsEnabled, loggingEnabled);    }  }

Picasso 是如何初始化默认的 Downloader 的?
在 createDefaultDownloader 中,首先通过反射查找是否存在okHttpClient 对象,如果存在则使用 okHttpDownloader,否则使用 UrlConnectionDownloader

static Downloader createDefaultDownloader(Context context) {    if (SDK_INT >= GINGERBREAD) {      try {        Class.forName("okhttp3.OkHttpClient");        return OkHttp3DownloaderCreator.create(context);      } catch (ClassNotFoundException ignored) {      }      try {        Class.forName("com.squareup.okhttp.OkHttpClient");        return OkHttpDownloaderCreator.create(context);      } catch (ClassNotFoundException ignored) {      }    }    return new UrlConnectionDownloader(context);  }

基于LinkedHashMap的LruCache就不在这里细说了,自己翻阅资料。
然后就是Picasso自有的线程池对象PicassoExecutorService的adjustThreadCount(NetworkInfo info) 方法,它会根据网络WIFI、4G、3G、2G的不同状态,创建了不同的线程池大小。
RequestTransformer定义成一个接口,可用于实现它进行自定义转换图片。
Stats用于统计监控,支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。
接着 实例化 Diaptcher 对象,开启异步下载,开启缓存。
最后返回实例化后的 Picasso 对象。

2、load
load(Uri) 返回的 RequestCreator 是一个请求创建者,可以对其设置各种属性。Picasso.load() 方法可以加载:File、resId、String 以及其他Uri类型的地址,除了 resId 单独作为参数交给 Request.Builder,其他的都被转换成了 Uri。
RequestCreateor构造方法主要的作用:
a)、传入了 Picasso 对象
b)、使用 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);  }

3、into
重头戏来了!之前都是伏笔,先一睹它的芳容:

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 || target.isLayoutRequested()) {        if (setPlaceholder) {          setPlaceholder(target, getPlaceholderDrawable());        }        picasso.defer(target, new DeferredRequestCreator(this, target, callback));        return;      }      data.resize(width, height);    }    //创建 Request 对象    Request request = createRequest(started);    String requestKey = createKey(request);    //从内存缓存中获取图片,获取到则直接返回requestKey相应的缓存图片。    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());    }    //创建 ImageViewAction 对象    Action action =        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,            errorDrawable, requestKey, tag, callback, noFade);    picasso.enqueueAndSubmit(action);  }

代码有点儿长~不过流程还算比较清晰:

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

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

2 . 设置图片的占位符、调整图片大小、图片裁切等–就是之前介绍的 RequestCreator 中的系列设置方法
3 . 创建 Request 对象
4 . 从内存中获取图片,获取到则直接返回
5 . 创建 ImageViewAction 对象,最后通过Picasso.enqueueActionSubmit(action) 提交请求,将上面的action对象提交到Picasso中。
接着看Picasso中的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);  }

最后又submit到之前初始化的调度器dispatcher了

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

我们来看Dispatcher中的dispatchSubmit:

  void dispatchSubmit(Action action) {    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));  }

终于等到你~熟悉Handler机制的都应该知道了;它就是调用handler去传递消息来调度action的,最后是交给了DispatcherHandler的handleMessage处理。具体怎么处理的呢;我们可以看到它在内部创建了BitmapHunter 对象。

 BitmapHunter hunter = (BitmapHunter) msg.obj;

4、BitmapHunter
BitmapHunter 实现了Runnable接口(就是一个会被添加到线程池ExecutorService 的一个任务),所有的加载、解析图片的过程都发生在子线程。
我们看到它的run()方法里面result = hunt(); 结果都来源于hunt()方法,所以我们重点看一下它的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();      exifOrientation = result.getExifOrientation();      bitmap = result.getBitmap();      //如果得到的不是图片而是InputStream,则转换成Bitmap      if (bitmap == null) {        InputStream is = result.getStream();        try {          bitmap = decodeStream(is, data);        } finally {          Utils.closeQuietly(is);        }      }    }    //打印LOG    if (bitmap != null) {      if (picasso.loggingEnabled) {        log(OWNER_HUNTER, VERB_DECODED, data.logId());      }      //使用stats做标记。      stats.dispatchBitmapDecoded(bitmap);      //进行裁剪,旋转等变换      if (data.needsTransformation() || exifOrientation != 0) {        synchronized (DECODE_LOCK) {          if (data.needsMatrixTransform() || exifOrientation != 0) {            bitmap = transformResult(data, bitmap, exifOrientation);            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");            }          }        }        // 在这里根据用户自定义的图形转换规则,对Bitmap做进一步处理        if (bitmap != null) {          stats.dispatchBitmapTransformed(bitmap);        }      }    }    // 加载、解析后返回被处理的bitmap。    return bitmap;  }

最后把返回的结果交给它们处理:
Dispatcher.performComplete()
Picasso.comple()//从Hunter中取出结果,调用Action.complete,or Action.error
至此,整个Picasso的异步加载过程完成。

盗图以总结:
这里写图片描述

1 0