序言
在Android开发过程中,图片处理是必不可少的,图片处理可繁可简。目前有也出现了很多优秀的图片加载库,如Glide、Picasso、Fresco等。
今天我们主要学习Glide的源码,Glide功能极为强大,支持图片的缓存策略,图片变换...
为了写这篇文章,除了自己阅读源码之外,也看了很多相关文章,其中极为推荐郭霖大神的文章。
虽然已经有这么好的文章,但我觉得自己要研究这一块,也应该记录一下自己学习和理解的过程。
文章从以下几个主题展开介绍:
- Glide的用法
- Glide源码分析(主要流程)
- Glide缓存机制
- Glide图片变换
- 对比Glide、Picasso、Fresco
- 总结
Glide用法
本文基于Glide-4.7.1
版本介绍。
Glide的用法很简单,按照下面几个步骤即可:
- 添加Gradle依赖
api 'com.github.bumptech.glide:glide:4.7.1'
api 'com.github.bumptech.glide:compiler:4.7.1'
api 'com.github.bumptech.glide:okhttp3-integration:4.7.1'
- 使用Glide
override fun loadCircleImage(url: String?, imageView: ImageView) {
val myOptions: RequestOptions = RequestOptions().dontAnimate()
.transform(GlideCircleTransform(imageView.context))
.diskCacheStrategy(DiskCacheStrategy.ALL)
Glide.with(imageView.context)
.load(url)
.apply(myOptions)
.into(imageView)
}
这样就可以把一张图片显示到ImageView上,很简单吧。
Glide源码分析(主要流程)
从Glide用法中,我们知道,要显示一张图片,代码很简单,apply(myOptions)
是设置一些额外功能,去掉也没问题。
下面是最简洁的用法:
Glide.with(context).load(url).into(imageView)
接下来,我们针对这句代码进行分析,虽然看似很简单,只有三句代码,但背后的源码实现极为复杂。所以此次分析,我们只看主要流程,不关注过多细节,因为一旦陷入细节,可能就无法把握主体流程了。
思路也很简单,我们就分析三个方法:with方法、load方法、into方法。
1. Glide.with方法
我们进入Glide类,找到with方法:
@NonNull
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
@NonNull
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
@NonNull
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
@NonNull
public static RequestManager with(@NonNull Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
@SuppressWarnings("deprecation")
@Deprecated
@NonNull
public static RequestManager with(@NonNull android.app.Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
@NonNull
public static RequestManager with(@NonNull View view) {
return getRetriever(view.getContext()).get(view);
}
可以看到,with有很多重载方法,最后都返回一个RequestManager对象,也就是with方法会返回一个RequestManager对象。
这些with重载方法分别接收参数Context、Activity、FragmentActivity、Fragment、android.app.Fragment、View
。通过这些类型获取Context类型,然后进行下面操作:
- 调用getRetriever方法:得到一个RequestManagerRetriever对象
- 调用RequestManagerRetriever.get方法:得到RequestManager对象
接下来,分析这两个方法调用:
a. getRetriever方法
@NonNull
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
Preconditions.checkNotNull(
context,
"You cannot start a load on a not yet attached View or a Fragment where getActivity() "
+ "returns null (which usually occurs when getActivity() is called before the Fragment "
+ "is attached or after the Fragment is destroyed).");
return Glide.get(context).getRequestManagerRetriever();
}
看最后一句代码,有两个方法调用:Glide.get()
和Glide.getRequestManagerRetriever()
,我们先看Glide.get方法:
@NonNull
public static Glide get(@NonNull Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context);
}
}
}
return glide;
}
Glide是采用单例模式,get方法用来获取实例,如果实例为空,则通过checkAndInitializeGlide方法初始化Glide实例:
private static void checkAndInitializeGlide(@NonNull Context context) {
if (isInitializing) {
throw new IllegalStateException("You cannot call Glide.get() in registerComponents(),"
+ " use the provided Glide instance instead");
}
isInitializing = true;
initializeGlide(context);
isInitializing = false;
}
继续调用initializeGlide方法:
private static void initializeGlide(@NonNull Context context) {
initializeGlide(context, new GlideBuilder());
}
private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
Context applicationContext = context.getApplicationContext();
//省略代码
Glide glide = builder.build(applicationContext);
//省略代码
applicationContext.registerComponentCallbacks(glide);
Glide.glide = glide;
}
通过GlideBuilder构建Glide对象,我们就直接看它的build方法:
@NonNull
Glide build(@NonNull Context context) {
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}
if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}
if (animationExecutor == null) {
animationExecutor = GlideExecutor.newAnimationExecutor();
}
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
if (connectivityMonitorFactory == null) {
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
}
if (bitmapPool == null) {
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
if (arrayPool == null) {
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
}
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptions.lock(),
defaultTransitionOptions);
}
该方法中初始化了很多Glide成员变量,并且最后调用new Glide()构造了一个Glide对象。列举一下初始化的成员变量:
-
sourceExecutor
:获取图片请求线程池GlideExecutor -
diskCacheExecutor
:从硬盘缓存加载图片线程池GlideExecutor -
animationExecutor
:执行动画的线程池GlideExecutor -
memorySizeCalculator
:从名字理解,是内存计算器 - ...
-
memoryCache
:内存缓存策略,LruResourceCache -
diskCacheFactory
:硬盘缓存工厂 -
engine
:图片加载引擎 -
requestManagerRetriever
:用来创建RequestManager的工具
初始化了Glide单例后,调用getRequestManagerRetriever方法获取RequestManagerRetriever对象:
@NonNull
public RequestManagerRetriever getRequestManagerRetriever() {
return requestManagerRetriever;
}
此处的requestManagerRetriever就是在GlideBuilder.build方法中初始化的requestManagerRetriever。
b. 调用RequestManagerRetriever.get方法
找到RequestManagerRetriever中的get方法:
@NonNull
public RequestManager get(@NonNull 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);
}
@NonNull
public RequestManager get(@NonNull FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
@NonNull
public RequestManager get(@NonNull Fragment fragment) {
Preconditions.checkNotNull(fragment.getActivity(),
"You cannot start a load on a fragment before it is attached or after it is destroyed");
if (Util.isOnBackgroundThread()) {
return get(fragment.getActivity().getApplicationContext());
} else {
FragmentManager fm = fragment.getChildFragmentManager();
return supportFragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());
}
}
@SuppressWarnings("deprecation")
@NonNull
public RequestManager get(@NonNull Activity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
@SuppressWarnings("deprecation")
@NonNull
public RequestManager get(@NonNull View view) {
if (Util.isOnBackgroundThread()) {
return get(view.getContext().getApplicationContext());
}
Preconditions.checkNotNull(view);
Preconditions.checkNotNull(view.getContext(),
"Unable to obtain a request manager for a view without a Context");
Activity activity = findActivity(view.getContext());
if (activity == null) {
return get(view.getContext().getApplicationContext());
}
if (activity instanceof FragmentActivity) {
Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);
return fragment != null ? get(fragment) : get(activity);
}
android.app.Fragment fragment = findFragment(view, activity);
if (fragment == null) {
return get(activity);
}
return get(fragment);
}
我们发现,跟Glide.with方法类似,RequestManagerRetriever也有很多get重载方法,我们归类整理一下,实际可以分为两种类型:
- Application生命周期RequestManager:get接收的参数类型是Application的Context 或者 调用get方法时处于子线程
- Activity生命周期的RequestManager:get接收的参数是Activity、FragmentActivity、Fragment、View
先看怎么获取Application生命周期的RequestManager:
@NonNull
private RequestManager getApplicationManager(@NonNull Context context) {
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
Glide glide = Glide.get(context.getApplicationContext());
applicationManager =
factory.build(
glide,
new ApplicationLifecycle(),
new EmptyRequestManagerTreeNode(),
context.getApplicationContext());
}
}
}
return applicationManager;
}
在构建applicationManager时传入了一个ApplicationLifecycle对象,表明这个它的生命周期与应用生命周期相同。
接下来,分析如何获取Activity生命周期的RequestManager,这里涉及到两个方法:fragmentGet和supportFragmentGet
@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
@Deprecated
@NonNull
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
@NonNull
private RequestManager supportFragmentGet(
@NonNull Context context,
@NonNull FragmentManager fm,
@Nullable Fragment parentHint,
boolean isParentVisible) {
SupportRequestManagerFragment current =
getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
两个方法中都是传入current.getGlideLifecycle(),这个方法返回一个ActivityFragmentLifecycle对象,代表Activity的生命周期。
以上介绍的两种生命周期区别如下:
- Application生命周期:这种比较简单,即请求的生命周期与应用生命周期相同,当加载图片请求发出,只有退出应用,才能取消请求。
- Activity生命周期:请求的生命周期与Activity相同,当请求发出,只要结束掉Activity,就会取消请求。那么怎样保证请求与Activity的生命周期相同呢?Glide源码中用了一个小技巧,即在每个Activity中都添加一个隐藏的Fragment,而Fragment的生命周期与Activity的生命周期一致,所以监听Fragment的生命周期就可以知道Activity的生命周期。
到这里,Glide.with方法分析完毕,我们来做一个小结:Glide.with方法初始化了Glide单例,获取了一个RequestManager对象,并且根据传入参数类型的不同,RequestManager对请求生命周期的管理有两种方式,一种是与Application生命周期保持一致,另外一种是与Activity生命周期保持一致。
2. RequestManager.load方法
分析完Glide.with方法后,再看一下RequestManager.load方法:
@NonNull
@CheckResult
@Override
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
load也有很多重载方法,我们用的最多的是通过一个url地址加载图片,所以此处我们就分析入参是String类型的load方法。
这里有两个方法调用asDrawable()
和load(string)
,我们先看一下asDrawable:
@NonNull
@CheckResult
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
@NonNull
@CheckResult
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
创建了一个RequestBuilder对象,它的资源类型是Drawable。接着进入RequestBuilder类,分析load方法:
@NonNull
@CheckResult
@SuppressWarnings("unchecked")
@Override
public RequestBuilder<TranscodeType> load(@Nullable Object model) {
return loadGeneric(model);
}
@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
做的事情很简答,就是初始化了RequestBuilder的成员变量model
和isModelSet
。
到这里load方法分析完毕。小结一下:RequestManager.load方法创建并返回了一个RequestBuilder对象,该ReqeustBuilder指定的泛型是Drawable类型。
3. RequestBuilder.into方法
最后,我们来分析一下into方法:
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
RequestOptions requestOptions = this.requestOptions;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions = requestOptions.clone().optionalCenterCrop();
break;
case CENTER_INSIDE:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions = requestOptions.clone().optionalFitCenter();
break;
case FIT_XY:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case CENTER:
case MATRIX:
default:
// Do nothing.
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}
由于我们只关心主流程,所以直接看最后一句代码,通过buildImageViewTarget方法把ImageView转换成Target对象:
@NonNull
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
接着看ImageViewTargetFactory.buildTarget方法:
@NonNull
@SuppressWarnings("unchecked")
public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view,
@NonNull Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
由于Z是Drawable类型,所以把ImageView转换成了DrawableImageViewTarget对象(获取到图片后会通过该对象把图片设置给ImageView),接着就把这个Target作为参数调用into方法:
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
@NonNull RequestOptions options) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
options = options.autoClone();
//构建一个Request对象,从此处跟踪代码可知具体实现类是SingleRequest
Request request = buildRequest(target, targetListener, options);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
request.recycle();
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
//如果target包含Request对象,则清空并且终止Request对象的执行
requestManager.clear(target);
//把新的Request对象绑定到target
target.setRequest(request);
//执行新的图片处理请求
requestManager.track(target, request);
return target;
}
在没有Glide的时候,我们处理RecyclerView或ListView图片加载是一件比较麻烦的事情,由于ListView中的Item的复用机制,会导致网络图片加载错位或闪烁。我们解决这个问题的办法也很简单,就是给当前ImageView设置tag,这个tag可以是图片的URL,当从网络获取图片时判断这个ImageView中的tag是否是这个图片的URL,如果是则直接显示图片,如果不是则从网络中加载图片,并且重新设置tag。
有了Glide之后,我们处理ListView或RecyclerView中图片加载就很无脑了,根本不需要我们额外做操作,正常使用就行了。这其中的原理是Glide帮我们处理了这些判断,target.setRequest方法中,本质就是给ImageView设置tag,tag就是Request对象。而在设置新Request之前,会先判断target之前设置的Request和当前Request是否一致,如果一致则直接使用之前的Request。如果不一样,则通过requestManager.clear(target)清除tag,再通过target.setRequest重新设置新tag。
接下来,我们看是怎样从网络加载图片的,跟踪requestManager.track这句代码:
void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
继续看requestTracker.runReqeust方法:
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
这个方法用来执行Request请求,调用Request.begin,上面注释中提到Request的实现类是SingleRequest,所以我们看SingleRequest.begin方法:
@Override
public void begin() {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
if (status == Status.COMPLETE) {
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
//最后也会调用onSizeReady方法
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
首先,判断model是否为空,如果请求的url为空,那就不用其扭曲,直接调用onLoadFailed方法加载失败。
然后,调用onSizeReady方法,真正的资源加载就是从这个方法开始的。
根据status判断当前是否处于加载中间态和是否有占位图,如果有则设置加载过程中的占位图。
我们重点看onSizeReady方法:
@Override
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
loadStatus = engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this);
if (status != Status.RUNNING) {
loadStatus = null;
}
}
先把status置为Running,然后获取图片的宽高属性值,接着通过Engine.load方法加载图片:
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
重要的东西都在这里了:
- 判断是否在主线程,到目前为止一直在主线程运行,还没有开启子线程
- 创建一个EngineKey,key关联着model,即url。EngineKey可以用来指定缓存地址,可以从缓存中查找资源
- 根据key分别调用loadFromActiveResources和loadFromCache从内存缓存中查找资源,如果有,则直接回调cb.onResourceReady()来设置图片。
- 构建EngineJob和DecodeJob对象,这两个对象是加载资源的两个重要类,EngineJob负责开启线程去加载资源,获取资源后转换到主线程并进行回调。DecodeJob是真正的执行者,它是真正去网络加载资源的地方。EngineJob开启线程,执行DecodeJob,DecodeJob执行完之后叫EngineJob去分发回调,这就是二者的关系。
接下来,我们直接看加载图片的代码,EngineJob.start方法开启线程:
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
很简单,冲线程池中分配一个线程执行DecodeJob(跟踪DecodeJob源码知道,DecodeJob实现了Runnable),到这里,从主线程切到子线程当中,接下来看run方法:
@Override
public void run() {
GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (Throwable t) {
if (stage != Stage.ENCODE) {
throwables.add(t);
notifyFailed();
}
if (!isCancelled) {
throw t;
}
} finally {
if (localFetcher != null) {
localFetcher.cleanup();
}
GlideTrace.endSection();
}
}
继续跟踪runWrapped方法:
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
这个方法中涉及到从缓存中获取图片和从网络请求图片,暂时忽略缓存,我们直接看如何从往网络获取图片,直接跟进runGenerators方法:
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
}
在while循环中会调用currentGenerator.startNext方法,defaultGenerator是DataFetcherGenerator类型,它是一个接口,在getNextGenerator方法中初始化:
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
由于我们只关注网络请求获取图片,所以直接看SourceGenerator这个实现类的startNext方法:
@Override
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
这个方法中,我们着重看loadData.fetcher.loadData(helper.getPriority(), this)这段代码,Glide可以结合一些网络库一起使用,在项目中,我们是结合OkHttp使用的,所以这里的loader是OkHttpLoader类型,而fetcher则是OkHttpStreamFetcher类型,我们直接看它的loadData方法:
@Override
public void loadData(@NonNull Priority priority,
@NonNull final DataCallback<? super InputStream> callback) {
Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();
this.callback = callback;
call = client.newCall(request);
call.enqueue(this);
}
到这里,就很清楚了,完全是按OkHttp的四步走去发请求获取图片。图片获取成功之后,一步一步回调,回调步骤如下:
OkHttpStreamFetcher.onResponse() -> SourceGenerator.onDataReady() -> DecodeJob.onDataFetchReady() -> EngineJob.reschedule() ->
我们看一下reschedule方法:
@Override
public void reschedule(DecodeJob<?> job) {
// Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
// up.
getActiveSourceExecutor().execute(job);
}
通过线程池执行DecodeJob,就会调用DecodeJob.run() -> DecodeJob.runWrapped()
, 在runWrapped方法中,已经获取到了数据,状态会变成DECODE_DATA
,接着调用decodeFromRetrievedData方法解析图片数据:
private void decodeFromRetrievedData() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Retrieved data", startFetchTime,
"data: " + currentData
+ ", cache key: " + currentSourceKey
+ ", fetcher: " + currentFetcher);
}
Resource<R> resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
继续跟进decodeFromData方法:
private <Data> Resource<R> decodeFromData(DataFetcher<?> fetcher, Data data,
DataSource dataSource) throws GlideException {
try {
if (data == null) {
return null;
}
long startTime = LogTime.getLogTime();
Resource<R> result = decodeFromFetcher(data, dataSource);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded result " + result, startTime);
}
return result;
} finally {
fetcher.cleanup();
}
}
接着看decodeFromFetcher方法:
@SuppressWarnings("unchecked")
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
return runLoadPath(data, dataSource, path);
}
看runLoadPath方法:
private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource,
LoadPath<Data, ResourceType, R> path) throws GlideException {
Options options = getOptionsWithHardwareConfig(dataSource);
DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
try {
// ResourceType in DecodeCallback below is required for compilation to work with gradle.
return path.load(
rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
} finally {
rewinder.cleanup();
}
}
返回path.load方法:
public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width,
int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException {
List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire());
try {
return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
} finally {
listPool.release(throwables);
}
}
private Resource<Transcode> loadWithExceptionList(DataRewinder<Data> rewinder,
@NonNull Options options,
int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback,
List<Throwable> exceptions) throws GlideException {
Resource<Transcode> result = null;
for (int i = 0, size = decodePaths.size(); i < size; i++) {
DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
try {
result = path.decode(rewinder, width, height, options, decodeCallback);
} catch (GlideException e) {
exceptions.add(e);
}
if (result != null) {
break;
}
}
if (result == null) {
throw new GlideException(failureMessage, new ArrayList<>(exceptions));
}
return result;
}
这里又会调用DecodePath.decode方法:
public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
@NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
return transcoder.transcode(transformed, options);
}
transcoder是DrawableBytesTranscoder对象,所以看它的transcode方法:
@Nullable
@Override
public Resource<byte[]> transcode(@NonNull Resource<Drawable> toTranscode,
@NonNull Options options) {
Drawable drawable = toTranscode.get();
if (drawable instanceof BitmapDrawable) {
return bitmapBytesTranscoder.transcode(
BitmapResource.obtain(((BitmapDrawable) drawable).getBitmap(), bitmapPool), options);
} else if (drawable instanceof GifDrawable) {
return gifDrawableBytesTranscoder.transcode(toGifDrawableResource(toTranscode), options);
}
return null;
}
然后进入BitmapDrawableTranscoder的transcode方法,这个方法返回一个Resource对象,接下来一层一层回调:
DecodeJob.notifyEncodeAndRelease() -> DecodeJob.notifyComplete -> EngineJob.onResourceReady -> MainThreadCallback.handleMessage -> SingleRequest.onResourceReady -> target.onResourceReady ->
直接看target实现类ImageViewTarget的onResourceReady方法:
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
setResourceInternal(resource);
} else {
maybeUpdateAnimatable(resource);
}
}
private void setResourceInternal(@Nullable Z resource) {
setResource(resource);
maybeUpdateAnimatable(resource);
}
再看实现类DrawableImageViewTarget的setResource方法:
@Override
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
到这里,总算把图片设置给了ImageView,至此,Glide加载图片的主体流程分析完毕了,可以看到这个过程很复杂,不是那么容易理解,好在总算把这个过程梳理完毕了。
Glide缓存机制
分析了Glide主体流程之后,我们来看一下Glide的缓存机制,从上面的源码分析,我们可以知道,Glide有内存缓存和硬盘缓存两种方式,接下来我们分别看这两种方式:
1. 内存缓存
Glide内存缓存分两种方式:若引用缓存
和LruCache缓存
。
下面看看怎样使用缓存,在Engine.load方法中有这样一段代码:
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
源码分析时简单提了一句,但没做详细分析,这段代码就是从内存缓存中获取图片。逻辑如下:
- 先从弱引用缓存中获取图片,如果找到则直接回调
- 弱引用缓存没有找到图片,再从LruCache中取图片,如果找到图片则直接回调
- LruCache中也没有找到图片,再回走网络请求加载图片
这里着重说一下弱引用缓存,它用来保存正在使用的图片,下面代码中会有所介绍。
具体如何从缓存中获取的这里不做详细介绍,各位同学跟踪到loadFromActiveResouces方法和loadFromCache方法中可以看到。
那么,问题来了,又是在哪里把图片存储到内存缓存中呢?
通过网络加载图片完成之后,会调用EngineJob.handleResultOnMainThread方法:
@Synthetic
void handleResultOnMainThread() {
stateVerifier.throwIfRecycled();
if (isCancelled) {
resource.recycle();
release(false /*isRemovedFromQueue*/);
return;
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received a resource without any callbacks to notify");
} else if (hasResource) {
throw new IllegalStateException("Already have resource");
}
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
//把正在使用的图片保存到弱引用缓存中,并且对使用数量+1
engineResource.acquire();
listener.onEngineJobComplete(this, key, engineResource);
for (int i = 0, size = cbs.size(); i < size; i++) {
ResourceCallback cb = cbs.get(i);
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource, dataSource);
}
}
//图片使用完成,对使用数量-1,当使用数量为0,则把它从弱引用缓存中移出,并且添加到LruCache缓存中
engineResource.release();
release(false /*isRemovedFromQueue*/);
}
这段代码涉及到两种缓存的使用,使用图片的过程中把图片存放在弱引用缓存,图片使用完成则存放在LruCache缓存。先看一下listener.onEngineJobComplete方法:
@SuppressWarnings("unchecked")
@Override
public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
Util.assertMainThread();
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.activate(key, resource);
}
}
jobs.removeIfCurrent(key, engineJob);
}
跟踪到ActiveResources.activate方法:
void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key,
resource,
getReferenceQueue(),
isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
再看一下activeEngineResources的声明:
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
到这里就很明了,弱引用缓存使用一个HashMap来保存图片资源的弱引用,key则是Engine.load方法中创建的EngineKey对象。
接下来,继续上面提到的,分析LruCache缓存,当图片使用完毕之后,会调用EngineResource.release方法:
void release() {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
此处的listener是Engine对象,所以我们看一下Engine.onResouceReleased方法:
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
一目了然,把图片资源存到cache中,并且从弱引用缓存中释放。此处的cache对象是LruResourceCache类型。
到这里,Glide内存缓存的两种方式:弱引用缓存和LruCache缓存分析完毕了。
Glide默认开启内存缓存,我们可以通过RequestOptions.skipMemoryCache()
方法设置是否使用内存缓存。
2. 硬盘缓存
Glide默认开启硬盘缓存,可以通过设置RequestOption.diskCacheStrategy(DiskCacheStrategy.NONE)
来关闭硬盘缓存,硬盘缓存有几种策略:
-
DiskCacheStrategy.NONE
:硬盘不缓存任何内容 -
DiskCacheStrategy.ALL
:硬盘既缓存原始图片,也缓存转换之后的图片 -
DiskCacheStrategy.DATA
:硬盘只缓存原始图片 -
DiskCacheStrategy.RESOURCE
:硬盘只缓存转换之后的图片 -
DiskCacheStrategy.AUTOMATIC
:Glide根据图片资源只能选择哪一种缓存策略(Glide默认选项)
解释一下原始图片和转换后的图片,我们使用Glide加载图片的时候,Glide默认并不会展示原始图片,而是会对图片进行压缩和转换,经过着一系列的操作得到的图片,我们称之为转换后的图片。
对硬盘缓存有了清晰的认识之后,我们看一下Glide在哪里使用硬盘缓存,还记得DecodeJob中,我们提到过硬盘缓存吗。
我们再一起回顾一下DecodeJob中的getNextStage方法:
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
这个方法逻辑比较清晰:
- 判断当次请求支持使用硬盘缓存的转换后图片。如果是的则
stage = Stage.RESOURCE_CACHE
- 判断当次请求支持使用硬盘缓存的原始图片。如果是的则
stage = Stage.DATA_CACHE
- 如果上面两种方式都不支持,则根据是否只能通过硬盘缓存获取图片把
stage = Stage.SOURCE
或者stage = Stage.FINISHED
。
那么这个stage
有什么用呢?
别着急,我们继续看DecodeJob.getNextGenerator方法:
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
不同的stage会对应不同的DataFetcherGenerator,源码分析中,我们只分析了SourceGenerator,而除此之外,这里还有ResourceCacheGenerator和DataCacheGenerator,这两个类分别对应从硬盘获取转换后图片资源 和 从硬盘获取原始图片资源。获取过程和从网络获取类似,也是从从线程池中分配一个线程,从硬盘获取图片资源并且进行解码操作,然后把图片设置个ImageView。
知道了如何使用硬盘缓存,那么还有一个问题,Glide是什么时候把图片存到硬盘上的呢?
带着这个疑问,我们继续看源码,在decodeFromRetrievedData方法中,会调用notifyEncodeAndRelease方法:
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
if (resource instanceof Initializable) {
((Initializable) resource).initialize();
}
Resource<R> result = resource;
LockedResource<R> lockedResource = null;
if (deferredEncodeManager.hasResourceToEncode()) {
lockedResource = LockedResource.obtain(resource);
result = lockedResource;
}
notifyComplete(result, dataSource);
stage = Stage.ENCODE;
try {
if (deferredEncodeManager.hasResourceToEncode()) {
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
if (lockedResource != null) {
lockedResource.unlock();
}
}
// Call onEncodeComplete outside the finally block so that it's not called if the encode process
// throws.
onEncodeComplete();
}
在这个方法中调用了DeferredEncodeManager.encode方法:
void encode(DiskCacheProvider diskCacheProvider, Options options) {
GlideTrace.beginSection("DecodeJob.encode");
try {
diskCacheProvider.getDiskCache().put(key,
new DataCacheWriter<>(encoder, toEncode, options));
} finally {
toEncode.unlock();
GlideTrace.endSection();
}
}
这里就很明显了,把图片资源存到硬盘上。
到这里,Glide两种缓存方式就分析完毕了,可以说Glide的缓存策略非常完善,只要掌握了这些缓存策略,我们可以根据项目中的实际情况灵活调整缓存方式。
Glide图变换
我们可以通过RequestOption设置图片的变换,包括center_crop、center_inside、circle_crop...
,那么这些变换是在哪里对图片起作用的呢?
举个例子,我想显示一个圆形的图片,那么就可以设置:
RequestOptions options = new RequestOptions().circleCrop();
我们跟进circleCrop方法:
@NonNull
@CheckResult
public RequestOptions circleCrop() {
return transform(DownsampleStrategy.CENTER_INSIDE, new CircleCrop());
}
可以看到,这里创建了一个CircleCrop对象,我们看一下CircleCrop的实现:
public class CircleCrop extends BitmapTransformation {
private static final int VERSION = 1;
private static final String ID = "com.bumptech.glide.load.resource.bitmap.CircleCrop." + VERSION;
private static final byte[] ID_BYTES = ID.getBytes(CHARSET);
@SuppressWarnings("PMD.CompareObjectsWithEquals")
@Override
protected Bitmap transform(
@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
return TransformationUtils.circleCrop(pool, toTransform, outWidth, outHeight);
}
@Override
public boolean equals(Object o) {
return o instanceof CircleCrop;
}
@Override
public int hashCode() {
return ID.hashCode();
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update(ID_BYTES);
}
}
这个类继承自BitmapTransformation类,主要的转换过程在transform方法中,会调用TransformationUtils.circleCrop方法:
public static Bitmap circleCrop(@NonNull BitmapPool pool, @NonNull Bitmap inBitmap,
int destWidth, int destHeight) {
int destMinEdge = Math.min(destWidth, destHeight);
float radius = destMinEdge / 2f;
int srcWidth = inBitmap.getWidth();
int srcHeight = inBitmap.getHeight();
float scaleX = destMinEdge / (float) srcWidth;
float scaleY = destMinEdge / (float) srcHeight;
float maxScale = Math.max(scaleX, scaleY);
float scaledWidth = maxScale * srcWidth;
float scaledHeight = maxScale * srcHeight;
float left = (destMinEdge - scaledWidth) / 2f;
float top = (destMinEdge - scaledHeight) / 2f;
RectF destRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
// Alpha is required for this transformation.
Bitmap toTransform = getAlphaSafeBitmap(pool, inBitmap);
Bitmap.Config outConfig = getAlphaSafeConfig(inBitmap);
Bitmap result = pool.get(destMinEdge, destMinEdge, outConfig);
result.setHasAlpha(true);
BITMAP_DRAWABLE_LOCK.lock();
try {
Canvas canvas = new Canvas(result);
// Draw a circle
canvas.drawCircle(radius, radius, radius, CIRCLE_CROP_SHAPE_PAINT);
// Draw the bitmap in the circle
canvas.drawBitmap(toTransform, null, destRect, CIRCLE_CROP_BITMAP_PAINT);
clear(canvas);
} finally {
BITMAP_DRAWABLE_LOCK.unlock();
}
if (!toTransform.equals(inBitmap)) {
pool.put(toTransform);
}
return result;
}
这个方法是真正的转换过程,计算图片的宽高,重新生成新的Bitmap。
系统提供了几种常用的图片变换成方式,我们也可以自定义变换,各位可以根据项目要求实现自定义变换。
到这里,图片变换也介绍完毕了。
对比Glide、Picasso、Fresco
可以看到,三大主流图片加载框架性能都不错,不过整体而言Glide更胜一筹。
总结
Glide确实是一款强大易用的图片加载框架,不过背后,是框架为我们做了很多的工作。
写这篇文章历经了很长时间,主要是因为Glide源码部分太过复杂。分析了Glide之后,Picasso和Fresco就不再做分析,原理大同小异,各位同学感兴趣的话可以自己去分析那两个框架。