软件编程
位置:首页>> 软件编程>> Android编程>> Android图片框架Glide原理深入探索

Android图片框架Glide原理深入探索

作者:明智的健哥  发布时间:2021-07-22 05:08:42 

标签:Android,Glide,原理

首先引入依赖

    implementation 'com.github.bumptech.glide:glide:4.12.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'

下面一行代码,就是Glide最简单的使用方式了

Glide.with(this).load(url).into(imageView)

with

首先,我们来看with,其实with的功能就是根据传入的context来获取图片请求管理器RequestManager,用来启动和管理图片请求。

public static RequestManager with(@NonNull FragmentActivity activity) {
   return getRetriever(activity).get(activity);
 }

context可以传入app,activity和fragment,这关系着图片请求的生命周期。通常使用当前页面的context,这样当我们打开一个页面加载图片,然后退出页面时,图片请求会跟随页面销毁而被取消,而不是继续加载浪费资源。

当context是app时,获得的RequestManager是一个全局单例,图片请求的生命周期会跟随整个app。

注意:如果with发生在子线程,不管context是谁,都返回应用级别的RequestManager单例。

private RequestManager getApplicationManager(@NonNull Context context) {
   // Either an application context or we're on a background thread.
   if (applicationManager == null) {
     synchronized (this) {
       if (applicationManager == null) {
         // Normally pause/resume is taken care of by the fragment we add to the fragment or
         // activity. However, in this case since the manager attached to the application will not
         // receive lifecycle events, we must force the manager to start resumed using
         // ApplicationLifecycle.
         // TODO(b/27524013): Factor out this Glide.get() call.
         Glide glide = Glide.get(context.getApplicationContext());
         applicationManager =
             factory.build(
                 glide,
                 new ApplicationLifecycle(),
                 new EmptyRequestManagerTreeNode(),
                 context.getApplicationContext());
       }
     }
   }
   return applicationManager;
 }

当context是Activity时,会创建一个 * 面的fragment添加到Activity,用于感知Activity的生命周期,同时创建RequestManager给该fragment持有。

private RequestManager supportFragmentGet(
     @NonNull Context context,
     @NonNull FragmentManager fm,
     @Nullable Fragment parentHint,
     boolean isParentVisible) {
   SupportRequestManagerFragment current = getSupportRequestManagerFragment(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;
 }

load

load方法会得到一个图片请求构建器RequestBuilder,用来创建图片请求。

public RequestBuilder<Drawable> load(@Nullable String string) {
   return asDrawable().load(string);
 }

into

首先是根据ImageView的ScaleType,来配置参数

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,会创建图片请求,获取Target载体已有的请求,对比两个请求,如果等效,启动异步请求。然后,图片载体绑定图片请求,也就是imageView setTag为request

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()");
   }
   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.setRequest(request);
   requestManager.track(target, request);
   return target;
 }

继续跟进异步请求 requestManager.track(target, request)

synchronized 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);//如果是暂停状态,就把请求存起来
   }
 }

到这里就启动了图片请求了,我们继续跟进request.begin()

public void begin() {
   synchronized (requestLock) {
     //......
     if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
     //如果有尺寸,开始加载
       onSizeReady(overrideWidth, overrideHeight);
     } else {
     //如果无尺寸就先去获取
       target.getSize(this);
     }
     //......
   }
 }

然后继续瞧瞧onSizeReady

public void onSizeReady(int width, int height) {
   stateVerifier.throwIfRecycled();
   synchronized (requestLock) {
   //......
     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);
     //......
   }
 }

跟进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,
     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);
     }
   }
   cb.onResourceReady(
       memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
   return null;
 }
private <R> LoadStatus waitForExistingOrStartNewJob(
     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,
     EngineKey key,
     long startTime) {
   EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
   if (current != null) {
     current.addCallback(cb, callbackExecutor);
     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, callbackExecutor);
   engineJob.start(decodeJob);
   if (VERBOSE_IS_LOGGABLE) {
     logWithTimeAndKey("Started new load", startTime, key);
   }
   return new LoadStatus(cb, engineJob);
 }

DecodeJob是一个Runnable,它通过一系列的调用,会来到HttpUrlFetcher的loadData方法

public void loadData(
     @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
   long startTime = LogTime.getLogTime();
   try {
     //获取输入流,此处使用的是HttpURLConnection
     InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
     //回调出去
     callback.onDataReady(result);
   } catch (IOException e) {
     if (Log.isLoggable(TAG, Log.DEBUG)) {
       Log.d(TAG, "Failed to load data for url", e);
     }
     callback.onLoadFailed(e);
   } finally {
     if (Log.isLoggable(TAG, Log.VERBOSE)) {
       Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
     }
   }
 }

至此,网络请求结束,最后把图片设置上去就行了,在SingleRequest的onResourceReady方法,它会把结果回调给Target载体

target.onResourceReady(result, animation);

继续跟进它,最终会执行setResource,把图片设置上去

protected void setResource(@Nullable Drawable resource) {
   view.setImageDrawable(resource);
 }

原理总结

with根据传入的context来获取图片请求管理器RequestManager,当传入的context是App时,获得的RequestManager是一个全局单例,图片请求的生命周期会跟随这个应用,当传入的是Activity时,会创建一个 * 面的空fragment添加到Activity,用来感知Activity的生命周期。load会得到了一个图片请求构建器RequestBuilder,用来创建图片请求。into开启加载,先会根据ImageView的ScaleType来配置参数,创建图片请求,图片载体绑定图片请求,然后开启图片请求,先从内存中加载,如果内存里没有,会创建一个Runnable,通过一系列的调用,使用HttpURLConnection获取网络输入流,把结果回调出去,最后把回调结果设置上去就OK了。

缓存

Glide * 缓存原理:读取一张图片时,顺序是: 弱引用缓存,LruCache,磁盘缓存。

用Glide加载某张图片时,先去弱引用缓存中寻找图片,如果有则直接取出来使用,如果没有,则去LruCache中寻找,如果LruCache中有,则从中取出图片使用,并将它放入弱引用缓存中,如果都没有图片,则从磁盘缓存或网络中加载图片。

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;
   }
   EngineResource<?> cached = loadFromCache(key); //从LruCache获取缓存图片
   if (cached != null) {
     if (VERBOSE_IS_LOGGABLE) {
       logWithTimeAndKey("Loaded resource from cache", startTime, key);
     }
     return cached;
   }
   return null;
 }

不过,这也会产生一个问题,就是Glide加载图片时,URL不变但是图片变了,这种情况会还是以前的旧图片。因为Glide加载图片会将图片缓存到本地,如果url不变则直接读取缓存不会再网络加载。

解决方案:

清除缓存让后台每次都更改图片的名字图片地址选用 &rdquo;url?key="+随机数这种格式

LruCache

LruCache就是维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。其内部维护了一个集合LinkedHashMap,LinkHashMap继承HashMap,在HashMap的基础上,新增了双向链表结构,每次访问数据的时候,会更新被访问数据的链表指针,该LinkedHashMap是以访问顺序排序的,当调用put()方法时,就会在集合中添加元素,判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头。

那么,问题来了,如果把一个(100 * 100)的图片放到(800 * 800)的Imageview中会怎么样呢?由上可知,Glide会为每个不同尺寸的Imageview缓存一张图片,也就是说不管这张图片有没有加载过,只要Imageview的尺寸不一样,Glide就会重新加载一次,这时候,它会在加载的Imageview之前从网络上重新下载,然后再缓存。举个例子,如果一个页面的Imageview是100 * 100,另一个页Imageview是800 * 800,它俩展示同一张图片的话,Glide会下载两次图片,并且缓存两张图片,因为Glide缓存Key的生成条件之一就是控件的长宽。

除了缓存,Glide还有一点我觉得做的非常好,就是在图片加载中关闭页面,此页面也不会造成内存泄漏,因为Glide在加载资源的时候,如果是在 Activity,Fragment 这类有生命周期的组件上进行的话,会创建一个 * 面的Fragment加入到FragmentManager之中,感知生命周期,当 Activity,Fragment进入不可见,或者已经销毁的时候,Glide会停止加载资源。但是如果,是在非生命周期的组件上进行时,会采用Application的生命周期贯穿整个应用,此时只有在应用程序关闭的时候才会停止加载。

来源:https://blog.csdn.net/qq_45485851/article/details/122010255

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com