这么久以来虽然经常用到一些图库,但是自己从来没有真正整理过我们使用过的这些东西有什么不同点,我们为什么要选择这个图库作为项目的加载图片的框架(由于本人资历尚浅,经验也不丰富,所以从没有试过自己做一个图片加载库),今天突发奇想,也算是让自己思路更清晰一点,来写一些关于这些图库的异同之处.
作为一个应用丰富的App,图片加载是必不可少的,甚至对于图片的质量和数量都是要求很高的,然而对于图片的加载是一个耗时操作,在UI线程为了避免ANR异常不能做耗时操作,是一个Android开发人员的基本常识,所以异步加载图片是必然的,这就让我们面临了一个怎么选择图片加载框架的问题?市面上比较成熟的图库有: Universal-Image-Loader(UIL)、Picasso、Glide、Fresco。其实任何一个图片加载框架都可以当做一个普通的下载文件流程,一般都包含这么几个步骤:初始化配置->构造请求->执行请求->处理请求结果。
1.Universal-Image-Loader(UIL)
这是一个很早就出现的图片加载框架了,作者是nostra13,UIL使用很方便,而且自带多种缓存策略,如最大尺寸先删除、时间最久删除 等,使用它,基本上不需要考虑太多的问题。另外,UIL还支持图片下载进度的监听,如果你有特殊需求,则可以在 图片开始下载前、刚开始下载等各个时间段来做一些额外的事情,非常方便。而且UIL可以在View滚动的过程中暂停图片的加载,有利于提升界面的流畅度。但由于作者在两年前宣布不再维护这个项目了,也就是说这个项目将不再更新,所以如果你将开发一个新项目,本人不推荐你使用此框架,因为市面上还有其它跟它一样强大甚至更好的图库可以使用,但如果你现在的项目是一个老牌项目,那也没必要着急更换它,因为它还是很强大的,没有到跟不上时代的地步,到了真需要更换的时候再换也来得及
2.Picasso
这是一个来自于开源界名气很大的Square公司开发的,总体来看,它属于轻量级别的图片加载库,但它也有着一些自己的特色。比如,很特别的拥有统计功能,可以知道使用了多少内存、缓存命中如何,另外它本身没有什么缓存策略,而是依赖所用的网络库的缓存策略——其实就是依赖了OkHttp。Picasso使用起来也是比较简单的,不过对于新项目来说,也不是很推荐,原因就在于,Glide比它更优秀,而且使用起来几乎 是一样的……
缓存路径:
这里讲一下关于Picasso缓存路径:
data/data/your package name/cache/picasso-cache/(默认路径)
有时候根据需求,我们需要更改其缓存路径,这里我们分析一下啊,Picasso 底层其实是使用OkHttp去下载图片,同时在设置Picasso的时候,有一个.downloader(Downloader downloader)方法,我们可以传递进去一个OkHttpDownloader( OkHttpClient client).
Picasso picasso = new Picasso.Builder(Context)
.downloader(new OkHttpDownloader(client))
.build();
看到这里我们应该想到,如果给OkHttpClient设置Cache是不是就可以改变缓存路径呢?只需要给OkHttpClient设置.cache(new Cache(file, maxSize))就可以实现修改缓存路径了。代码就是:
File file = new File("缓存文件所在路径");
if (!file.exists()) {
file.mkdirs();
}
long maxSize = Runtime.getRuntime().maxMemory() / 8; //设置图片缓存大小为运行时缓存的八分之一
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(file, maxSize))
.build();
Picasso picasso = new Picasso.Builder(this)
.downloader(new OkHttpDownloader(client))
.build();
这里需要注意的就是:当把OkHttp升级到OkHttp3时,给downloader设置OkHttpDownloader()并不支持OkHttp3.如果想使用OkHttp3,需要使用 OkHttp3Downloader来替代OkHttpDownloader,OkHttp3Downloader库是jakewharton专为为了能使用OkHttp3作为下载器而写的,使用姿势也很简单:在Module dependencies添加依赖:
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
然后上面的代码改成:
Picasso picasso = new Picasso.Builder(this)
.downloader(new OkHttp3Downloader(client)) //注意此处替换为 OkHttp3Downloader
.build();
关于Picasso实例化对象(两种方式)
- Picasso.with(context)
此方法提供默认方式,生成单例的Picasso对象. - new Picasso.Builder(context).build()
此方式提供自定义线程池、缓存、下载器等方法.
关于Picasso源码简单分析
- Picasso.with(context),在源码中我们很容易就可以看出这是在构造一个单例的对象,而且是通过new Builder(context).build()建造者模式构建.
1.通过Builder(context)往下看就会发现 this.context = context.getApplicationContext();得到的是全局的上下文,这是为了让Picasso下载器同步应用的生命周期的,然后我们的重点就可以放在build()上了.
2.在build()方法里面我们会发现有六个方法:
(源代码)
(第一个方法) if (downloader == null) {
downloader = Utils.createDefaultDownloader(context); //创建一个默认的下载器.
1.其中Downloader是一个用于从网络上加载图片的接口,需要实现load和shutdown方法。load用于加载图片,shutdown用于关闭一些操作.
2.Picasso的线程池是经过优化过的,可以根据当前设备网络状况设置其ThreadCount。
在网络良好的条件下,线程池持有较多线程,保证下载速度够快。在网络较差的条件下(2G网络等),线程池减少持有线程,保证带宽不会被多个连接阻塞。
}
(第二个方法) if (cache == null) {
cache = new LruCache(context); //初始化缓存,创建内存缓存
}
(第三个方法) if (service == null) {
service = new PicassoExecutorService(); //初始化线程池
1.默认启动了3个核心线程,采用了PriorityBlockingQueue优先级阻塞队列,也就是说Picasso支持优先级调度.(对网络状态进行线程的优化)
}
(第四个方法) if (transformer == null) {
transformer = RequestTransformer.IDENTITY; // 初始化转换器,请求的前置处理,在请求发出去之前执行,类似于拦截器
1.默认RequestTransformer.IDENTITY表示不作处理.
}
(第五个方法) Stats stats = new Stats(cache); //状态控制类,统计一些状态信息,用来发送各种消息,例如查找图片缓存的命中率,下载是否完成等
(第六个方法) Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); //最后创建调度器,用来分发任务
1.从build方法中可以看出,大多数参数直接传进了这个类的构造方法中,可见这个类不容小觑。
Dispatcher主要是来调度任务的,比如提交任务,取消任务,暂停加载,恢复加载,重试,加载完成,监听网络等等。
同样,里面也用了一个HandlerThread和Handler来分发任务。通过一系列的dispatchXXX,由Handler发送消息,Handler接收消息后,通过performXXX来调度任务。
- 关于Picasso中任务调度器Dispatcher的简单分析:
翻看源码我们会看到其Dispatcher类的构造方法有六个参数,这里主要分析其中两个,一个ExecutorService,另一个就是Handler,
第一,我们首先讲一下Handler
public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
super(looper);
this.dispatcher = dispatcher;
}
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: { //提交请求
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
case REQUEST_CANCEL: { //取消请求
Action action = (Action) msg.obj;
dispatcher.performCancel(action);
break;
}
case TAG_PAUSE: { //暂停请求
Object tag = msg.obj;
dispatcher.performPauseTag(tag);
break;
}
case TAG_RESUME: { //恢复请求
Object tag = msg.obj;
dispatcher.performResumeTag(tag);
break;
}
case HUNTER_COMPLETE: { //捕获完成
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
case HUNTER_RETRY: { //重试
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performRetry(hunter);
break;
}
case HUNTER_DECODE_FAILED: { //解码失败
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performError(hunter, false);
break;
}
}
}
第二,关于ExecutorService
if (service instanceof PicassoExecutorService) {
((PicassoExecutorService) service).adjustThreadCount(info);
}
//在adjustThreadCount(info)方法里面就是下面这段代码,很明可以看出,这段代码是根据网络状态信息info来决定线程池个数的,默认是3条线程
if (info == null || !info.isConnectedOrConnecting()) {
setThreadCount(DEFAULT_THREAD_COUNT);
return;
}
switch (info.getType()) {
case ConnectivityManager.TYPE_WIFI: //wife状态下
case ConnectivityManager.TYPE_WIMAX: //802·16无线城域网,类似于wife
case ConnectivityManager.TYPE_ETHERNET: //以太网数据连接
setThreadCount(4);
break;
case ConnectivityManager.TYPE_MOBILE:
switch (info.getSubtype()) {
case TelephonyManager.NETWORK_TYPE_LTE: // 4G状态下
case TelephonyManager.NETWORK_TYPE_HSPAP:
case TelephonyManager.NETWORK_TYPE_EHRPD:
setThreadCount(3);
break;
case TelephonyManager.NETWORK_TYPE_UMTS: // 3G状态下
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
setThreadCount(2);
break;
case TelephonyManager.NETWORK_TYPE_GPRS: // 2G状态下
case TelephonyManager.NETWORK_TYPE_EDGE:
setThreadCount(1);
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT);
}
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT); //默认状态下是3条
- 接下来我们讲一下Picasso中的load("image src url")方法,源码中load()方法有四个
(源代码如下)
//通过uri参数获得RequestCreator对象
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
//通过请求路径path,获取其uri参数获得RequestCreator对象
public RequestCreator load(String path) {
if (path == null) {
return new RequestCreator(this, null, 0);
}
if (path.trim().length() == 0) {
throw new IllegalArgumentException("Path must not be empty.");
}
return load(Uri.parse(path));
}
//通过文件file获得其uri参数,从而获取RequestCreator对象
public RequestCreator load(File file) {
if (file == null) {
return new RequestCreator(this, null, 0);
}
return load(Uri.fromFile(file));
}
//通过指定的请求id来获取RequestCreator对象
public RequestCreator load(int resourceId) {
if (resourceId == 0) {
throw new IllegalArgumentException("Resource ID must not be zero.");
}
return new RequestCreator(this, null, resourceId);
}
(从上面的四个方法可以看出,其实又可以分为两类:uri和resourceId。uri又分为file和net。)
从上面的代码可以看出load()方法最终都是需要得到RequestCreator对象,那RequestCreator又有什么作用呢?
RequestCreator是用来配置加载参数的。RequestCreator有两个功能
- 配置加载参数。
包括placeHolder与error图片,加载图片的大小、旋转、居中等属性。 - 执行加载。
通过调用into(object)方法进行加载。
- 说完load()方法,接下来得说说into()方法了
into()可以说是Picasso中比较复杂的方法,方法有五个,方法体也比较长,代码我这里就不贴了,大家可以自行翻看,这个方法在RequestCreator里面
通过源码分析,其逻辑还是比较清晰的,这里总结一下:
- into会检查当前是否是在主线程上执行。
long started = System.nanoTime();
checkMain();
- 如果我们没有提供一个图片资源并且有设置placeholder,那么就会把我们设置的placeholder显示出来,并中断执行。(下面的代码)
Drawable drawable =
placeholderResId != 0 ? picasso.context.getResources().getDrawable(placeholderResId)
: placeholderDrawable;
if (!data.hasImage()) {
picasso.cancelRequest(target);
target.onPrepareLoad(drawable);
return;
}
- defered属性我们一般情况下不需要关注,只有当我们调用了RequestCreator的fit方法时defered才为true,但我们几乎不会这样做。
if (deferred) {
throw new IllegalStateException("Fit cannot be used with get.");
}
- 接下来就是创建了一个Request对象,我们在前面做得一些设置都会被封装到这个Request对象里面。
Request finalData = createRequest(started);
String key = createKey(finalData, new StringBuilder());
- 检查我们要显示的图片是否可以直接在缓存中获取,如果有就直接显示出来好了。
if (!skipMemoryCache) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
picasso.cancelRequest(target);
target.onBitmapLoaded(bitmap, MEMORY);
return;
}
}
- 缓存没命中,那就只能费点事把源图片down下来了。这个过程是异步的,并且通过一个Action来完成请求前后的衔接工作。
Action action =
new TargetAction(picasso, target, request, skipMemoryCache, errorResId, errorDrawable,
requestKey);
picasso.enqueueAndSubmit(action); //异步提交请求action
3.Glide
Google官方推荐图库,在许多Android的原生应用中都采用了Glide来加载图片。其实Glide与Picasso的使用姿势是惊人的相似的
Picasso.with(context).load("image src url").into(ImageView);
Glide.with(context).load("image src url").into(ImageView);(当然这只是它们常用的)
从某种程度上说,Glide可以看作是Picasso的增强版,所以它有着自己独特的优势,Glide不仅支持常见的jpg和png格式,还能显示gif动画,甚至是视频,或者说它已经不仅仅是一个普通的图片加载库了,而是一个多媒体库。另外一个优势是,Glide在内存方面的表现相当出色,首先它的图片默认格式是RGB565,要比Picasso默认的ARGB8888节省更多内存,而且它缓存的不是原始图片,而是缓存了图片的实际大小——比如加载的图片是 19201080的大小,而在你的App中,显示该图片的ImageView控件大小只有1280720,那么Glide就会很聪明的自动缓存 1280*720大小的图片。
关于Glide的源码简单解析
在Glide中我们经常用的一种姿势就是:
Glide.with(context).load("image src url").into(ImageView);
那么这里解析也是从这段代码开始
1.关于with(context)方法
在源码中这个方法是静态的,重载方法有五个,
//第一个方法传入一个上下文,根据上下文的所属生命周期来确定需要获取对象的生命周期
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
//第二个方法传入一个activity
public static RequestManager with(Activity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}
//第三个方法传入一个FragmentActivity
public static RequestManager with(FragmentActivity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}
//第四个方法传入一个app.Fragment
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static RequestManager with(android.app.Fragment fragment) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(fragment);
}
//第五个方法传入一个V4兼容包下的Fragment
public static RequestManager with(Fragment fragment) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(fragment);
}
这几个方法不长,逻辑也很清晰,都是通过一个RequestManagerRetriever的静态get()方法得到一个RequestManagerRetriever对象,其实这个静态get()方法就是一个单例的具体实现,然后再调用RequestManagerRetriever的实例get()方法,去获取RequestManager对象。这里需要注意的是实例get()方法中传入的参数类型,不同的参数类型对应不同的生命周期,下面代码就是具体的实现
(源代码)
private RequestManager getApplicationManager(Context context) {
// Either an application context or we're on a background thread.
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
// Normally pause/resume is taken care of by the fragment we add to the fragment or activity.
// However, in this case since the manager attached to the application will not receive lifecycle
// events, we must force the manager to start resumed using ApplicationLifecycle.
applicationManager = new RequestManager(context.getApplicationContext(),
new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
}
}
}
return applicationManager;
}
//第一种,传入全局的上下文,根据不同类型的上下文执行不同的方法
public RequestManager get(Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
//第二种,传入一个FragmentActivity,根据情况的不同调用的方法也不同,这里需要注意一下if (Util.isOnBackgroundThread())这种情形表示在子线程执行Glide加载图片,最终会执行最上面那个方法
public RequestManager get(FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(activity, fm);
}
}
//第三种,传入一个Fragment,也是根据情况的不同调用的方法也不同,注意 if (Util.isOnBackgroundThread())这种情形表示在子线程执行Glide加载图片,最终也会执行最上面的方法
public RequestManager get(Fragment fragment) {
if (fragment.getActivity() == null) {
throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
}
if (Util.isOnBackgroundThread()) {
return get(fragment.getActivity().getApplicationContext());
} else {
FragmentManager fm = fragment.getChildFragmentManager();
return supportFragmentGet(fragment.getActivity(), fm);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
RequestManagerFragment current = getRequestManagerFragment(fm); //获取隐藏的app包下的Fragment
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
RequestManager supportFragmentGet(Context context, FragmentManager fm) {
SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm); //获取隐藏的V4包下的Fragment
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
其实通过上面的代码看下来,逻辑上还是比较清晰的,RequestManagerRetriever类中看似有很多个get()方法的重载,什么Context参数,Activity参数,Fragment参数等等,实际上只有两种情况而已,即传入Application类型的参数,和传入非Application类型的参数。
- 先看传入Application参数的情况。如果在Glide.with()方法中传入的是一个Application对象,那么这里就会调用带有Context参数的get()方法重载,然后会调用getApplicationManager()方法来获取一个RequestManager对象。Application对象的生命周期即应用程序的生命周期,因此Glide并不需要做什么特殊的处理,它自动就是和应用程序的生命周期是同步的,如果应用程序关闭的话,Glide的加载也会同时终止。
- 再看传入非Application参数的情况。不管你在Glide.with()方法中传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终的流程都是一样的,那就是会向当前的Activity当中添加一个隐藏的Fragment。具体添加的逻辑是在上述代码都有注释说明,分别对应的app包和v4包下的两种Fragment的情况。那么这里为什么要添加一个隐藏的Fragment呢?因为Glide需要知道加载的生命周期。比如说:如果你在某个Activity上正在加载着一张图片,结果图片还没加载出来,Activity就被用户关掉了,但是如果图片请求还在继续,当请求的数据回来之后没有界面可以进行渲染,这就会造成内存泄漏,所以这种情况下肯定是需要取消Glide的网络的请求的。可是Glide并没有办法知道Activity的生命周期,于是Glide就使用了添加隐藏Fragment的技巧,因为Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止网络请求了。这里需要注意的一点就是:如果我们是在非主线程当中使用的Glide,那么不管你是传入的Activity还是Fragment,都会被强制当成Application来处理。
总体来说,第一个with()方法其实就是为了得到一个RequestManager对象而已,然后Glide会根据我们传入with()方法的参数来确定图片加载的生命周期,接下来我们就分析一下load("image src url")这个方法
2.关于load("image src url")方法
通过上面的with(context)方法返回的都是RequestManager对象,那么load()方法肯定在RequestManager这个类里面,我们知道Glide是支持图片URL字符串、图片本地路径等等加载形式的,load重载的方法有很多,我们常用的一般都是load(String string),关于URL字符串加载形式,下面我们分析一下这种情形:
(源代码:)
/**
* Returns a request builder to load the given {@link java.lang.String}.
* signature.
*
* @see #fromString()
* @see #load(Object)
*
* @param string A file path, or a uri or url handled by {@link com.bumptech.glide.load.model.UriLoader}.
*/
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>) fromString().load(string);
}
/ * @see #from(Class)
* @see #load(String)
*/
public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}
private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass){
ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
Glide.buildFileDescriptorModelLoader(modelClass, context);
if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
+ " which there is a registered ModelLoader, if you are using a custom model, you must first call"
+ " Glide#register with a ModelLoaderFactory for your custom model class");
}
//传入StreamStringLoader对象,获取DrawableTypeRequest对象
return optionsApplier.apply(
new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}
RequestManager类的代码是非常多的,关于load(String string)简化之后比较重要的方法就只剩下上述代码中的这三个方法。
先来看load()方法,这个方法中的逻辑是非常简单的,只有一行代码,就是先调用了fromString()方法,再调用load()方法,然后把传入的图片URL地址传进去。而fromString()方法也极为简单,就是调用了loadGeneric()方法,并且指定参数为String.class.
执行loadGeneric()方法时,分别调用了Glide.buildStreamModelLoader()方法和Glide.buildFileDescriptorModelLoader()方法来获得ModelLoader对象。ModelLoader对象是用于加载图片的,而我们给load()方法传入不同类型的参数,这里也会得到不同的ModelLoader对象。由于我们刚才传入的参数是String.class,因此最终得到的是StreamStringLoader对象,它是实现了ModelLoader接口的。
最后可以看到,loadGeneric()方法是要返回一个DrawableTypeRequest对象的,因此在loadGeneric()方法的最后又new了一个DrawableTypeRequest对象,然后把刚才获得的ModelLoader对象(StreamStringLoader对象),还有其他的一些参数传进去。
这里就可以看到如果得到一个DrawableTypeRequest对象,那这里面肯定是有所作为的(源码如下:)
/**
* Attempts to always load the resource as a {@link android.graphics.Bitmap}, even if it could actually be animated.
*
* @return A new request builder for loading a {@link android.graphics.Bitmap}
*/
public BitmapTypeRequest<ModelType> asBitmap() {
return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader,
fileDescriptorModelLoader, optionsApplier));
}
public GifTypeRequest<ModelType> asGif() {
return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier));
}
这里就可以看到我们经常使用的 asBitmap() 和 asGif(),这两个方法分别是用于强制指定加载静态图片和动态图片,而从源码中可以看出,它们分别又创建了一个BitmapTypeRequest和GifTypeRequest,如果没有进行强制指定的话,那默认就是使用DrawableTypeRequest。
上面的fromString()方法会返回一个DrawableTypeRequest对象,接下来会调用这个对象的load()方法,把图片的URL地址传进去。通过对DrawableTypeRequest类的查看,没有找到load()方法,所以在DrawableTypeRequest的父类DrawableRequestBuilder中可以看到load()方法.
@Override
public DrawableRequestBuilder<ModelType> load(ModelType model) {
super.load(model);
return this;
}
/**
* {@inheritDoc}
*
* <p>
* Note - If no transformation is set for this load, a default transformation will be applied based on the
* value returned from {@link android.widget.ImageView#getScaleType()}. To avoid this default transformation,
* use {@link #dontTransform()}.
* </p>
*
* @param view {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public Target<GlideDrawable> into(ImageView view) {
return super.into(view);
}
DrawableRequestBuilder中有很多个方法,这些方法其实就是Glide绝大多数的API了。通过源码,我们可以看到load()和into()这两个方法了,所以我们总结一下:最终load()方法返回的其实就是一个DrawableTypeRequest对象。那么接下来分析into()方法中的逻辑。
3.关于into()方法的分析
从上面的代码我们可以看到DrawableRequestBuilder类里面的into()方法只有
return super.into(view);
这说明into()真正的实现逻辑是在DrawableRequestBuilder的父类GenericRequestBuilder,所以into()方法需要在这个类进行分析
/**
* Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into the view, and frees
* any resources Glide may have previously loaded into the view so they may be reused.
*
* @see Glide#clear(android.view.View)
*
* @param view The view to cancel previous loads for and load the new resource into.
* @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}.
*/
public Target<TranscodeType> into(ImageView view) {
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
return into(glide.buildImageViewTarget(view, transcodeClass));
}
在上面代码中into(ImageView view)方法里面最后一行代码先是调用glide.buildImageViewTarget()方法,这个方法会构建出一个Target对象,Target对象则是用来最终展示图片用的,如果我们跟进去的话会看到如下代码:
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
这里可以看到会通过imageViewTargetFactory调用buildTarget(imageView, transcodedClass)这个方法,如果继续查看源码,会发现在buildTarget()方法中会根据传入的class参数来构建不同的Target对象。这个class参数其实基本上只有两种情况,如果你在使用Glide加载图片的时候调用了asBitmap()方法,那么这里就会构建出BitmapImageViewTarget对象,否则的话构建的都是GlideDrawableImageViewTarget对象。
这里glide.buildImageViewTarget(view, transcodeClass),我们得到一个GlideDrawableImageViewTarget对象,然后回到
return into(glide.buildImageViewTarget(view, transcodeClass));
我们可以看到into(GlideDrawableImageViewTarget对象)的源码
/**
* Set the target the resource will be loaded into.
*
* @see Glide#clear(com.bumptech.glide.request.target.Target)
*
* @param target The target to load the resource into.
* @return The given target.
*/
public <Y extends Target<TranscodeType>> Y into(Y target) {
Util.assertMainThread();
if (target == null) {
throw new IllegalArgumentException("You must pass in a non null Target");
}
if (!isModelSet) {
throw new IllegalArgumentException("You must first set a model (try #load())");
}
Request previous = target.getRequest();
if (previous != null) {
previous.clear();
requestTracker.removeRequest(previous);
previous.recycle();
}
Request request = buildRequest(target); //调用buildRequest()方法构建出了一个Request对象
target.setRequest(request);
lifecycle.addListener(target);
requestTracker.runRequest(request); //执行Request对象
return target;
}
上面代码构建出来的Request对象是用来发出加载图片请求的,它是Glide中非常关键的一个组件.这就是我们经常用的with(),load(),into()最表面的意思,里面还有很多很多内容没有写出来,我也是参考郭霖大神的文章以及自己的拙见才写这么点东西的,也算是做了一次笔记吧!
关于Glide优化
- 配置使用Volley和OkHttp来加载图片
Volley和OkHttp是项目中使用最广泛的两个网络库,也是两个相对来说速度比较快的,Glide默认使用的是HttpUrlConnection的方式请求网络,其效率是比较低的,可以使用Volley或者OkHttp(和项目使用的网络请求库一致)作为Glide的网络请求方式.
Gradle配置
//使用volley
dependencies {
compile 'com.github.bumptech.glide:volley-integration:1.4.0@aar'
//compile 'com.mcxiaoke.volley:library:1.0.8'
}
//使用okhttp
dependencies {
compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
//compile 'com.squareup.okhttp:okhttp:2.2.0'
}
当在Library库中使用aar的时候,Library中的GlideModule会自动合并 到主项目中mainfest文件中,当使用jar包导入时,需要手动去合并Library合并GlideModule或者使用自己配置的GlideModule。
Maven配置
//使用volley
<dependency>
<groupId>com.github.bumptech.glide</groupId>
<artifactId>volley-integration</artifactId>
<version>1.4.0</version>
<type>aar</type>
</dependency>
<dependency>
<groupId>com.mcxiaoke.volley</groupId>
<artifactId>library</artifactId>
<version>1.0.8</version>
<type>aar</type>
</dependency>
//使用okhttp
<dependency>
<groupId>com.github.bumptech.glide</groupId>
<artifactId>okhttp-integration</artifactId>
<version>1.4.0</version>
<type>aar</type>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.2.0</version>
<type>jar</type>
</dependency>
jar的形式引入
如果通过Maven,Ant 或者其它系统工具来构建的话,是不支持manifest 文件合并的,你必须手动在AndroidManifest.xml添加GlideModule metadata 属性。
//使用volley
<meta-data
android:name="com.bumptech.glide.integration.volley.VolleyGlideModule"
android:value="GlideModule" />
//使用okhttp
<meta-data
android:name="com.bumptech.glide.integration.okhttp.OkHttpGlideModule"
android:value="GlideModule" />
//添加混淆配置
-keep class com.bumptech.glide.integration.volley.VolleyGlideModule
-keep class com.bumptech.glide.integration.okhttp.OkHttpGlideModule
其实如果我们想使用OkHttp3作为Glide网络请求方式,可以自行查看相关文档.
4.Fresco
这个号称是Android平台上目前最为强大的图片加载库,由Facebook公司开发。与Glide一样,Fresco也是支持gif动画显示,而且在内存方面的表现更加优秀。由于将图片放在Ashmem(匿名共享内存)中,大大降低了App的内存占用(因为Ashmem没有被统计到App的内存使用里),再加上各种级别优化,使得Fresco基本上告别了OOM,而且Fresco的图片直接显示为ARGB8888这种最高质量级别,即使是在这种高质量的情况下依然保证了比其他库更少的内存占用,这就是Fresco最吸引人的地方。而且类似于进度监听、缓存策略等,也是非常完善的,总之作为一个图片加载库,Fresco在功能和性能方面已经趋于完美了。
Picasso,Glide,Fresco的区别
Picasso :和Square的网络库一起能发挥最大作用,因为Picasso可以选择将网络请求的缓存部分交给了okhttp实现。使用4.0+系统上的HTTP缓存来代替磁盘缓存.
Picasso 底层是使用OkHttp去下载图片,所以Picasso底层网络协议为Http.Glide:模仿了Picasso的API,而且在它的基础上加了很多的扩展(比如gif等支持),Glide默认的Bitmap格式是RGB_565,比Picasso默认的ARGB_8888格式的内存开销要小一半;Picasso缓存的是全尺寸的(只缓存一种),而Glide缓存的是跟ImageView尺寸相同的(即5656和128128是两个缓存) 。
Glide 底层默认使用的是HttpUrlConnection的方式请求网络,所以Glide的底层网络协议也为Http.-
Fresco:最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区,这个区域没有被统计到App的内存使用里)。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。为什么说是5.0以下,因为在5.0以后系统默认就是存储在Ashmem区了。
Image pipeline 默认使用HttpURLConnection。应用可以根据自己需求使用不同的网络库。Fresco的Image Pipeline负责图片的获取和管理。图片可以来自远程服务器,本地文件,或者Content Provider,本地资源。压缩后的文件缓存在本地存储中,Bitmap数据缓存在内存中.功能性 Fresco > Glide > Picasso
包大小 Fresco > Glide > Picasso
主要功能:
共有的功能:根据content生命周期进行图片加载或暂停和恢复,缓存图片到本地。
加载图片格式及大小:
- Picasso:下载全尺寸图片,load全尺寸图片到imageview上,图片使用ARGB-8888格式。
- Glide:包含Picasso功能,默认下载不同图片至本地,load 对应imageview尺寸的图片,图片使用ARGB-565格式。
可加载gif、缩略图、视频静态图片、转换字节数组、显示动画。 - Fresco:结合Picasso、Glide优点,更适用于加载大量图片。另支持渐进式显示图片、WebP格式图片。
对图片转换
- Picasso: picasso-transformations,:结合picasso,支持将图片转换为其他形状后显示。
- Glide: glide-transformations:结合glide,支持将图片转换为其他形状后显示。
- Fresco: android-gpuimage:支持将图片变幻为各种滤镜效果。
总结:
- Picasso所能实现的功能,Glide都能做,无非是所需的设置不同。但是Picasso体积比起Glide小太多,如果项目中网络请求本身用的就是okhttp或者retrofit(本质还是okhttp),那么建议用Picasso,体积会小很多(Square全家桶的干活)。
- Glide的好处是大型的图片流,比如gif、Video,如果你们是做美拍、爱拍这种视频类应用,建议使用。
- Fresco在5.0以下的内存优化非常好,代价就是体积也非常的大,按体积算Fresco>Glide>Picasso不过在使用起来也有些不便(小建议:它只能用内置的一个ImageView来实现这些功能,用起来比较麻烦,我们通常是根据Fresco自己改改,直接使用他的Bitmap层).
该如何选择图片加载库?
如果你手中的项目比较老旧,而且代码量较大,你又没什么时间去大改,那么继续维持当前的选择是比较稳妥的办法。如果是新上马的项目,那么UIL由于不再维护、Picasso基本被Glide全方位超越,我推荐使用Glide或Fresco。如果你的App里,图片特别多,而且都是很大、质量很高的图 片,而且你不太在乎App的体积(可能性不大),那么Fresco就是很好的选择了,而Glide相比较Fresco,Glide要轻量一些,而且是Google官方推荐,所以在多数时候,会是开发者的首选。话说回来,如果你对这些图库都不满意,那可以自己写一个,如果可以的话!!