Android 进阶5:Glide4.0源码分析
这篇博客其实准备了很久,从Glide的各种使用到Glide的源码分析,Glide的源码很复杂,个人感觉,比LeakCanary,Eventbus等框架源码复杂很多,网上说别Picasso源码复杂,以后再看Picasso的源码吧。
本片博客打算分为三个部分
- Glide的使用以及原理
- Glide的源码分析
- Glide的生命周期
1: Glide的使用
其实关于Glide 的使用,网上有很多,基本都是大同小异,在此不再陈述,这里说一些Glide的比较生疏的用法;
自定义添加动画
当我们第一次加载动画的时候:
private void glideAddAnimation() {
Glide.with(this).load(imageUrl).transition(GenericTransitionOptions.with(R.anim.my_anim)).into(imageView);
}
除了GenericTransitionOptions之外,还有BitmapTransitionOptions和DrawableTransitionOptions,三个的区别不再赘述;R.anim.my_anim也就是我们自己自定义的动画。
上述代码当第一次运行的时候,你会发现挺好的,但是当你第二次运行的时候(相同URL),没有动画过渡;因为上述动画仅仅针对第一次从网络加载图片有效,第二次从缓存获取图片的过渡动画需要另外设置。
Glide.with(this).load(imageUrl)
.transition(GenericTransitionOptions.with(R.anim.my_anim)).listener(
new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target,
boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target,
DataSource dataSource,
boolean isFirstResource) {
if (dataSource == DataSource.MEMORY_CACHE) {
imageView.startAnimation(
AnimationUtils.loadAnimation(getApplicationContext(), R.anim.my_anim));
}
return false;
}
}).into(imageView);
添加Listener监听,当dataSource代表数据来源,等于MEMORY_CACHE(内存缓存)中获取时,添加动画。
给图片添加动画比较消耗性能,甚至比解码原图资源消耗的都大,所以为了提升性能,请在使用 Glide 向 ListView , GridView, 或 RecyclerView 加载图片时考虑避免使用动画。这是官方的原话。
加载圆角图片
在说圆角图片之前,先说一下,,Glide是支持圆形图片的:
private void glideCircle() {
Glide.with(this).load(imageUrl).apply(RequestOptions.circleCropTransform()).into(imageView);
}
关于圆角图片就需要自定义BitmapTransformation了,代码如下:
/**
* 圆角图片
* Created by ${liumengqiang} on 2018/10/9.
*/
public class SecondRoundTransform extends BitmapTransformation {
private float radius = 10;
@Override
protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
//转化后的Bitmap(centerCrop)
Bitmap centerCropBitmap = TransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight);
//从BitmapPool(可以理解为Bitmap池)获取一份Bitmap
Bitmap roundBitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
//创建画布,在RoundBitmap写入数据
Canvas canvas = new Canvas(roundBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(new BitmapShader(centerCropBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
RectF rectF = new RectF(0, 0, centerCropBitmap.getWidth(), centerCropBitmap.getHeight());
//圆角
canvas.drawRoundRect(rectF, radius, radius, paint);
return roundBitmap;
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
}
}
private void glideRound() {
Glide.with(this).load(imageUrl).apply(RequestOptions.bitmapTransform(new SecondRoundTransform()))
.into(imageView);
}
在上述自定义BitmapTransformation中,为了防止频繁创建Bitmap造成内存抖动,所以是从BitmapPool中获取Bitmap对象。
官方自定BitmapTransformation 文档:https://github.com/bumptech/glide/wiki/Transformations
缓存策略
关于缓存策略,3.0和4.0差别还是比较大的
Glide3.0
DiskCacheStrategy.NONE:不缓存
DiskCacheStrategy.SOURCE:之缓存原图
DiskCacheStrategy.RESULT:只缓存最终要显示的图片(默认选项; Picasso默认缓存原图)
DiskCacheStrategy.ALL:缓存所有版本的图片
Glide4.0
DiskCacheStrategy.ALL 使用DATA和RESOURCE缓存远程数据,仅使用RESOURCE来缓存本地数据。
DiskCacheStrategy.NONE 不使用磁盘缓存
DiskCacheStrategy.DATA 在资源解码前就将原始数据写入磁盘缓存
DiskCacheStrategy.RESOURCE 在资源解码后将数据写入磁盘缓存,即经过缩放等转换后的图片资源。
DiskCacheStrategy.AUTOMATIC 根据原始图片数据和资源编码策略来自动选择磁盘缓存策略。(默认策略)
关于4.0的默认策略官方是这样说的:
默认的策略叫做 AUTOMATIC ,它会尝试对本地和远程图片使用最佳的策略。当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如,变换,裁剪–译者注)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。
Glide加载一张新图片时候会检查多级的缓存:
- 活动资源(ActivityResource):现在是否有另一个ImageView在显示此图片(宽高也一致)
- 内存资源(MemoryCache):图片是否存在在内存中
- 资源类型(Rource):图片是否曾被解码之后写入过磁盘缓存
- 数据来源(Data):原始数据图片是否写入过文件缓存
第一步和第二步检查都是针对于内存的,如果有就直接返回图片。后两步是发生在磁盘上的,需要异步处理。
每个图片都对应一个Key,这个Key的生成参数:图片路径,Transformtion,Options,请求数据类型等参数。
这些参数经过哈希处理之后就生成一个单独的Key。
Glide默认网络请求方式
Glide默认的网络请求实现是:HttpUrlConnection;
添加implementation 'com.github.bumptech.glide:okhttp3-integration:4.3.1’之后默认的加载数据使用时OkHttp。
Glide也支持Volley加载数据,但是其性能要低于OKHttp。
Glide常见需求
- 路径不变,但是服务器的图片变了
- 为了保证图片安全,图片带token加载数据(比如七牛云图片)
- 请求自定义尺寸图片
对于需求1,该问题的产生是由于Glide的缓存机制,Key缓存是根据图片路径以及其它的一些参数决定的,造成的后果就是请求的永远是缓存中的老图片,有同学会说跳过磁盘缓存就行了,这样确实行,Glide的强大之处就在于缓存机制。再次我们提供的思路是在Key上下功夫,思路就是:定期刷新缓存策略或者是key添加标记(例如version版本号)。官网:https://muyangmin.github.io/glide-docs-cn/doc/caching.html
需求2是常见的需求,但是本身项目中没有遇到,所以没有发言权,附上解决方案:https://blog.csdn.net/wangdaqi77/article/details/72819097。
至于这个解决方案原理我们下文讲到。
需求3的场景就是:请求一定尺寸大小的图片。官方解决方案:https://github.com/bumptech/glide/wiki/Downloading-custom-sizes-with-Glide
通过上文中我们可以提出问题点:
Glide本质的请求加载数据使用的是HttpUrlConnection(默认,不导入Okhttp)
2: Glide的源码流程分析
关于Glide的源码3.0 和4.0 的差别还是很大的,从缓存策略就可以看出。接下来分析的Glide源码针对于Glide4.0。
我们最简单的使用Glide代码示下:
Glide.with(this).load(imageUrl).apply(RequestOptions.circleCropTransform()).into(imageView);
典型的链式结构,简简单单的一句话就可以加载图片,殊不知Glide在背后为我们做了大量的工作。
先看Glide.with(this) :
//TODO 思考:为什么传入FragmentActivity对象。
@NonNull
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
可以看到该方法返回的了RequestManager对象,对于上文的思考,我们先给出答案:为了GLide的生命周期和Activity的生命周期绑定。 其实with接收的参数有好几种类型,自行查看 ,在此不再赘述。
@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();
}
@NonNull
public static Glide get(@NonNull Context context) {
//单例模式,这里运用了加锁机制以及双判断,防止并发。
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context);
}
}
}
return glide;
}
private static void checkAndInitializeGlide(@NonNull Context context) {
.......
initializeGlide(context);
......
}
private static void initializeGlide(@NonNull Context context) {
// 在此处创建了GlideBuilder对象,既然Glide是单例的,那么这个对象同样也是单例的。
initializeGlide(context, new GlideBuilder());
}
@SuppressWarnings("deprecation")
private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
.........
if (Log.isLoggable(TAG, Log.DEBUG)) {
//这里开始加载manifest配置,不过为了提升性能,在自定义GlideMoudle的时候,我们都禁止从manifest读取信息了,这样能够提高新能
for (com.bumptech.glide.module.GlideModule glideModule : manifestModules) {
Log.d(TAG, "Discovered GlideModule from manifest: " + glideModule.getClass());
}
}
//是否设置了RequestManagerFactory,一般是null。
RequestManagerRetriever.RequestManagerFactory factory =
annotationGeneratedModule != null
? annotationGeneratedModule.getRequestManagerFactory() : null;
builder.setRequestManagerFactory(factory);
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.applyOptions(applicationContext, builder);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.applyOptions(applicationContext, builder);
}
//这里返回了Glide对象,重点分析。
Glide glide = builder.build(applicationContext);
.......
Glide.glide = glide;
}
通过上述代码,我们能够总结一下Gide.get(context)方法中主要做了几件事:
- 创建GlideBuilder对象
- 通过GlideBuilder的build方法返回Glide对象。
我们进入build方法看下是怎样创建Glide对象的。
@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();
}
//Bitmap缓存池创建
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());
}
//内存加载(Lru算法)
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);
//创建Glide对象,
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptions.lock(),
defaultTransitionOptions);
}
GlideBuilder的build方法主要做了三件事:
- 创建各种加载器或者执行器
- 创建RequestManagerRetriever对象
- 创建Glide对象,并且将前两步创建的对象都回传给Glide。
我们看下RequestManagerRetriever的构造方法:
public RequestManagerRetriever(@Nullable RequestManagerFactory factory) {
//由于我们没有传递factory,所以默认使用DEFAULT_FACTORY。
this.factory = factory != null ? factory : DEFAULT_FACTORY;
//创建handler对象,回调给主线程
handler = new Handler(Looper.getMainLooper(), this /* Callback */);
}
private static final RequestManagerFactory DEFAULT_FACTORY = new RequestManagerFactory() {
@NonNull
@Override
//此处返回的是RequestManager对象,需要注意。
public RequestManager build(@NonNull Glide glide, @NonNull Lifecycle lifecycle,
@NonNull RequestManagerTreeNode requestManagerTreeNode, @NonNull Context context) {
return new RequestManager(glide, lifecycle, requestManagerTreeNode, context);
}
};
通过默认的DEFAULT_FACTORY的build方法,返回的是RequestManager对象.。
至此我们的GLide对象创建好了,重新回答with方法,看下get方法,其实get方法最终调用的都是RequestManagerRetriever类的supportFragmentGet方法,
@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) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
//创建RequestManager对象
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
factory默认是DEFAULT_FACTORY,通过build方法创建requestManager对象。至此Glide.with(this)最终创建RequestManager对象就分析完了。
接下来看下load(url)方法:
//通过RequestManager的load方法返回RequestBuilder对象。
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
//创建RequestBuidler对象
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
其实load是有好几个重载方法的,各位可以自行查看,上述代码做的事就是:创建RequestBuidler对象。顾名思义:请求构造,也就是请求参数设置啦。注意一下asDrawable方法内as方法的参数类型就是Drawable.class。
再来看下RequestBuilder对象的load方法:
@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
此处我们需要注意的是,我们的路径赋值给了model对象!这个下文会用到的。
请求构造好了,是不是该添加请求设置了? 没错,就是Glide的load方法之后into方法之前中间的一系列设置,在此不再详述。那么设置都设置好了,请求数据在哪里呢? 看into(imageView)方法:
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
........
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}
重载了into方法,先来看下glideContext.buildImageView方法,这里参数view就是我们的ImageVIew,transcodeClass是在RequestBuilder构造方法中赋值的,还记得上文中的as方法的参数就是Drawable.class,所以此处的transcodeClass就是Draable.class对象。
@NonNull
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
public class ImageViewTargetFactory {
@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)");
}
}
}
由于clazz是Drawable.class,所以返回的是DrawableImageViewTarget对象。这个东西下文会用到。
绕了一大圈,重新进入到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();
//这个target就是DrawableImageViewTarget对象,需要注意
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;
}
requestManager.clear(target);
//给target添加一个新请求
target.setRequest(request);
//加载请求
requestManager.track(target, request);
return target;
}
上述代码主要做了三件事
- 创建请求,
- 判断请求是否已经运行过了,如果运行过了,就直接请求
- 如果是新的请求,那么加载请求
我们先看下创建请求Request。buildRequest最终调用的方法就是:buildThumbnailRequestRecursive
private Request buildThumbnailRequestRecursive(一堆参数) {
if (thumbnailBuilder != null) {
//省略代码,判断是否添加了缩略图,显然我们这里没有添加缩略图
} else {
// Base case: no thumbnail.
return obtainRequest(
target,
targetListener,
requestOptions,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight);
}
}
private Request obtainRequest(一堆参数) {
//创建SingleRequest对象
return SingleRequest.obtain(
context,
glideContext,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
targetListener,
requestListeners,
requestCoordinator,
glideContext.getEngine(),
transitionOptions.getTransitionFactory());
}
上述代码主要就是创建请求只不过就是看创建哪一种类型的请求,由于我们没有添加缩略图,所以最终创建的是SingleRequest对象。
有了请求对象,就该执行请求了,我们看下requestManager.track(target, request);
void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
//开始一个请求
request.begin();
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
由于我们创建的是SingleRequest对象,所以进入到其begin方法内:
@Override
public void begin() {
.......
//该Request是否正在运行
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
//该请求是否已经完成了
if (status == Status.COMPLETE) {
//如果完成了就从缓存中获取
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
// Restarts for requests that are neither complete nor running can be treated as new requests
// and can run again from the beginning.
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
//先测量size
target.getSize(this);
}
........
}
如果我们没有调用override方法设置宽高,那么此时的宽高都是没有值的,所以此时会先测量宽高; 需要注意getSize的参数是this,进入到其方法内:
@CallSuper
@Override
//cb也就是SingleRequest对象。说明最后还要回到给SingleRequest对象
public void getSize(@NonNull SizeReadyCallback cb) {
sizeDeterminer.getSize(cb);
}
void getSize(@NonNull SizeReadyCallback cb) {
//获取目标image View的宽高
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
//回调给SingleRequest的onSizeReady方法执行。
cb.onSizeReady(currentWidth, currentHeight);
return;
}
........
}
上述代码做了一件事就是获取ImageView的宽高,然后在回调给SingleRequest的onSizeReady方法:
@Override
public void onSizeReady(int width, int height) {
........
//设置标记
status = Status.RUNNING;
.......
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);
.........
}
看到engine对象是不是眼熟呢? 它是在GlideBuilder的build方法中创建的,上文中有提到。 执行了engine.load方法,参数一大堆,注意一下最后一个参数:this, 也就是SiingleRequest对象! 需要特别注意。看下load方法做了什么:
public <R> LoadStatus load(一大堆参数) {
Util.assertMainThread();
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
//此处生成缓存的key
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) {
//此处需要特别注意,添加SingleRequest对象给EngineJob。
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继承自Runnable接口,等待执行器 执行任务
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);
//添加回调SingleRequest给EngineJob。
engineJob.addCallback(cb);
//开始执行
engineJob.start(decodeJob);
.......
}
上述代码充分体现了Glide的缓存机制,首先从活动资源查找资源,再从缓存查找资源,如果都没找到,就返回开启异步执行任务。需要注意两点:
- decodeJobFactory.build返回的是DecodeJob对象,最后一个参数是EngineJob对象,在DecodeJob的init方法内,将EngineJob对象赋值给了callback对象,此处需要格外注意!
- 创建EngineJob之后,调用了addCallbackf方法,这样,EngineJob就拥有了SingleRequest实例
看一下创建Key的代码,可以看出Key是由图片路径,宽高等相关设置共同决定的。老版本3.0的代码是:
final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
这个和老版本的代码还是有差距的,上述需求点访问七牛云图片就是在这里做了手脚。
进入到EngineJob的start方法:
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
执行decodejob ,之后就都是异步了,进入到DecodeJob的run方法内:
@Override
public void run() {
.......
try {
if (isCancelled) {
notifyFailed();
return;
}
//进入此方法
runWrapped();
} catch (Throwable t) {
.........
}
private void runWrapped() {
switch (runReason) {
case INITIALIZE: //runReason的初始化实在DecodeJob的init方法中初始化的 默认是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);
}
}
//递归返回Stage.SOURCE
private Stage getNextStage(Stage current) { //current是INITIALIZE
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);
}
}
关于DecodeJob的runReason变量初始化可以自行查找。getNextStage方法的current参数是INITIALIZE,所以先在磁盘是否有图片转换处理过的缓存,显然是没有的,在查找是否存在图片的原始数据,很显然,还是没有;此时就返回Stage.SOURCE,开始从网络查找。
至此本地缓存就算都遇见了:
- 活动资源查找
- 内存缓存查找
- 磁盘查找——转换过的图片缓存
- 磁盘查找——原视图片数据
接下来看下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,也就是说currentGenerator就是SourceGenerator对象。
接下来就是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();
.........
}
重点看startNext方法,进入到SourceGenerator的startNext方法:
@Override
public boolean startNext() {
.......
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;
}
此处调用了fetcher.loadData方法,Glide默认加载使用的是HttpUrlFetcher方法,注意一下参数this代表的是SourceGenerator类对象:
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//输入流,是不是惊喜?
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
}
......
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
Map<String, String> headers) throws IOException {
//使用的是HttpUrlConnection
urlConnection = connectionFactory.build(url);
.......
}
最终使用的还是HttpUrlConnection,返回输入流,返回输入流给SourceGenerator:
public void onDataReady(Object data) {
......
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
在回调给 DecodeJob ,cb就是DecodeJob对象,上文有让注意的。
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
.........
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();
}
}
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
........
notifyComplete(result, dataSource);
........
}
private void notifyComplete(Resource<R> resource, DataSource dataSource) {
setNotifiedOrThrow();
callback.onResourceReady(resource, dataSource);
}
上述代码的主要流程就是操作读取流, 转化图片资源,并通过回调返回给SingleRequest, 这个callback就是EngineJob对象,上文有让注意的,实际上上述代码流程还是很复杂的,内部还进行了转化图片,缓存图片等等操作
@Override
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
this.resource = resource;
this.dataSource = dataSource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
@Synthetic
void handleResultOnMainThread() {
.....
cb.onResourceReady(engineResource, dataSource);
.....
}
发送handle信息,切回UI主线程。此处又有一个回调,cb就是我们的SingleRequest,上文有注意点;
@SuppressWarnings("unchecked")
@Override
public void onResourceReady(Resource<?> resource, DataSource dataSource) {
.......
onResourceReady((Resource<R>) resource, (R) received, dataSource);
}
private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
........
target.onResourceReady(result, animation);
........
}
上述的target就是DrawableImageViewTarget对象,这个上文也是有让注意的。但是在DrawableImageViewTarget类继承自ImageViewTarget,在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) {
// Order matters here. Set the resource first to make sure that the Drawable has a valid and
// non-null Callback before starting it.
setResource(resource);
maybeUpdateAnimatable(resource);
}
setResource是个抽象方法,在子类DrawableImageViewTarget中实现了:
@Override
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
这里的view就是ImageView!
整个流程走完了,烧脑,叉会儿腰,喝杯水,再来看Glide加载数据的生命周期
上一篇: Android 4.0 设置全屏修改
推荐阅读
-
Android 进阶5:Glide4.0源码分析
-
Android Animation动画原理源码分析
-
Android RxJava:一步步带你源码分析 RxJava
-
带你从源码一步步分析Android View面试中的事件分发流程
-
Android进阶:四、RxJava2 源码解析 1
-
面试必备:Android Activity启动流程源码分析
-
Android 高级进阶之overdraw分析及解决
-
C++:智能指针(5)——enable_shared_from_this工作原理、源码分析
-
Android开发进阶自定义控件之滑动开关实现方法【附demo源码下载】
-
Android点击事件派发机制源码分析