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加载网络链接亦是如此