好久没写文章了,一个是工作的原因,还一个就是这几个月看了很多文章,一直在补充自己的知识。之前看到一句很喜欢的话——感到快乐就忙东忙西,感到累了就放空自己,这几个月一“快乐”了,就停不下来地看、一直在写代码。期间由于项目的需求,用这里那里学来的东西,写了一套RxJava+Retrofit+OkHttp的网络请求框架;重新整理了项目中下拉刷新的列表,封装了一套UltraPTR+RecyclerView的下拉刷新控件……当然,也包括今天这里要写到图片加载缓存库,包括对Picasso和Glide的封装。

Picasso和Glide

Picasso和Glide两个库应该算是目前Android界最流行的图片加载库了,Picasso是由JakeWharthon大神牵头开发和维护的,Glide则是Google内部员工开源的,所以无论选择那个,都可以不必太过担心售后的问题,毕竟大品牌就摆在那里。Picasso正如它的名字取于大画家“毕加索”,所以它以加载图片的质量高著称,当然还有一个是它所需的缓存空间少;而Glide也好比它的中文意思”滑翔”,则以它加载图片的速度快为特色,相对的它需要的缓存空间更大。其中详细的对比,在这里就不做展开,毕竟今天的主题是“打造自己的图片加载库”。由于Picaaso和Glide的Api和用法都是类似的,为了描述方便,接下来主要用Picasso来讲解。

详细的对比,这里推荐一篇文章。
Picasso和Glide图片加载库对比

第一版实现

封装第三方的图片加载库,对于我们开发者来说,最关心的就是两点:使用简单、替换方便
参考了之前项目,和网上的一些例子,普遍的做法就是写一个工具类,然后维护一个静态加载库实例,最后根据需求写一系列的静态方法。这个实现应该是最简单、直接、暴力的了,使用也简单,在需要加载图片的地方,直接调用一些静态方法即可用上Picasso。

ImageLoaderUtil.getInstance().load(url, imageView1);ImageLoaderUtil.getInstance().load(resourceId, imageView2);

但是如果这样都用静态方法来实现,使用虽然简单,替换却不怎么简单。当要替换另一个图片加载库时,我们需要改写一系列的load重载方法,而且随着需求点越多,这些方法会越来越多,变得越来越难管理。再者从设计的角度来看,这样的“简单封装”也是不可取的,违背了软件设计的OCP开闭原则

使用设计模式

Picasso本身的功能点并不多,参考其本身使用方法的设计,就是典型的建造者模式的实现。因此我们也决定使用建造者模式进行调用的封装。而针对替换方便,我们可以考虑使用策略模式进行替换封装。这里简单普及一下建造者模式和策略模式。

建造者模式

建造者模式(Builder),是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

通俗的讲,使用者只需要告诉我你想要哪些功能点,逐个告诉我,然后我来拼凑给你,期间的建造过程和细节,你都不需要知道。这样正符合我们对Picasso的调用,指定加载的参数,内部使用Picasso进行图片加载,外部不需要只能里面使用的是Picasso还是Glide,我只需要得到我指定参数的图片。

策略模式

策略模式(Strategy),定义多组不同的算法或策略,并将每个算法或策略封装到具有共同接口的独立类中,从而可以灵活相互替换,不影响使用者。

策略模式的核心在于定义一组算法的公共接口,然后就可以实现这些接口来制定不同的策略。拿到我们的应用场景,我们可以定义一个策略接口,包含一个loadImage(Builder builder)方法,并在参数内传入我们上面提供的建造者对象,则可以在具体的策略实现类里面,构造我们的第三方图片加载库调用。由于我们可以指定不同的策略,因此我们可以非常轻松的切换Picasso和Glide。

改进版实现

BaseImageConfig.java

根据调用的配置,我们先定下通用配置的基类BaseImageConfig.java。这里支持配置远程url、本地资源resourceId和Uri加载,默认加载到ImageView,还包含占位图placeholder和加载失败图errorPic。

public class BaseImageConfig {protected String url;protected int resourceId;protected Uri uri;protected ImageView imageView;protected int placeholder;protected  int errorPic;public String getUrl() {return url;}public int getResourceId() {return resourceId;}public Uri getUri() {return uri;}public ImageView getImageView() {return imageView;}public int getPlaceholder() {return placeholder;}public int getErrorPic() {return errorPic;}
}

PicassoImageConfig.java

PicassoImageConfig类继承BaseImageConfig,并针对Picasso的功能点增加自定义参数,如缓存策略,transformation转换等等,并使用建造者模式实现。

public class PicassoImageConfig extends BaseImageConfig {/*** 默认,内存缓存、本地缓存*/public static final int STRATEGY_ALL = 0;/*** 只需本地缓存*/public static final int STRATEGY_NO_MEMORY = 1;/*** 直接从网络获取*/public static final int STRATEGY_NO_MEMORY_DISK = 2;/*** 从内存缓存、本地缓存获取*/public static final int STRATEGY_OFFLINE = 3;private boolean isFit = false;private Transformation transformation;private List<Transformation> transformations;private int cacheStrategy = STRATEGY_ALL;private PicassoImageConfig(Builder builder) {this.url = builder.url;this.resourceId = builder.resourceId;this.uri = builder.uri;this.placeholder = builder.placeholder;this.errorPic = builder.errorPic;this.imageView = builder.imageView;this.isFit = builder.isFit;this.transformation = builder.transformation;this.transformations = builder.transformations;this.cacheStrategy = builder.cacheStrategy;}public boolean isFit() {return isFit;}public Transformation getTransformation() {return transformation;}public List<Transformation> getTransformations() {return transformations;}public int getCacheStrategy() {return cacheStrategy;}public static Builder builder() {return new Builder();}public static final class Builder {private String url;private int resourceId;private Uri uri;private int placeholder;private int errorPic;private ImageView imageView;private boolean isFit = false;private Transformation transformation;private List<Transformation> transformations;private int cacheStrategy;private Builder() {}public Builder load(String url) {this.url = url;return this;}public Builder load(int resId) {this.resourceId = resId;return this;}public Builder load(Uri uri) {this.uri = uri;return this;}/*public Builder resourceId(int resourceId) {this.resourceId = resourceId;return this;}*/public Builder placeholder(int placeholder) {this.placeholder = placeholder;return this;}public Builder errorPic(int errorPic) {this.errorPic = errorPic;return this;}public Builder into(ImageView imageView) {this.imageView = imageView;return this;}public Builder fit() {this.isFit = true;return this;}public Builder transformation(Transformation transformation) {this.transformation = transformation;return this;}public Builder transformations(List<Transformation> transformations) {this.transformations = transformations;return this;}public Builder cacheStrategy(int cacheStrategy) {this.cacheStrategy = cacheStrategy;return this;}public PicassoImageConfig build() {return new PicassoImageConfig(this);}}
}

BaseImageLoaderStrategy.java

定义所有策略的公共接口。loadImage用于加载图片,clear用于清除缓存。

public interface BaseImageLoaderStrategy<T extends BaseImageConfig> {void loadImage(Context ctx, T config);void clear(Context ctx, T config);
}

PicassoImageLoaderStrategy.java

封装Picasso核心代码,通过调用Picasso加载图片的策略具体类。

public class PicassoImageLoaderStrategy implements BaseImageLoaderStrategy<PicassoImageConfig> {public PicassoImageLoaderStrategy(Context ctx) {OkHttpClient okHttpClient = new OkHttpClient.Builder().cache(OkHttp3Downloader.createDefaultCache(MyApplication.getContext())).build();Picasso picasso = new Picasso.Builder(ctx.getApplicationContext()).downloader(new OkHttp3Downloader(okHttpClient)).build();if (AppConfig.IS_DEBUG_ABLE) {picasso.setIndicatorsEnabled(true);picasso.setLoggingEnabled(true);}Picasso.setSingletonInstance(picasso);}@Overridepublic void loadImage(Context ctx, PicassoImageConfig config) {if (ctx == null) throw new IllegalStateException("Context is required");if (config == null) throw new IllegalStateException("GlideImageConfig is required");if (config.getImageView() == null) throw new IllegalStateException("imageview is required");if (TextUtils.isEmpty(config.getUrl()) && config.getResourceId() == 0 && config.getUri() == null) {throw new IllegalStateException("url or resourceId or Uri is required");}Picasso picasso = Picasso.with(ctx);RequestCreator requestCreator;if (!TextUtils.isEmpty(config.getUrl())) {requestCreator = picasso.load(config.getUrl());}else if (config.getResourceId() != 0) {requestCreator = picasso.load(config.getResourceId());//使用资源id为picasso的stableKey,用来清除缓存requestCreator.stableKey(String.valueOf(config.getResourceId()));}else {requestCreator = picasso.load(config.getUri());}switch (config.getCacheStrategy()) {case PicassoImageConfig.STRATEGY_ALL:break;case PicassoImageConfig.STRATEGY_NO_MEMORY:requestCreator.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE);break;case PicassoImageConfig.STRATEGY_NO_MEMORY_DISK:requestCreator.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE).networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE);break;case PicassoImageConfig.STRATEGY_OFFLINE:requestCreator.networkPolicy(NetworkPolicy.OFFLINE);break;}if (config.isFit()) {requestCreator.fit();}if (config.getTransformation() != null) {requestCreator.transform(config.getTransformation());}if (config.getTransformations() != null) {requestCreator.transform(config.getTransformations());}if (config.getPlaceholder() != 0) {requestCreator.placeholder(config.getPlaceholder());}if (config.getErrorPic() != 0) {requestCreator.error(config.getErrorPic());}requestCreator.into(config.getImageView());}@Overridepublic void clear(Context ctx, PicassoImageConfig config) {if (ctx == null) throw new IllegalStateException("Context is required");if (config == null) throw new IllegalStateException("GlideImageConfig is required");if (TextUtils.isEmpty(config.getUrl()) && config.getResourceId() == 0 && config.getUri() == null) {throw new IllegalStateException("url or resourceId or Uri is required");}Picasso picasso = Picasso.with(ctx);if (!TextUtils.isEmpty(config.getUrl())) {picasso.invalidate(config.getUrl());}else if (config.getResourceId() != 0){picasso.invalidate(String.valueOf(config.getResourceId()));}else {picasso.invalidate(config.getUri());}}
}

ImageLoaderUtil.java

最后就是我们第一版里面的ImageLoaderUtil工具类,不过这里只是实现对策略指定而已。

public class ImageLoaderUtil {private static volatile ImageLoaderUtil INSTANCE = null;private BaseImageLoaderStrategy mStrategy;public ImageLoaderUtil() {}public static ImageLoaderUtil getInstance() {if (INSTANCE == null) {synchronized (ImageLoaderUtil.class) {if (INSTANCE == null) {INSTANCE = new ImageLoaderUtil();}}}return INSTANCE;}/*** 指定ImageLoader策略*/public void init(BaseImageLoaderStrategy strategy) {setLoadImgStrategy(strategy);}public <E extends Activity, T extends BaseImageConfig> void loadImage(E context, T config) {if (this.mStrategy == null) {throw new IllegalStateException("You must call init() to set the ImageLoader strategy first!");}this.mStrategy.loadImage(context, config);}public <T extends BaseImageConfig> void clear(Context context, T config) {if (this.mStrategy == null) {throw new IllegalStateException("You must call init() to set the ImageLoader strategy first!");}this.mStrategy.clear(context, config);}public void setLoadImgStrategy(BaseImageLoaderStrategy strategy) {this.mStrategy = strategy;}}

最后的最后,我们需要在程序最开始启动的地方(也就是Application里面)指定ImageLoaerUtil的当前策略。

ImageLoaderUtil.getInstance().init(new PicassoImageLoaderStrategy(this));

具体调用

         //一般使用ImageLoaderUtil.getInstance().loadImage(this,PicassoImageConfig.builder().load(R.drawable.ic_test).into(imageView1).build());//定制使用ImageLoaderUtil.getInstance().loadImage(this,PicassoImageConfig.builder().load("https://img-my.csdn.net/uploads/201205/11/1336732187_4598.jpg").transformation(new CropCircleTransformation())             //转成圆形.cacheStrategy(PicassoImageConfig.STRATEGY_NO_MEMORY)       //不使用内存缓存.errorPic(R.drawable.ic_error)                         //加载失败默认图.into(imageView2).build());

上面提到了一个CropCircleTransformation,是自定义的Picasso transformation,用来将图片转成圆形图形。transformation的功能非常强大,在里面可以直接拿到你加载的图片的bitmap,有了bitmap你就尽可以为所欲为了…这里推荐一个github上面写得比较完整的transformation,其中还包括了Glide的transformation(也是一样的名字,可见两者相似度之高)

Picasso-Transformations

Picasso的坑

1、Picasso + OkHttp3

Picasso和OkHttp都是square旗下的开源项目,因此两者能给非常友好的兼容起来,而且Picasso本身的本地缓存也是依赖于OkHttp(或者HttpURLConnection)实现的。
但由于2.5.2版本里面,Picasso指定的OkHttp路径名是“com.squareup.okhttp.OkHttpClient”,而OkHttp3已经将包名改成了“okhttp3.OkHttpClient”,因此默认情况下它是不兼容OkHttp3的。所以如果你项目引入的OkHttp3以后的版本,又需要让Picasso用上OkHttp3做下载和缓存,则需要自定义Picasso的Downloader。如上面PicassoImageLoaderStrategy的代码。

Picasso picasso = new Picasso.Builder(ctx.getApplicationContext()).downloader(new OkHttp3Downloader(okHttpClient)).build();

这里直接用了JW大神写好的OkHttp3Downloader,下面是git地址。

JW大神的OkHttp3Downloader

2、加载图片墙,2张大图下来就卡飞了

Picasso加载图片的高清晰,在于它加载的都是原图,无论你的ImageView有多小,它加载到内存以及显示到ImageView中的都是你指定的原图。因此,在图片墙应用中,如果直接简单的loadImage,2张5M的图片下来,就足以将屏幕卡爆。
网上通用的解决方案,是加载大图不使用内存缓存,也就是使用我们上面的PicassoImageConfig.STRATEGY_NO_MEMORY策略。
但实际下来发现还是会非常卡,经过多次测试猜测应该是大图加载到多个ImageView中导致的,因此在后面又加入了fit()方法,自适应ImageView的大小进行加载。从而解决上面的图片墙加载大图问题。

3、使用fit()、resize()导致的加载失败

使用fit()解决了加载大图的问题,但同时又引入了另一个加载失败的问题。经过层层调试,发现是在2.5.2版本中BitmapHunter的问题,报出了Exception: java.io.IOException: Cannot reset ,俨然是Picasso的一个大Bug啊…
到github上面看了下Issues,发现JakeWharton大神也已经知道了,已经在snapshot版本里面改好了,只是一直没时间合到realse版本里面…ORZ…. 被迫无奈,只能在Gradle里面将picasso切到snapshot版本,从而问题暂时解决。
这里注明下:2.6.0版本已经把OkHttp3Downloader加了进去,所以已经兼容好了OkHttp3。

repositories {
maven { url ‘https://oss.sonatype.org/content/repositories/snapshots/’ }
}
……
compile ‘com.squareup.picasso:picasso:2.6.0-SNAPSHOT’

总结

以上就是对Picasso封装的过程,鉴于Glide的使用方法和Picasso的使用是完全类似的,在这里就不做展开,在后续的完整代码提交中,会带有Gilde的封装。如果想要自己实现,只需要参照PicassoImageConfig和PicassoImageLoaderStrategy两个类写就行了,其他就没什么改动。
简单回顾一下,我们思考一个封装的过程,关键点是从目的出发。对于图片加载库,我们的目的就是——”使用简单、替换方便”,结合Picasso和Glide的实际,使用简单,我们想到了建造者模式;替换方便,我们想到了策略模式,紧接从设计模式出发,逐步实现我们整个打造的过程。
说到这里,很多人会觉得,像图片加载库这些在项目中十分通用的第三方库,一些参数、方法都十分多,如果重新封装一层,显示累赘,没什么必要。其实这就取决于你对整个项目架构的部署了,直接使用当然也是可以的,只是从软件设计的角度,这显然是会对整个项目带来非常多的外部侵入,第三方库的代码会散落在项目的个个地方,耦合度过高,违背了依赖倒置原则。
当项目迁移或者越来越庞大的时候,这些代码会变得十分难管理。相反如果做了封装处理,我们代码依然是我们的代码,第三方的代码也还是第三方的代码,层次相当的清晰。
我们举一反三,延伸下去。如果我们对网络请求、日志管理、下拉刷新等等这些引用带第三方库的地方进行封装,打造成自己的一整套架构,形成模块化的管理,从整个项目架构来看,就更加的清晰了。
这也是我逐步在思考和部署的方向,包括上面提到的RxJava+Retrofit+OkHttp的网络请求框架,UltraPTR+RecyclerView的下拉刷新等等,都将往模块化、层级化去构建。这几块都会在后面逐步发布上来,同时我也会将完整代码和Demo发布带GitHub上,慢慢填充。在自我提高和总结的同时,也希望能更多的给也在做着同样事情的朋友们一些参考。

打造自己的图片加载缓存库(Picasso OR Glide)相关推荐

  1. Android图片加载缓存库3

    Universal-Image-Loader 基本介绍及使用 大家平时做项目的时候,或多或少都会接触到异步加载图片,或者大量加载图片的问题,而加载图片时候经常会遇到各种问题,如oom,图片加载混乱等. ...

  2. Android 强大的图片加载缓存— Glide

    在图片加载库烂大街的今天,选择一个适合自己使用的图片加载库已经成为了每一个Android开发者的必经之路.现在市面上知名的图片加载库有UIL,Picasso,Volley ImageLoader,Fr ...

  3. Android UIL图片加载缓存源码分析-内存缓存

    本篇文章我们来分析一下著名图片加载库Android-Universal-Image-Loader的图片缓存源码. 源码环境 版本:V1.9.5 GitHub链接地址:https://github.co ...

  4. Android 框架练成 教你打造高效的图片加载框架

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/41874561 ,本文出自: [张鸿洋的博客] 1.概述 优秀的图片加载框架不要 ...

  5. Android图片加载框架:玩转Glide的回调与监听

    回调的源码实现 作为一名Glide老手,相信大家对于Glide的基本用法已经非常熟练了.我们都知道,使用Glide在界面上加载并展示一张图片只需要一行代码: Glide.with(this).load ...

  6. Android中UIL框架特点,聊聊Android优秀的图片加载缓存的开源框架?UIL、Glide、Picasso...

    今天总结下有关Android的图片开源框架UIL.Glide.Picasso.当然不止这些还有okhttp.xutlis.afinal.andbase.volley等等,今天主要是对于Glide使用进 ...

  7. Android Glide图片加载-缓存机制(内存缓存和磁盘缓存)

    前言 glide的缓存机制.Glide的缓存设计是非常的先进的,考虑的场景也很周全.Glide 的缓存分为两种,一是内存缓存,另一个是硬盘缓存. 这两种缓存的作用各不相同,内存缓存的主要作用是防止应用 ...

  8. android:强大的图片下载和缓存库Picasso

    picasso是Square公司开源的一个Android图形缓存库,地址http://square.github.io/picasso/,可以实现图片下载和缓存功能. picasso使用简单 需要再b ...

  9. Android图片加载库:最全面解析Glide用法

    目录 1. 简介 介绍:Glide,是Android中一个图片加载开源库 Google的开源项目 主要作用:实现图片加载 2. 功能特点 2.1 功能列表 从上面可以看出,Glide不仅实现了图片异步 ...

最新文章

  1. 表征学习、图神经网络、可解释的 AI,ML 机器人七大研究进展一览
  2. Spring 报It is indirectly referenced。。
  3. 关于ASP.NET 中站点地图sitemap 的使用【转xugang】
  4. ***正传——著名网络安全人士郭鑫成长经历
  5. 多项式求和x+(x^2)/2!+(x^3)/3!+...
  6. Win7下拖拽文件的bug
  7. 将传统 WPF 程序迁移到 DotNetCore 3.0
  8. 【渝粤教育】21秋期末考试马克思主义基本原理概论(A)10882k1 (2)
  9. vc++6.0 模拟鼠标点击代码 木马程序的编写 VC 模拟键盘输入
  10. oracle 建表字段设置,Oracle创建表、删除表、修改表(添加字段、修改字段、删除字段)语句总结...
  11. JavaCC报错:ERROR: Second call to constructor of static parser
  12. WCF 第十三章 可编程站点 使用AJAX和JSON进行网页编程
  13. Spark Runtime概述
  14. 基于python和酷Q的QQ机器人开发实践(1)
  15. scsi设备扫描特征分析
  16. 靠自己。linux manul手册入门
  17. 暑期作息时间表模板_2015中学生暑假作息时间表模板
  18. vue集成wangeditor3.0版本 解决光标乱跳等问题
  19. 中国地产商寻找下一个春天 1
  20. 常用的基础英文字体推荐

热门文章

  1. 桂林理工大学计算机网络技术专业学费,2020年桂林理工大学学费是多少
  2. 学优计算机怎么关机,Windows7电脑关机速度慢的解决办法
  3. c 语言编程软件打开文件,VC WinExec打开指定程序或者文件的方法
  4. havok 动画压缩
  5. Shell编程练习 - 系统一键巡检脚本
  6. PhotoView的使用
  7. java的多态性_java中多态性什么意思?
  8. 计算机怎样放音乐,电脑放歌说静声怎么设计
  9. Pinnacle Studio Ultimate v25.0.1.211 品尼高视频剪辑软件中文版
  10. android broadcast--有道笔记整理