Android Picasso 原理解析

Android Picasso 原理解析

  • 一.提供的功能特性
  • 二.简单使用
  • 三.源码结构
    • 1.整体类图
    • 2.内存缓存
    • 3.磁盘缓存
    • 4.Picasso对象
    • 5.RequestCreator对象
    • 6.Action对象
    • 7.Dispatcher对象
    • 8.RequestHandler对象
    • 9.BitmapHunter对象
    • 10.图片对应的Key
      • (1)内存缓存
      • (2)磁盘缓存
  • 四.流程分析

另一个常用的图片库Glide的解析,可以参考这篇文章。

一.提供的功能特性

  1. 使用默认的内存缓存和磁盘缓存(可自定义)

  2. 可自定义图片的变换操作、解析格式等

  3. 可以选择裁剪方式、大小等

  4. 可以加载网络资源、本地资源等

  5. 可以选择内存缓存和磁盘缓存的策略

  6. 可以暂停、恢复、取消加载

  7. 可以设置加载成功失败的占位图及相应监听

二.简单使用

Picasso.with(context)//拿到Picasso对象
				.load(url)//load一个url
                .resize(width, height)//重新裁剪大小
                .memoryPolicy(MemoryPolicy.NO_STORE)//内存缓存策略(不存入内存)
				.networkPolicy(NetworkPolicy.OFFLINE)//磁盘缓存策略(不通过网络请求)
                .into(imageView);//显示到ImageView中

三.源码结构

1.整体类图

Android Picasso 原理解析_第1张图片

2.内存缓存

Android Picasso 原理解析_第2张图片

  1. picasso默认的内存缓存实现是LruCache类,维护一个Lru规则的LinkedList存储key和bitmap

  2. 默认的缓存大小为堆内存的1/7,约15%

  3. 我们可以自己实现Cache类来设置到Picasso的全局环境中(下面会提到)

3.磁盘缓存

Android Picasso 原理解析_第3张图片

  1. picasso提供了两个磁盘缓存类OkHttpDownloader、UrlConnectionDownloader,在引入OkHttp库时会使用OkHttpDownloader

    static Downloader createDefaultDownloader(Context context) {
      try {
        Class.forName("com.squareup.okhttp.OkHttpClient");
        return OkHttpLoaderCreator.create(context);
      } catch (ClassNotFoundException ignored) {
      }
      return new UrlConnectionDownloader(context);
    }
    
  2. OkHttpDownloader里面的操作其实是靠OkHttpClient完成,其通过设置headers的一些属性来实现磁盘缓存,HttpUrlConnection也是通过类似方式实现

  3. OkHttpClient可以设置其Cache对象,默认为一个DiskLruCache,提供get、put等方法,在使用OkHttpClient请求时,会根据其设置headers属性来决定DiskCache的存取

  4. 磁盘缓存默认路径为应用的cache文件夹下的picasso-cache文件夹,大小为可用磁盘空间的2%(5~50MB)

  5. 我们可以自己实现一个Downloader设置到picasso全局环境中(下面会提到),来进行磁盘缓存

4.Picasso对象

下面就来按步骤分析一下picasso加载图片的流程,首先看Picasso.with(context)获取Picasso对象
Android Picasso 原理解析_第4张图片

  1. 由图可知,Picasso是通过builder模式构建的,Builder类提供一些方法设置全局环境,如BitmapConfig、Downloader、cache等,所以我们可以自己创建这样的builder,并设置相应属性,build后调用setSingletonInstance设置到单例对象上即可

  2. 默认创建Picasso对象时,使用的是默认的属性,如上面提到的OkHttpDownloader、LruCache等

5.RequestCreator对象

在我们调用load(url)方法后,返回的是一个新的RequestCreator对象,这个对象是包装各种请求参数用的,其相关方法为链式调用
Android Picasso 原理解析_第5张图片

  1. RequestCreator对象提供了各种占位图的设置方法以及缓存策略的方法

  2. 其内部封装了一个Request.Builder对象,用于设置图片相关参数,也是通过builder模式创建;如加载的uri、resize的大小、裁剪方式等,对我们其实是不可见的

6.Action对象

Android Picasso 原理解析_第6张图片

  1. 由图可知,Action抽象类提供了加载完成后的一些回调方法,不同的实现类用于以不同方式处理加载完成的后续工作,在我们into(iv/target/…)后会构建相应的Action,然后开始加载的流程

  2. ImageViewAction就是into(ImageView)时构建的action,它在加载完成后会将bitmap设置到ImageView上

  3. TargetAction为我们自定义target对象的处理,会将Bitmap回调给相应的方法上供我们处理

7.Dispatcher对象

dispatcher对象在Picasso对象创建时创建,用于调度派发整个加载流程
Android Picasso 原理解析_第7张图片

  1. 内部会创建新的线程及其handler,即任何的派发、调度的请求都会在这个新的线程的handler上进行

  2. 创建时会保存住主线程的handler,在结束加载后,将结果返回到主线程上进行

  3. 在发起加载请求时,找到相应处理的RequestHandler(下面会说),构建BitmapHunter对象(下面会说,实质是Runnable),并将其放入到ExecutorService线程池进行执行

8.RequestHandler对象

Android Picasso 原理解析_第8张图片

  1. 上面说到的RequestHandler就是用语处理不同源数据的类,具体的实现类处理自己的源(如本地文件的、网络的),最终将流包装到一个内部的Result类对象中返回即可

  2. canHandleRequest会根据load的uri的scheme判断是否是自己要解析的数据源,在dispatcher中构建BitmapHunter时,也是根据该方法找到正确的RequestHandler进行创建的

  3. picasso默认提供一些RequestHandler类,并在创建picasso对象时注册进去,我们也可在上面说的Picasso.Builder中设置自定义的requestHandler

  4. 这里举两个RequestHandler的处理:

//本地源解析
@Override public Result load(Request request, int networkPolicy) throws IOException {
  return new Result(getInputStream(request), DISK);//将流包装,构造Result对象
}

InputStream getInputStream(Request request) throws FileNotFoundException {
  ContentResolver contentResolver = context.getContentResolver();
  return contentResolver.openInputStream(request.uri);
}
 
//网络流解析
@Override public Result load(Request request, int networkPolicy) throws IOException {
  Response response = downloader.load(request.uri, request.networkPolicy);//通过downloader加载数据
  if (response == null) {
    return null;
  }

  Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;

  Bitmap bitmap = response.getBitmap();
  if (bitmap != null) {
    return new Result(bitmap, loadedFrom);
  }

  InputStream is = response.getInputStream();
  if (is == null) {
    return null;
  }
  ...
  return new Result(is, loadedFrom);//将流包装,构造Result对象
}

可以看到,网络处理中,使用的就是指定的Downloader对象进行加载

9.BitmapHunter对象

上面有了请求类、调度类、加载类和处理类,那么实际的生成Bitmap的类是哪个呢,下面就来看看BitmapHunter这个类,它就是负责管理加载过程和生成bitmap的类
Android Picasso 原理解析_第9张图片

  1. 其实质为Runnable,即ExecutorService执行的真正处理对象

  2. 他会现根据内存策略决定是否从内存缓存中查找、内存中没有会调用相应requestHandller的load方法进行加载(本地缓存在内部进行)

  3. 拿到Result后,根据设置的图片相关参数进行decode,生成Bitmap,再根据默认的和自定义的变换规则进行变换,生成最终的bitmap对象

  4. 最终通知dispatcher完成加载,回调结果

10.图片对应的Key

这里有必要说下图片在内存缓存和磁盘缓存中对应的key值的形式

(1)内存缓存

生成内存缓存key

static String createKey(Request data, StringBuilder builder) {
  if (data.stableKey != null) {
    builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
    builder.append(data.stableKey);
  } else if (data.uri != null) {
    String path = data.uri.toString();
    builder.ensureCapacity(path.length() + KEY_PADDING);
    builder.append(path);
  } else {
    builder.ensureCapacity(KEY_PADDING);
    builder.append(data.resourceId);
  }
  builder.append(KEY_SEPARATOR);

  if (data.rotationDegrees != 0) {
    builder.append("rotation:").append(data.rotationDegrees);
    if (data.hasRotationPivot) {
      builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
    }
    builder.append(KEY_SEPARATOR);
  }
  if (data.hasSize()) {
    builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
    builder.append(KEY_SEPARATOR);
  }
  if (data.centerCrop) {
    builder.append("centerCrop").append(KEY_SEPARATOR);
  } else if (data.centerInside) {
    builder.append("centerInside").append(KEY_SEPARATOR);
  }

  if (data.transformations != null) {
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, count = data.transformations.size(); i < count; i++) {
      builder.append(data.transformations.get(i).key());
      builder.append(KEY_SEPARATOR);
    }
  }

  return builder.toString();
}
  1. 如代码所示,每个bitmap的key值就是uri加上各种参数(以KEY_SEPARATOR分割)组合成的String,这也就说明了,不同参数(比如resize不同大小的)请求到的bitmap的是不同的,都会存入cache中

  2. 而clear方法清空相应uri的cache时,是以第一个KEY_SEPARATOR前的String做比较,也就是会清空同一uri的所有bitmap

(2)磁盘缓存

磁盘缓存key

//将Request的url转换为key即可
private static String urlToKey(Request request) {
  return Util.md5Hex(request.urlString());
}
 
//MD5加密转换为16进制字符串
public static String md5Hex(String s) {
  try {
    MessageDigest messageDigest = MessageDigest.getInstance("MD5");
    byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8"));
    return ByteString.of(md5bytes).hex();
  } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
    throw new AssertionError(e);
  }
}

由代码可知,将url进行MD5加密转换为16进制字符串作为key,存放在本地磁盘中

四.流程分析

以上把各个模块作用及实现做了简单分析,下面来看看picasso整体的运行流程,将这些模块串联起来

我们就以主流程Picasso.with(context).load(url).into(imageView);来分析加载过程
Android Picasso 原理解析_第10张图片

  1. 得到全局的Picasso对象,设置url等相关参数构建请求对象

  2. 先去内存缓存找(策略允许的话),如果有则直接返回,没有则进行派发

  3. dispatcher派发时,寻找RequestHandler,并创建BitmapHunter,交由线程池处理

  4. BitmapHunter处理时,先在内存缓存里找(策略允许的话),有的话直接返回,没有的话通过RequestHandler进行处理

  5. RequestHandler由自己的实现类进行相应的处理,网络源时会根据其Downloader对象进行加载数据,根据磁盘缓存策略,从磁盘或者网络读取数据

  6. 将数据流封装到Result对象返回

  7. BitmapHunter再等到Result的流后,根据请求时设置的参数进行decode和transformation,生成最终的bitmap对象,通知dispatcher进行最终处理派发

  8. dispatcher将bitmap放入内存缓存(根据缓存策略),再将结果通知主线程做处理

  9. 主线程中调用Action的相应处理方法完成最终处理:ImageViewAction就是将Bitmap设置到ImageView上。至此结束加载过程

你可能感兴趣的:(原理解析,android相关)