写在前面

Github–Glide 镇楼

欢迎光临下篇 ~

  • 目录

  • (上)

  • 1.代码实例

  • 2.GlideApp

  • 3.with

  • 4.监听生命周期

  • 5.load

  • 6.into

  • (下)

  • 1.资源加载和缓存机制

  • 2.TargetView

  • 3.Transform


资源加载和缓存机制

书接上文,讲到了into方法最后委托’RequestTracker #track’去获取资源:

  # RequestManagervoid track(@NonNull Target<?> target, @NonNull Request request) {targetTracker.track(target);// 交给RequestTracker去获取资源requestTracker.runRequest(request);}# RequestTrackerpublic 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");}// 将request加入到等待列表pendingRequests.add(request);}}

我们先看发起请求的部分request.begin(),这里的Request是一个接口,它有三个实现类:

我们以SingleRequest为例:

  @Overridepublic void begin() {// 判断:不可以在callback里调用// 您无法启动或清除RequestListener或Target回调中的负载// 如果尝试在加载失败时启动回退请求,请使用RequestBuilder #error(RequestBuilder)// 否则,请考虑使用处理程序将您的into()或clear()调用发布到主线程assertNotCallingCallbacks();// 如果我们认为对象已回收且处于非活动状态(即当前位于对象池中),则抛出异常stateVerifier.throwIfRecycled();startTime = LogTime.getLogTime();if (model == null) {// Util.isValidDimensions:如果宽度和高度均大于0 和/或 等于{@link Target #SIZE_ORIGINAL},则返回trueif (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.// 如果用户设置了后备可绘制对象,则仅以更详细的日志级别记录日志,因为后备可绘制对象指示用户偶尔需要回传nullint logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;// 1.log  2.向listeners传递onLoadFailed  3.setErrorPlaceholderonLoadFailed(new GlideException("Received null model"), logLevel);return;}// 如果当前正在加载,则抛出异常if (status == Status.RUNNING) {throw new IllegalArgumentException("Cannot restart a running request");}// 如果我们在完成之后重新启动(通常是通过诸如notifyDataSetChanged之类的操作,它会向相同的Target或View启动相同的请求)// 我们可以直接使用上次获取的资源和大小,而跳过获取新大小 (例如,开始新的加载等)// 这确实意味着要重新加载的用户,因为他们期望视图大小已更改,因此需要在开始新的加载之前显式清除View或Targetif (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)) {// View size已经记录,调用onSizeReady加载请求onSizeReady(overrideWidth, overrideHeight);} else {// 获取宽高// 若控件已添加,则获取宽高// 若未添加,设置ViewTreeObserver,回调时获取target.getSize(this);}if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)&& canNotifyStatusChanged()) {// 已经开始图片获取,通知进行预操作,例如设置placeHoldertarget.onLoadStarted(getPlaceholderDrawable());}if (IS_VERBOSE_LOGGABLE) {logV("finished run method in " + LogTime.getElapsedMillis(startTime));}}// 成功和失败的回调类似,举个成功的例子private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {// We must call isFirstReadyResource before setting status.boolean isFirstResource = isFirstReadyResource();status = Status.COMPLETE;this.resource = resource;··· // logisCallingCallbacks = true;try {// 调用listener的onResourceReadyboolean 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;}notifyLoadSuccess();}

图片资源的加载在获取到控件宽高后调用的onSizeReady(overrideWidth, overrideHeight)里执行:

  /*** A callback method that should never be invoked directly.*/@Overridepublic void onSizeReady(int width, int height) {// 如果我们认为对象已回收且处于非活动状态(即当前位于对象池中),则抛出异常stateVerifier.throwIfRecycled();··· // log// 必须已经获取到宽高if (status != Status.WAITING_FOR_SIZE) {return;}// 修改状态为runningstatus = Status.RUNNING;// 获取宽高float sizeMultiplier = requestOptions.getSizeMultiplier();this.width = maybeApplySizeMultiplier(width, sizeMultiplier);this.height = maybeApplySizeMultiplier(height, sizeMultiplier);··· // log// 加载图片 co co da yo ~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);// 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));}}

在看加载图片的代码前,我们先来看一下它的注释:

启动给定参数的加载,必须在主线程上调用
任何请求的流程如下:
1.检查当前活跃的资源,返回(如果存在),然后将所有新的非活跃的资源移动到内存缓存中
2.检查内存缓存并提供缓存的资源(如果存在)
3.检查当前一组正在进行的负载,并将cb添加到正在进行的负载中
4.开始新的加载

活跃的资源是指已提供给至少一个请求但尚未释放的资源。一旦资源的所有使用者都释放了该资源,该资源便进入缓存。如果资源曾经从缓存返回给新的使用者,则将其重新添加到活动资源中。如果将资源从缓存中逐出,则将对其资源进行回收并在可能的情况下重新使用,并丢弃该资源。没有严格的要求主动释放资源,有效资源的持有为弱引用

活跃的资源:当前正在被使用的资源

而后面图片的加载以及缓存机制,这里先给出一个总体的流程,方便后面的观看:Glide使用了四级缓存,活跃的资源、内存缓存、磁盘缓存、网络缓存。活跃的资源是指当前正在被使用的资源,因为内存缓存涉及到Lru算法,而正在被使用的资源优先级一定高于未被使用的,不应该被先释放,所以单独拿出来。后面的代码在加载图片资源时,也是按照这个顺序一步步找过去的。接下来我们继续看代码:

  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) {// UI线程Util.assertMainThread();long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;// 注释1 key// 因为图片加载的复用不仅仅与源有关,还与宽高等一系列参数有关,所以定义EngineKeyEngineKey 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);···return null;}// 从缓存的资源列表中读取EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);if (cached != null) {cb.onResourceReady(cached, DataSource.MEMORY_CACHE);···return null;}// 加载任务Map中是否有已完成的JobEngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);if (current != null) {current.addCallback(cb);···return new LoadStatus(cb, current);}// 未找到可复用资源// 新建EngineJob去获取资源// 这里的EngineJob是从Pool中获取到的 注释2EngineJob<R> engineJob =engineJobFactory.build(key,isMemoryCacheable,useUnlimitedSourceExecutorPool,useAnimationPool,onlyRetrieveFromCache);// 新建DecodeJob,解码Job// 获取方法与EngineJob类似,从另一个Pool中获取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);}注释1 # EngineKey@Overridepublic boolean equals(Object o) {if (o instanceof EngineKey) {EngineKey other = (EngineKey) o;return model.equals(other.model)&& signature.equals(other.signature)&& height == other.height&& width == other.width&& transformations.equals(other.transformations)&& resourceClass.equals(other.resourceClass)&& transcodeClass.equals(other.transcodeClass)&& options.equals(other.options);}return false;}注释2:见下图

接下来我们看一下engineJob.start(decodeJob):

  # EngineJobpublic void start(DecodeJob<R> decodeJob) {this.decodeJob = decodeJob;GlideExecutor executor = decodeJob.willDecodeFromCache()? diskCacheExecutor: getActiveSourceExecutor();executor.execute(decodeJob);}# DecodeJob/*** 如果job将尝试从磁盘缓存中解码资源,则返回true* 如果它将始终从source解码,则返回false*/boolean willDecodeFromCache() {Stage firstStage = getNextStage(Stage.INITIALIZE);return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;}// 获取下一个状态// 尝试获取的状态顺序:INITIALIZE -> RESOURCE_CACHE -> DATA_CACHE -> SOURCE// 最后归于Finishedprivate Stage getNextStage(Stage current) {switch (current) {case INITIALIZE:// 活跃的资源return diskCacheStrategy.decodeCachedResource()? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);case RESOURCE_CACHE:// 缓存的sourcereturn 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.// 缓存的原始datareturn onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;case SOURCE:case FINISHED:// 根据提供方式去自行获取 or 已完成return Stage.FINISHED;default:throw new IllegalArgumentException("Unrecognized stage: " + current);}}

接下来调用了DecodeJob的run方法

  @Overridepublic void run() {// 通知开发者sth is failing,防止异常被吞GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);// try语句中的方法可能会使currentFetcher无效,因此请在此处设置一个局部变量,以确保以任何一种方式清除了fetcher// 对应finally中的操作DataFetcher<?> localFetcher = currentFetcher;try {if (isCancelled) {notifyFailed();return;}// run的包装方法runWrapped();} catch (Throwable t) {// 捕获Throwable而不是异常以处理OOM// 我们在GlideExecutor中使用.submit()吞没了Throwables,因此我们不会通过这样做来无声地隐藏崩溃// 但是,我们确保在加载失败时始终通知我们的回调// 没有此通知,未捕获的throwables将永远不会通知相应的回调,这可能导致负载永久性地挂起// 这种情况对于在后台线程上使用Future的用户而言尤其糟糕if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "DecodeJob threw unexpectedly"+ ", isCancelled: " + isCancelled+ ", stage: " + stage, t);}// 编码时,我们已经通知了回调,因此再次这样做是不安全的if (stage != Stage.ENCODE) {throwables.add(t);notifyFailed();}if (!isCancelled) {throw t;}} finally {// Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call// close in all cases anyway.if (localFetcher != null) {// 清理或回收此数据获取器使用的任何资源// 在{@link #loadData(Priority,DataCallback)}提供的数据已由{@link ResourceDecoder}解码之后,将在finally块中调用此方法。localFetcher.cleanup();}GlideTrace.endSection();}}private void runWrapped() {switch (runReason) {case INITIALIZE:// get stagestage = getNextStage(Stage.INITIALIZE);// 获取generatorcurrentGenerator = getNextGenerator();// 执行generatorrunGenerators();break;case SWITCH_TO_SOURCE_SERVICE:runGenerators();break;case DECODE_DATA:decodeFromRetrievedData();break;default:throw new IllegalStateException("Unrecognized run reason: " + runReason);}}

DecodeJob的执行是通过generator来进行四级缓存遍历的:

  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);}}private void runGenerators() {currentThread = Thread.currentThread();startFetchTime = LogTime.getLogTime();boolean isStarted = false;// while判断条件中的currentGenerator.startNext()是真正的执行// 尝试一个新的{@link DataFetcher},如果启动了{@link DataFetcher},则返回true,否则返回falsewhile (!isCancelled && currentGenerator != null&& !(isStarted = currentGenerator.startNext())) {// 当前generator未启动,抬走换下一个stage = getNextStage(stage);currentGenerator = getNextGenerator();if (stage == Stage.SOURCE) {// sourcereschedule();return;}}// We've run out of stages and generators, give up.if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {// 都抬走了,失败了notifyFailed();}// Otherwise a generator started a new load and we expect to be called back in// onDataFetcherReady.}

generator的startNext()函数是执行函数,generator的实现有三个:

按照访问的顺序分别是ResourceCacheGenerator、DataCacheGenerator、SourceGenerator。这里先插一点:后文generator中使用了ModelLoader,把我看晕了。。。于是我在网上找了一篇写的很棒的解析:Glide 4.x之ModelLoader简单分析—chunqiuwei,这部分解析就先放在这里,等以后有时间会来填这个坑 ~ 总结来说,就是Glide在初始化时搞了个Registry对象,这个对象将不同的Class类型和对应的工厂类的对应关系保存了起来;等到要用的时候可以获取到ModelLoader对象,通过这个对象创建LoadData对象,这个LoadData对象实现了实际的获取方式(其实也没有,也调用了别的)。

首先看一下ResourceCacheGenerator(DataCacheGenerator与这个类似):

  @SuppressWarnings("PMD.CollapsibleIfStatements")@Overridepublic boolean startNext() {// 获取Keys,加了一层缓存方便多次获取List<Key> sourceIds = helper.getCacheKeys();if (sourceIds.isEmpty()) {return false;}// 获取资源类型List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();if (resourceClasses.isEmpty()) {if (File.class.equals(helper.getTranscodeClass())) {return false;}···}// 寻找ModelLoaderwhile (modelLoaders == null || !hasNextModelLoader()) {// source是一级,resource是二级resourceClassIndex++;if (resourceClassIndex >= resourceClasses.size()) {sourceIdIndex++;if (sourceIdIndex >= sourceIds.size()) {return false;}resourceClassIndex = 0;}// 生成新的currentKeyKey sourceId = sourceIds.get(sourceIdIndex);Class<?> resourceClass = resourceClasses.get(resourceClassIndex);Transformation<?> transformation = helper.getTransformation(resourceClass);currentKey =new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoopshelper.getArrayPool(),sourceId,helper.getSignature(),helper.getWidth(),helper.getHeight(),transformation,resourceClass,helper.getOptions());cacheFile = helper.getDiskCache().get(currentKey);// 获取ModelLoaderif (cacheFile != null) {sourceKey = sourceId;modelLoaders = helper.getModelLoaders(cacheFile);modelLoaderIndex = 0;}}loadData = null;boolean started = false;while (!started && hasNextModelLoader()) {ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);// 构建loadData对象:如上文描述,用于图片加载loadData = modelLoader.buildLoadData(cacheFile,helper.getWidth(), helper.getHeight(), helper.getOptions());if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {started = true;// 加载图片loadData.fetcher.loadData(helper.getPriority(), this);}}return started;}

再来看一下SourceGenerator:

  @Overridepublic boolean startNext() {// 保存数据:该变量在onDataReady(Object data)中赋值,用于保存数据流if (dataToCache != null) {Object data = dataToCache;dataToCache = null;cacheData(data);}// 在获取数据后,SourceGenerator在缓存数据后会新建一个DataCacheGenerator,即该对象if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {return true;}sourceCacheGenerator = null;loadData = null;boolean started = false;while (!started && hasNextModelLoader()) {// 遍历LoadDataloadData = helper.getLoadData().get(loadDataListIndex++);if (loadData != null&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {started = true;// load dataloadData.fetcher.loadData(helper.getPriority(), this);}}return started;}# HttpUrlFetcher@Overridepublic 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);} 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));}}}// 发起Http请求获取图片输入流private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,Map<String, String> headers) throws IOException {··· // redirect逻辑// 构建http请求,设置参数urlConnection = connectionFactory.build(url);for (Map.Entry<String, String> headerEntry : headers.entrySet()) {urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());}urlConnection.setConnectTimeout(timeout);urlConnection.setReadTimeout(timeout);urlConnection.setUseCaches(false);urlConnection.setDoInput(true);// 通过对该方法的递归调用来处理重定向urlConnection.setInstanceFollowRedirects(false);// ConnecturlConnection.connect();// get streamstream = urlConnection.getInputStream();if (isCancelled) {return null;}final int statusCode = urlConnection.getResponseCode();if (isHttpOk(statusCode)) {// 请求成功return getStreamForSuccessfulRequest(urlConnection);} else if (isHttpRedirect(statusCode)) {// 重定向String redirectUrlString = urlConnection.getHeaderField("Location");if (TextUtils.isEmpty(redirectUrlString)) {throw new HttpException("Received empty or null redirect url");}URL redirectUrl = new URL(url, redirectUrlString);// Closing the stream specifically is required to avoid leaking ResponseBodys in addition// to disconnecting the url connection below. See #2352.cleanup();return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);} else if (statusCode == INVALID_STATUS_CODE) {throw new HttpException(statusCode);} else {throw new HttpException(urlConnection.getResponseMessage(), statusCode);}}// 获取到InputStream后回调到# SourceGenerator@Overridepublic void onDataReady(Object data) {DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {// 这里就是刚才SourceGenerator的startNext开头处,判断是否为空的dataToCachedataToCache = data;// 调用cb的reschedulecb.reschedule();} else {cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,loadData.fetcher.getDataSource(), originalKey);}}

这里的cb就是DecodeJob的getNextGenerator()中生成SourceGenerator传入的this,即DecodeJob本身。

  # DecodeJob@Overridepublic void reschedule() {runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;callback.reschedule(this);}

这里的callback是在DecodeJob实例化时传入的,参数是EngineJob:

  # EngineJob@Overridepublic 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的run方法,区别在于此时的runReason = RunReason.SWITCH_TO_SOURCE_SERVICE,且获取到的数据保存在dataToCache中。

  # DecodeJobprivate void runWrapped() {switch (runReason) {···case SWITCH_TO_SOURCE_SERVICE:runGenerators();break;···}}# SourceGenerator@Overridepublic boolean startNext() {if (dataToCache != null) {Object data = dataToCache;dataToCache = null;cacheData(data);}if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {return true;}sourceCacheGenerator = null;···}

这里最后调用了DataCacheGenerator的startNext,其对应的loadData为FileLoader,继而调用了其内部实现的FileFetcher的loadData:

    @Overridepublic void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {try {// 注释1 获取InputStreamdata = opener.open(file);} catch (FileNotFoundException e) {if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "Failed to open file", e);}callback.onLoadFailed(e);return;}// 回调callback.onDataReady(data);}@Overridepublic void onDataReady(Object data) {cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);}注释1:# StreamFactorypublic static class StreamFactory extends FileLoader.Factory<InputStream> {public StreamFactory() {super(new FileLoader.FileOpener<InputStream>() {public InputStream open(File file) throws FileNotFoundException {return new FileInputStream(file);}public void close(InputStream inputStream) throws IOException {inputStream.close();}public Class<InputStream> getDataClass() {return InputStream.class;}});}}
接着cb的onDataReady会调用到SourceGenerator再到DecodeJob:

  # DecodeJob@Overridepublic void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,DataSource dataSource, Key attemptedKey) {// 保存keythis.currentSourceKey = sourceKey;// 保存InputStreamthis.currentData = data;// 保存FileFetcherthis.currentFetcher = fetcher;// 保存数据来源,此处为DataSource.DATA_DISK_CACHEthis.currentDataSource = dataSource;this.currentAttemptingKey = attemptedKey;if (Thread.currentThread() != currentThread) {// 如果不等于当前线程// 设置当前运行状态为图片解码runReason = RunReason.DECODE_DATA;callback.reschedule(this);} else {GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");try {// 从图片输入流中对图片进行解码decodeFromRetrievedData();} finally {GlideTrace.endSection();}}}private void decodeFromRetrievedData() {··· // logResource<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();}}// notifyEncodeAndRelease会调用notifyComplete然后调用到EngineJob的onResourceReady# EngineJob@Overridepublic void onResourceReady(Resource<R> resource, DataSource dataSource) {this.resource = resource;this.dataSource = dataSource;MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();}// 这里通过MAIN_THREAD_HANDLER发送消息,最后会调用到SingleRequest的onResourceReady# SingleRequest@SuppressWarnings("unchecked")@Overridepublic void onResourceReady(Resource<?> resource, DataSource dataSource) {···onResourceReady((Resource<R>) resource, (R) received, dataSource);}private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {···try {···if (!anyListenerHandledUpdatingTarget) {Transition<? super R> animation =animationFactory.build(dataSource, isFirstResource);// 这里的target,我们以ImageViewTarget为例target.onResourceReady(result, animation);}} finally {isCallingCallbacks = false;}notifyLoadSuccess();}# ImageViewTarget@Overridepublic 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);}# BitmapImageViewTarget@Overrideprotected void setResource(Bitmap resource) {view.setImageBitmap(resource);}

至此,Glide的图片加载流程就结束了。从Generator部分开始,源码看的就有些头昏脑胀了,已经开始配合着网上大佬的blog开始捋源码的逻辑,看完之后感觉一切都很清晰了。这里贴出大佬的博客:Glide解析一:Glide整体流程—jxiang112


TargetView

Glide的目标控件是封装在ImageViewTarget中的,ImageViewTarget extends ViewTarget、ViewTarget extends BaseTarget、BaseTarget implements Target。
在一层层的封装之下,实现了
1.对目标View的状态(是否attach)监控并同时控制Request
2.对动画效果的控制
3.获取目标View的大小
4.设置placeholder、error等

千万利器莫过于撕破伤口丶

撕,从最底层开始撕 ~

Target

Glide可以在加载期间将资源加载到该接口并通知相关生命周期事件的接口。

此类中的生命周期事件如下:onLoadStarted onResourceReady onLoadCleared onLoadFailed
典型的生命周期是onLoadStarted-> onResourceReady/onLoadFailed-> onLoadCleared。
但是,如果资源位于内存中或由于model为空而导致加载失败,则可能不会调用onLoadStarted。
如果从未清除目标,则可能永远不会调用onLoadCleared。

public interface Target<R> extends LifecycleListener {int SIZE_ORIGINAL = Integer.MIN_VALUE;/*** 启动加载时调用的生命周期回调* * 注1-可能不会每次加载都调用此方法,例如,加载开始之前失败(model为null时)* 注2-在调用任何其他生命周期方法之前,可以多次调用此方法* 由于生命周期或连接性事件,可能会暂停和重新启动加载,每次重新启动都可能会在此处引起调用*/void onLoadStarted(@Nullable Drawable placeholder);/*** 加载失败时调用的强制生命周期回调** 注-如果model为null,则可以在{@link #onLoadStarted(Drawable)}之前调用* 必须确保当前在重绘容器(通常是视图)或更改其可见性之前,不再使用{@link #onResourceReady(Object,Transition)}中收到的任何Drawable*/void onLoadFailed(@Nullable Drawable errorDrawable);/*** 资源加载完成后将调用的方法*/void onResourceReady(@NonNull R resource, @Nullable Transition<? super R> transition);/*** 强制性生命周期回调,在取消加载并释放其资源时调用** 必须确保当前在重绘容器(通常是视图)或更改其可见性之前,不再使用{@link #onResourceReady(Object,Transition)}中收到的任何Drawable*/void onLoadCleared(@Nullable Drawable placeholder);···/*** 获取当前Target的加载请求,不应在Glide之外调用*/@NullableRequest getRequest();
}public interface LifecycleListener {void onStart();void onStop();void onDestroy();
}
BaseTarget

BaseTarget是一个抽象类,它为Target的大多数方法实现了空实现,增加了一个Request变量:

  private Request request;@Overridepublic void setRequest(@Nullable Request request) {this.request = request;}
ViewTarget

用于将Bitmaps加载到View中的基本Target,它为大多数方法提供了默认实现,并可以使用OnDrawListener确定视图的大小。

要检测ListView或任何重复使用View的ViewGroup中的View重用,此类使用View#setTag(Object)方法存储一些元数据,以便视图被重用,任何先前的load或来自先前load的资源都可以取消或重新使用。

在给此类的视图上对View#setTag(Object)的任何调用都将导致过多的分配 和/或 IllegalArgumentException。如果必须在View上调用View#setTag(Object),请使用#setTagId(int)指定供Glide使用的自定义标签。

子类必须在#onLoadCleared(Drawable)中调用super。

我们来看ViewTarget的这两个变量:

  private final SizeDeterminer sizeDeterminer;@Nullableprivate OnAttachStateChangeListener attachStateListener;

OnAttachStateChangeListener负责监听View是否附着到界面上,而它的具体实现也是根据attach和detach 去 begin和pause load。

SizeDeterminer是在ViewTarget中定义的一个内部类,用于获取控件的大小。它其中有一个方法是在当前View未绘制前要获取大小:

  void getSize(@NonNull SizeReadyCallback cb) {// 获取宽高int currentWidth = getTargetWidth();int currentHeight = getTargetHeight();if (isViewStateAndSizeValid(currentWidth, currentHeight)) {cb.onSizeReady(currentWidth, currentHeight);return;}// We want to notify callbacks in the order they were added and we only expect one or two// callbacks to be added a time, so a List is a reasonable choice.if (!cbs.contains(cb)) {cbs.add(cb);}// 通过ViewTreeObserver监听,当View要被绘制时,获取大小并回调if (layoutListener == null) {ViewTreeObserver observer = view.getViewTreeObserver();layoutListener = new SizeDeterminerLayoutListener(this);observer.addOnPreDrawListener(layoutListener);}}
ImageViewTarget

实现了onLoadXXX系列函数、onResourceReady函数,增加了一个Animatable对象(动画)。将onLoadXXX函数最后归于protected abstract void setResource(@Nullable Z resource)

  @Overridepublic void onLoadStarted(@Nullable Drawable placeholder) {super.onLoadStarted(placeholder);setResourceInternal(null);setDrawable(placeholder);}private void setResourceInternal(@Nullable Z resource) {setResource(resource);···}
BitmapImageViewTarget

实现了setResource方法。

  @Overrideprotected void setResource(Bitmap resource) {view.setImageBitmap(resource);}

Transform

transform()的作用就是改变原始资源在客户端上最终的展现结果,比如我们在加载图片时希望它有圆角、或是设置与控件大小不同时的适配规则。网上有一个提供了许多自定义Transform的开源库:wasabeef/glide-transformations

首先我们来看一下,设置Transform的过程,以CenterCrop为例:

  # GildeRequest@NonNull@CheckResultpublic GlideRequest<TranscodeType> centerCrop() {if (getMutableOptions() instanceof GlideOptions) {this.requestOptions = ((GlideOptions) getMutableOptions()).centerCrop();} else {this.requestOptions = new GlideOptions().apply(this.requestOptions).centerCrop();}return this;}# GlideOptions@Override@NonNull@CheckResultpublic final GlideOptions centerCrop() {return (GlideOptions) super.centerCrop();}# RequestOptions@NonNull@CheckResultpublic RequestOptions centerCrop() {return transform(DownsampleStrategy.CENTER_OUTSIDE, new CenterCrop());}@NonNullprivate RequestOptions transform(@NonNull Transformation<Bitmap> transformation, boolean isRequired) {if (isAutoCloneEnabled) {return clone().transform(transformation, isRequired);}// 注释1DrawableTransformation drawableTransformation =new DrawableTransformation(transformation, isRequired);// 注释2transform(Bitmap.class, transformation, isRequired);transform(Drawable.class, drawableTransformation, isRequired);// TODO: 删除BitmapDrawable解码器和此转换。// 注册为BitmapDrawable只是为了避免重复而进行的优化,并且稍后获取转换时会进行isAssignableFrom检查。 // 可以删除它而不影响功能。transform(BitmapDrawable.class, drawableTransformation.asBitmapDrawable(), isRequired);transform(GifDrawable.class, new GifDrawableTransformation(transformation), isRequired);return selfOrThrowIfLocked();}注释1:# DrawableTransformation// 1.流程// 首先尝试将{@link Drawable}转换为{@link Bitmap},// 然后在转换后的Bitmap上运行{@link Transformation}//// 此类对于{@link BitmapDrawable}来说相对有效,在{@link BitmapDrawable}可以方便地访问。// 对于基于非{@link Bitmap}的{@link Drawable},此类必须首先尝试使用{@link Canvas}将{@link Drawable}绘制到{@link Bitmap},这效率较低。// 实现{@link Animatable}的{@link Drawable}会失败,并发生异常。// 如果请求的大小为{@link Target#SIZE_ORIGINAL},则{@link Drawable}的{@link Drawable#getIntrinsicHeight()}和/或{@link Drawable#getIntrinsicWidth()}返回<= 0的情况将失败,并发生异常。 // 使用{@link Transformation#transform(Context,Resource,int,int)}中提供的尺寸绘制没有内部尺寸的{@link Drawable}。结果,它们可能会错误地或以意想不到的方式转换。//// 2.代码// DrawableTransformation将·Transformation<Bitmap> wrapped·保存起来,在其实现的transform()函数中调用:@NonNull@Overridepublic Resource<Drawable> transform(@NonNull Context context,@NonNull Resource<Drawable> resource, int outWidth, int outHeight) {// 从BitmapPool中复用BitmapPool bitmapPool = Glide.get(context).getBitmapPool();Drawable drawable = resource.get();// 获取BitmapResource<Bitmap> bitmapResourceToTransform =DrawableToBitmapConverter.convert(bitmapPool, drawable, outWidth, outHeight);// 如果无法转换为Bitmap,则无法进行transformif (bitmapResourceToTransform == null) {if (isRequired) {throw new IllegalArgumentException("Unable to convert " + drawable + " to a Bitmap");} else {return resource;}}// 调用wrapped的transformResource<Bitmap> transformedBitmapResource =wrapped.transform(context, bitmapResourceToTransform, outWidth, outHeight);if (transformedBitmapResource.equals(bitmapResourceToTransform)) {transformedBitmapResource.recycle();return resource;} else {return newDrawableResource(context, transformedBitmapResource);}}注释2:// 保存transformation@NonNullprivate <T> RequestOptions transform(@NonNull Class<T> resourceClass,@NonNull Transformation<T> transformation,boolean isRequired) {if (isAutoCloneEnabled) {return clone().transform(resourceClass, transformation, isRequired);}// check not nullPreconditions.checkNotNull(resourceClass);Preconditions.checkNotNull(transformation);// 保存transformationtransformations.put(resourceClass, transformation);// 标志位记录fields |= TRANSFORMATION;isTransformationAllowed = true;fields |= TRANSFORMATION_ALLOWED;// Always set to false here. Known scale only transformations will call this method and then// set isScaleOnlyOrNoTransform to true immediately after.isScaleOnlyOrNoTransform = false;if (isRequired) {fields |= TRANSFORMATION_REQUIRED;isTransformationRequired = true;}return selfOrThrowIfLocked();}

接着我们来看一下Transform应用的地方。这里我们要回顾一下上文中讲到的,图片网络加载之后到ImageView设置图片这部分:当通过HttpUrlFetcher获取到图片InputStream后,又会回调回DecodeJob中,文章位置跳转在这里:跳转回上文代码处。

  @Overridepublic void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,DataSource dataSource, Key attemptedKey) {···if (Thread.currentThread() != currentThread) {···} else {···try {decodeFromRetrievedData();} finally {···}}}// 调用的过程很长,这里不贴代码里,只把函数名贴出来:// decodeFromRetrievedData -> onDataFetcherReady -> decodeFromData -> decodeFromFetcher // -> runLoadPath -> LoadPath #load -> loadWithExceptionList -> DecodePath #decode // -> DecodeJob #onResourceDecoded# DecodeJob@Synthetic@NonNull<Z> Resource<Z> onResourceDecoded(DataSource dataSource,@NonNull Resource<Z> decoded) {@SuppressWarnings("unchecked")Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();Transformation<Z> appliedTransformation = null;Resource<Z> transformed = decoded;if (dataSource != DataSource.RESOURCE_DISK_CACHE) {appliedTransformation = decodeHelper.getTransformation(resourceSubClass);// 调用Transform的transform()transformed = appliedTransformation.transform(glideContext, decoded, width, height);}···Resource<Z> result = transformed;···return result;}

写在后面

至此Glide的流程就分析完了 ~

如果文章中有错误的地方,希望各位大佬们批评指正~

If you like this article, it is written by Johnny Deng.
If not, I don’t know who wrote it.

图片加载—Glide为什么这么强?Glide源码分析(下)相关推荐

  1. Android图片加载框架:玩转Glide的回调与监听

    回调的源码实现 作为一名Glide老手,相信大家对于Glide的基本用法已经非常熟练了.我们都知道,使用Glide在界面上加载并展示一张图片只需要一行代码: Glide.with(this).load ...

  2. Android一行代码实现网络加载GIF闪图(附源码)

    最近项目有个需求是要从网络加载GIF闪图, 但是Android原生的ImageView并不支持Gif... 于是从网上看了些Dome, 发现总是有些这样那样的问题, 譬如: ☹ 没有缓存,还要自己写一 ...

  3. iOS自定义弹出视图、收音机APP、图片涂鸦、加载刷新、文件缓存等源码

    iOS精选源码 一款优秀的 聆听夜空FM 源码 zhPopupController 简单快捷弹出自定义视图 WHStoryMaker搭建美图(贴纸,涂鸦,文字,滤镜) iOS cell高度自适应 有加 ...

  4. iOS自定义弹出视图、收音机APP、图片涂鸦、加载刷新、文件缓存等源码 1

    iOS精选源码 一款优秀的 聆听夜空FM 源码 zhPopupController 简单快捷弹出自定义视图 WHStoryMaker搭建美图(贴纸,涂鸦,文字,滤镜) iOS cell高度自适应 有加 ...

  5. Java是如何加载资源文件的?(源码解毒)

    上文提到应老板要求开发一个测试工具能方便的加载存于文件中的测试参数,当时考虑既然是测试,把测试参数文件和测试类放在一起岂不是很方便,但是老板说:我的需求是你把测试参数文件放到统一文件夹下比如resou ...

  6. react.lazy 路由懒加载_React lazy/Suspense使用及源码解析

    React v16.6.0已经发布快一年了,为保障项目迭代发布,没有及时更新react版本,最近由于开启了新项目,于是使用新的react版本进行了项目开发.项目工程如何搭建,如何满足兼容性要求,如何规 ...

  7. 免Root 实现App加载Xposed插件的工具Xpatch源码解析(一)

    前言 Xpatch是一款免Root实现App加载Xposed插件的工具,可以非常方便地实现App的逆向破解(再也不用改smali代码了),源码也已经上传到Github上,欢迎各位Fork and St ...

  8. 网页中加载二次元3D虚拟主播源码(1:项目介绍和源码)

    vrm格式的二次元3D虚拟主播在日本实际上已经盛行多年,由于文化和差异的原因,在我们这只有年轻人比较喜爱.今天我们讲的是如何加载这种模型,然后实现一些动画. 别的不说,我们先上效果视频: 3D二次元虚 ...

  9. android音乐播放器开发在线加载歌词,android开发计算器源码

    import android.util.Log; public class OnlineLrcUtil { private static String TAG = "OnlineLrcUti ...

  10. 人脸检测(十)--强分类器源码分析

    原文: http://blog.csdn.net/beerbuddys/article/details/40712957 下面的内容很长,倒杯水(有茶或者咖啡更好),带上耳机,准备就绪再往下看.下面我 ...

最新文章

  1. 化敌为友 运营商组团拥抱OTT为哪般
  2. list group by java_Java List集合实现MySQL Group By功能
  3. 图灵奖得主门徒、RISC-V 创始成员领衔,睿思芯科获数千万美金融资 | AI 创业周报第6期...
  4. HDFS只支持文件append操作, 而依赖HDFS的HBase如何完成增删改查功能
  5. TensorFlow随笔-多分类单层神经网络softmax
  6. 【天池赛事】零基础入门语义分割-地表建筑物识别 Task6:分割模型模型集成
  7. 第六章 SpringCloud之Ribbon负载均衡
  8. ubuntu下面安装Keil uvision4与入门实例
  9. 【机器学习】脑机接口利器:错误率仅3%
  10. java 线程安全的原因_java的多线程:java安全问题产生的原因与JMM的关系
  11. 高维(多变量)优化问题的技术与瓶颈
  12. 视频教程-毕业设计精品课之基于ASP.NET旅游网站源码实战讲解(带房屋互租模块)-.NET
  13. 山东计算机软考题库,软考习题库
  14. python学习笔记——取矩阵的上三角或下三角元素
  15. 人人都是产品经理 读后感
  16. 杰理之ANC降噪【篇】
  17. arcgis10之获取面要素四至点坐标
  18. 奇奇邮件群发助手V2.0使用说明
  19. 一张图带你看完图论第一章(包含定义、定理、公式、推导证明和例题)
  20. 使用GerberTools的Gerber Panelizer工具进行gerber文件拼板的方法

热门文章

  1. 【OpenCV C++】分离颜色通道多通道图像混合
  2. 【面经】百度NLP算法实习生--9.17
  3. Inno Setup打包实现安装自启和开机自启
  4. 四:es聚和函数Aggregations
  5. Vue3 + vueRouter4.x 控制台No match found for location with path ‘/home‘ 解决
  6. WL2803E25-5/TR超低压差 低压静态电流 高PSRR CMOS LDO
  7. Linux下的常见指令以及权限理解(下)
  8. php富友接口对接http请求
  9. 通过IPV6上QQ及自建IPV6代理的方法
  10. selenium 酷狗音乐搜索歌曲播放