Glide作为最近几年比较火热的图片加载框架,几乎广泛分布于各类App中,最近这一年都在用Coil,反而很少在用Glide了,之后会进行Coil的源码分析,因为Coil全部用Kotlin实现的,很多伙伴可能不太熟悉,因此先吃从Glide说起,如果对Coil感兴趣的伙伴,关注后续的源码分析。
1 Glide三大主线
如果使用过Glide的伙伴们应该了解,在使用Glide的链式调用时,主要分为3大主线:with、load、into
Glide.with(this).load("url").into(iv_image)
就能够轻松完成一张图片的加载,但是其中的原理却是非常复杂的,而且Glide源码异常庞大,所以在分析源码时一定要挑重点查看。
1.1 with主线
首先我们看一下with方法,这个方法是Glide中的一个重载方法,在with方法中可以传入Activity或者Fragment
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
既然能够传入Activity和Fragment,相比是要与其生命周期做关联,后续我们会详细分析。这里想说一个之前项目中的线上事故,问题如下:
Glide.with(requireContext()).load("url").into(iv_image)
之前伙伴在使用Glide的时候,可能是因为写的太快而且编译器并没有报错,在调用with方法的时候传入了Context上下文,所以Glide没有与页面生命周期绑定,导致页面消失后,请求依然在后台运行,最终刷新页面时找不到加载的容器直接崩溃,所以with方法一定是与生命周期有关,闲话不多说继续看源码。
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
// Context could be null for other reasons (ie the user passes in null), but in practice it will
// only occur due to errors with the Fragment lifecycle.
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).");
/** Glide的get方法最终目的就是创建Glide对象*/
return Glide.get(context).getRequestManagerRetriever();
}
1.1.1 Glide的创建
我们可以看到,getRetriever方法最终返回了一个RequestManagerRetriever对象,其中我们关注以下这段代码
Glide.get(context).getRequestManagerRetriever();
其中get方法是Glide中的一个单例,采用双检锁的方式,最终返回了一个Glide对象,也就是在zcheckAndInitializeGlid这个方法中
// Double checked locking is safe here.
@SuppressWarnings("GuardedBy")
public static Glide get(@NonNull Context context) {
if (glide == null) {
GeneratedAppGlideModule annotationGeneratedModule =
getAnnotationGeneratedGlideModules(context.getApplicationContext());
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context, annotationGeneratedModule);
}
}
}
return glide;
}
我们顺着这个方法一直找下去,最终调用了Glide的build方法,创建了一个Glide对象,我们看到在Glide的构造方法中传入了一些像缓存、Bitmap池相关的对象,后面我们会继续分析。
@NonNull
Glide build(
@NonNull Context context,
List<GlideModule> manifestModules,
AppGlideModule annotationGeneratedGlideModule) {
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(),
animationExecutor,
isActiveResourceRetentionAllowed);
}
if (defaultRequestListeners == null) {
defaultRequestListeners = Collections.emptyList();
} else {
defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
}
GlideExperiments experiments = glideExperimentsBuilder.build();
/**在这里直接创建了RequestManagerRetriever对象*/
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory, experiments);
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptionsFactory,
defaultTransitionOptions,
defaultRequestListeners,
manifestModules,
annotationGeneratedGlideModule,
experiments);
}
再次回到getRetriever方法中,因为Glide的get方法创建了Glide对象,也就是调用了Glide的getRequestManagerRetriever方法返回RequestManagerRetriever对象。
@NonNull
public RequestManagerRetriever getRequestManagerRetriever() {
return requestManagerRetriever;
}
看到这个方法,也就是说在此之前已经将这个对象初始化了,那么什么时候初始化的呢?就是在创建Glide对象的时候。
Glide(
/**这里暂时不关注其他参数*/
//......
@NonNull RequestManagerRetriever requestManagerRetriever,
//......) {
this.requestManagerRetriever = requestManagerRetriever;
}
1.1.2 RequestManagerRetriever
在Glide的build方法中,是直接创建了RequestManagerRetriever对象
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory, experiments);
这个对象需要一个requestManagerFactory参数,这个参数可以在外部定义
public RequestManagerRetriever(
@Nullable RequestManagerFactory factory, GlideExperiments experiments) {
this.factory = factory != null ? factory : DEFAULT_FACTORY;
this.experiments = experiments;
handler = new Handler(Looper.getMainLooper(), this /* Callback */);
lifecycleRequestManagerRetriever = new LifecycleRequestManagerRetriever(this.factory);
frameWaiter = buildFrameWaiter(experiments);
}
如果没有定义就是用默认的DEFAULT_FACTORY。
在创建RequestManagerRetriever之后,相当于拿到了一个请求的管理类,方便后续加载图片。
1.1.3 生命周期管理
在获取到RequestManagerRetriever对象之后,调用了其get方法,这里我们看下如果在with方法中传入了Activity,是如何处理的。
public RequestManager get(@NonNull Activity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else if (activity instanceof FragmentActivity) {
return get((FragmentActivity) activity);
} else {
/**判断当前Activity是否销毁,如果销毁了就报错*/
assertNotDestroyed(activity);
frameWaiter.registerSelf(activity);
/**获取当前Activity的事务管理器*/
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
首先判断如果是在子线程中,那么会调用另外一个重载的get方法,传入的是Context,而不是Activity了,那么我们重点关注下主线程中是如何处理。
private RequestManager fragmentGet(
@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
/**创建了RequestManagerFragment,这是一个空页面*/
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
/**第一次进来,这个方法返回空*/
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
// This is a bit of hack, we're going to start the RequestManager, but not the
// corresponding Lifecycle. It's safe to start the RequestManager, but starting the
// Lifecycle might trigger memory leaks. See b/154405040
if (isParentVisible) {
requestManager.onStart();
}
current.setRequestManager(requestManager);
}
return requestManager;
}
首先,调用了fragmentGet方法,看名字好像是要获取一个Fragment,果然在这个方法中第一步就创建了一个RequestManagerFragment空页面,如果熟悉LifeCycle源码的伙伴应该了解,这个页面大概率就是用来同步当前页面的生命周期的。
@Override
public void onStart() {
super.onStart();
lifecycle.onStart();
}
@Override
public void onStop() {
super.onStop();
lifecycle.onStop();
}
@Override
public void onDestroy() {
super.onDestroy();
lifecycle.onDestroy();
unregisterFragmentWithRoot();
}
果然在RequestManagerFragment的生命周期方法中,做了状态的回调。当创建了一个空Fragment之后,会从这个Fragment中获取RequetManager,当然第一次进来的时候肯定是空的,因此会创建一次,并调用setRequestManager塞给RequestManagerFragment。
1.1.4 总结
因此with方法主要干了以下几件事:
(1)创建了Glide对象;
(2)创建了一个空的Fragment,并将生命周期状态回调,相当于将Glide与当前页面的生命周期绑定。
1.2 load主线
通过with源码,我们知道在调用with方法后,最终获得一个RequetManager对象,相当于调用了它的load方法。
@NonNull
@CheckResult
@Override
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
其实load方法很简单,asDrawable方法是创建一个RequestBuilder对象
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
因为load方法能传值很多类型,像url、File、Bitmap等等,因此load方法只是将这些需要加载的类型给model赋值,剩余的事情,都是交给into完成。
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
if (isAutoCloneEnabled()) {
return clone().loadGeneric(model);
}
this.model = model;
isModelSet = true;
return selfOrThrowIfLocked();
}
1.3 into主线
在Glide中,除了Glide的创建还有生命周期的绑定,剩下都是交给into主线完成,因此into是Glide中最重要最复杂的。
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
BaseRequestOptions<?> requestOptions = this;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
// Clone in this method so that if we use this RequestBuilder to load into a View and then
// into a different target, we don't retain the transformation applied based on the previous
// View's scale type.
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,
Executors.mainThreadExecutor());
}
在into方法中,我们可以看到需要传入的就是ImageView.
在此之前会根据ImageView的scaleType属性,来给RequestOption复制,以便控制图片的显示状态。
然后一个比较重要的方法into方法,这个方法是RequestBuilder的into方法,传入了3个参数,分别是代表给容器的一个Target对象,还有一个比较重要的就是线程池,因为网络请求涉及到线程的上下文切换。
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
/**① 创建一个SingleRequest对象 */
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// If the request is completed, beginning again will ensure the result is re-delivered,
// triggering RequestListeners and Targets. If the request is failed, beginning again will
// restart the request, giving it another chance to complete. If the request is already
// running, we can let it continue running without interruption.
if (!Preconditions.checkNotNull(previous).isRunning()) {
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();
}
return target;
}
requestManager.clear(target);
/** 给Target赋值 */
target.setRequest(request);
/** 发起请求的开始 */
requestManager.track(target, request);
return target;
}
首先在这个方法中,调用buildRequest方法返回一个Request,这里可以直接跟伙伴们说就是SingleRequest
1.3.1 发起请求
创建request完成后,调用了RequetManager的track方法。
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
其实最终就是调用了RequetTracker的runRequest方法执行请求,首先将Request添加到了requests的请求队列中。
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);
}
}
然后判断isPaused的状态,这个状态决定了当前是否进行网络请求加载。其实这个状态就是在绑定页面生命周期后,当页面不可见或者销毁的时候,就将isPaused设置为true,这时即便请求到了Glide这边,也不会执行,而且直接调用了clear方法。
public synchronized void onStop() {
/**这里就会设置 isPaused = true*/
pauseRequests();
targetTracker.onStop();
}
当然如果isPaused为false,那么就直接调用了request的begin方法,也就是SingleRequest的begin方法。
@Override
public void begin() {
synchronized (requestLock) {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Only log at more verbose log levels if the user has set a fallback drawable, because
// fallback Drawables indicate the user expects null models occasionally.
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 we're restarted after we're complete (usually via something like a notifyDataSetChanged
// that starts an identical request into the same Target or View), we can simply use the
// resource and size we retrieved the last time around and skip obtaining a new size, starting
// a new load etc. This does mean that users who want to restart a load because they expect
// that the view size has changed will need to explicitly clear the View or Target before
// starting the new load.
if (status == Status.COMPLETE) {
onResourceReady(
resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return;
}
// Restarts for requests that are neither complete nor running can be treated as new requests
// and can run again from the beginning.
experimentalNotifyRequestStarted(model);
cookie = GlideTrace.beginSectionAsync(TAG);
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
/**准备好了,可以发起请求了*/
onSizeReady(overrideWidth, overrideHeight);
} else {
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));
}
}
}
如果做过图片加载监听的伙伴,应该对几个回调函数比较熟悉:onLoadStarted、onResourceReady、onLoadFailed,没错就是在这个方法中做的回调。
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
当这张图片的宽高尺寸符合标准的时候,就会调用onSizeReady方法,进行加载。
@Override
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
synchronized (requestLock) {
if (IS_VERBOSE_LOGGABLE) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
if (IS_VERBOSE_LOGGABLE) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
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,
callbackExecutor);
// This is a hack that's only useful for testing right now where loads complete synchronously
// even though under any executor running on any thread but the main thread, the load would
// have completed asynchronously.
if (status != Status.RUNNING) {
loadStatus = null;
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
}
在这里就是调用engine的load方法,在这个方法中,会根据图片的宽高等信息生成一个EngineKey,这个key是唯一的,与加载的图片一一对应。
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,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource<?> memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}
// Avoid calling back while holding the engine lock, doing so makes it easier for callers to
// deadlock.
cb.onResourceReady(
memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return null;
}
1.3.2 三级缓存
通过生成的EngineKey,调用loadFromMemory方法,来获取图片资源EngineResource。
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
、/**从活动缓存中取资源*/
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
/** 从LRU中取出缓存资源,并放到活动缓存中*/
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
我们可以看到在调用loadFromMemory方法时,主要从loadFromActiveResources、loadFromCache两个地方获取图片资源,分别代表从活动缓存和LRUCache中取资源,对于Glide三级缓存,后边将会有详细的文章介绍。
这样如果都没有找到资源,那么就会调用waitForExistingOrStartNewJob方法,其实就是从网络请求获取资源。
1.3.3 onResourceReady
当网络请求完成之后,或者从三级缓存中获取到了资源,都会回调到SingleRequest的onResourceReady方法。
private void onResourceReady(
Resource<R> resource, R result, DataSource dataSource, boolean isAlternateCacheKey) {
// We must call isFirstReadyResource before setting status.
boolean isFirstResource = isFirstReadyResource();
status = Status.COMPLETE;
this.resource = resource;
if (glideContext.getLogLevel() <= Log.DEBUG) {
Log.d(
GLIDE_TAG,
"Finished loading "
+ result.getClass().getSimpleName()
+ " from "
+ dataSource
+ " for "
+ model
+ " with size ["
+ width
+ "x"
+ height
+ "] in "
+ LogTime.getElapsedMillis(startTime)
+ " ms");
}
notifyRequestCoordinatorLoadSucceeded();
isCallingCallbacks = true;
try {
boolean anyListenerHandledUpdatingTarget = false;
if (requestListeners != null) {
for (RequestListener<R> listener : requestListeners) {
anyListenerHandledUpdatingTarget |=
listener.onResourceReady(result, model, target, dataSource, isFirstResource);
}
}
anyListenerHandledUpdatingTarget |=
targetListener != null
&& targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);
if (!anyListenerHandledUpdatingTarget) {
Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource);
target.onResourceReady(result, animation);
}
} finally {
isCallingCallbacks = false;
}
GlideTrace.endSectionAsync(TAG, cookie);
}
在这个方法中,首先会判断是否设置了RequestListener,如果设置了那么就会调用这个接口的onResourceReady方法,最终也会调用target的onResourceReady方法。
其实target可以认为就是ImageView,因为在into中除了可以传值ImageView之外,还可以传值Target
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
setResourceInternal(resource);
} else {
maybeUpdateAnimatable(resource);
}
}
最终就是调用了setResourceInternal方法,通过调用ImageView的setImageBitmap或者setImageDrawable方法给ImageView显示图片。
protected void setResource(Bitmap resource) {
view.setImageBitmap(resource);
}
1.3.4 小结
其实在into中,我们可以分为以下几个大段:
(1)首先创建图片加载请求,其实就是创建了SingleRequest;
(2)判断当前是否能够执行请求(isPaused是否为false),如果能够发起请求,最终调用Engine的load方法;
(3)根据图片的信息生成EngineKey,并拿这个key分别从活动缓存、Lru中获取图片资源,如果获取到,直接回调onResourceReady;如果没有获取到,那么就发起网络请求获取资源,成功之后加入活跃缓存并回调onResourceReady。
(4)在SingleRequest的onResourceReady方法中,最终其实就是调用了ImageView的setImageBitmap方法或者setImageDrawable显示图片。
2 手写简单Glide框架
如果要实现Glide,那么就需要对Glide的特性有所了解,其中生命周期绑定、三级缓存是其最大的亮点,因此在实现时,也着重实现这两点。
2.1 生命周期绑定
/**
* 手写简易Glide图片加载框架
* 功能包括3大主线:with、load、into
* 支持生命周期绑定,监听生命周期变化
* 支持三级缓存
*/
object MyGlide:DefaultLifecycleObserver {
/**目的为了与页面的生命周期绑定*/
fun with(lifecycleOwner: LifecycleOwner):MyGlide{
/**这里和Glide不同的是,直接传入了LifecycleOwner*/
lifecycleOwner.lifecycle.addObserver(this)
return this
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
}
}
首先这里是跟Glide有点不一样,因为如果想要监听页面的生命周期,只需要在LifeCycle中将这个页面作为被观察,即可获取页面的实时状态,因此没有Glide那么繁琐。
fun load(url: String): MyGlide {
if (requestOption == null) {
requestOption = RequestOption()
}
requestOption?.params = url
return this
}
fun load(file: File): MyGlide {
if (requestOption == null) {
requestOption = RequestOption()
}
requestOption?.params = file
return this
}
fun load(drawableId: Drawable): MyGlide {
if (requestOption == null) {
requestOption = RequestOption()
}
requestOption?.params = drawableId
return this
}
然后load方法也是一个重载方法,支持多种资源加载,因此需要一个RequestOption实体来保存这些请求参数。
class RequestOption(
/**请求的参数类型*/
var params: Any? = null,
/**ImageView的ScaleType属性*/
val scaleType: ImageView.ScaleType = ImageView.ScaleType.FIT_XY,
/**图片的宽 高*/
val width: Int = 0,
val height: Int = 0
)
2.2 发起网络请求
其中into方法与Glide一致,也是需要传入ImageView控件,在load方法中传入的参数,在这个方法中会依次判断。
fun into(view: ImageView) {
if (requestOption == null) {
requestOption = RequestOption()
}
requestOption?.scaleType = view.scaleType
buildTargetRequest(requestOption, object : IRequestListener {
override fun onLoadStart() {
}
override fun onResourceReady(source: Bitmap?) {
if (isPause) {
return
}
/**只有在页面活跃的时候,才可以加载图片*/
if (source != null) {
view.setImageBitmap(source)
}
}
})
}
private fun buildTargetRequest(requestOption: RequestOption?, listener: IRequestListener) {
if (requestOption == null) return
/**判断请求的参数*/
when (requestOption.params) {
is String -> {
doNetRequest(requestOption.params as String, listener)
}
is File -> {
doFileRequest(requestOption.params as File, listener)
}
is Int -> {
doDrawableRequest(requestOption.params as Int)
}
/**其他类型暂不处理*/
else -> {
null
}
}
}
private fun doDrawableRequest(i: Int): Bitmap? {
return null
}
/**加载文件资源*/
private fun doFileRequest(file: File, listener: IRequestListener) {
val inputStream = FileInputStream(file)
listener.onResourceReady(BitmapFactory.decodeStream(inputStream))
}
/**如果是String字符串,那么就需要发起网络请求*/
private fun doNetRequest(url: String, listener: IRequestListener) {
/**在子线程中执行*/
CoroutineScope(Dispatchers.IO).launch(Dispatchers.IO) {
try {
val newURL = URL(url)
val connection = newURL.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.connectTimeout = 60 * 1000
connection.readTimeout = 20 * 1000
connection.doInput = true
Log.e("TAG", "code == ${connection.responseCode}")
connection.connect()
listener.onLoadStart()
/**获取图片资源流*/
val inputStream = connection.inputStream
withContext(Dispatchers.Main){
listener.onResourceReady(BitmapFactory.decodeStream(inputStream))
}
inputStream.close()
} catch (e: Exception) {
Log.e("TAG", "exp===>${e.message}")
}
}
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
isPause = true
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
isPause = false
}
在buildTargetRequest方法中,判断传入的类型,决定是否需要发起网络请求。这里有一点需要注意的就是,Glide中其实对资源获取采用三级缓存的方式,通过EngineKey来从内存中寻找,这里没有使用后续会补充上去。
还有一点 就是在进行网络请求的时候,需要注意主线程的切换,这里我没有使用线程池而是采用了协程的方式切换,接下来我们加载一张本地图片看下效果
MyGlide.with(this)
.load(File("/storage/emulated/0/src.webp"))
.into(iv_image)
我们使用Glide一样的API就能够加载一张本地图片。
MyGlide.with(this)
.load("https://图片url地址")
.into(iv_image)
使用url加载网络链接亦是如此