简述
Glide图片加载框架通过into方法为视图设置图片,在《Glide v4 源码浅析(2)-load方法与Registry说明》中通过load方法获得了一个RequestBuilder对象,这里将调用它的into方法传入ImageView,开始加载资源并显示在ImageView上。
into中的执行流程很长,这里分成上下两部分。上部分主要分析拉取源数据的过程,下部分记录对数据进行解码转换处理后设置到ImageView的过程。
源码分析
ViewTarget创建
into方法有多个重载,这里调用的是:
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
RequestOptions requestOptions = this.requestOptions;
// 设置缩放属性
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.
}
}
// 先创建ViewTarget,然后传入into方法
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}
先看glideContext.buildImageViewTarget方法:
@NonNull
public<X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
// imageViewTargetFactory为GlideContext初始化时传入的ImageViewTargetFactory对象。
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view,
@NonNull Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
这里根据transcodeClass的类型创建对应的ViewTarget子类。本例的transcodeClass为Drawable.class,因此返回DrawableImageViewTarget对象。
ps:ViewTarget是介于请求和请求者之间的中介者的角色,它负责在View上加载展示资源。
回到into方法:
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
@NonNull RequestOptions options) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
// 调用load方法后isModelSet设为true。
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
options = options.autoClone();
// 创建为target加载资源的请求,实例为SingleRequest。
Request request = buildRequest(target, targetListener, options);
// 取出target中保存的上一次的请求。
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// 若当前创建的请求和上一次的请求的参数、配置、优先级、监听器数量等一致,并且,
// 允许内存缓存或者上一次请求未成功完成,则释放本次创建的请求的资源。
request.recycle();
// 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取消target的请求的加载(若有)并释放已为它加载的资源(若有)。
requestManager.clear(target);
// 设置请求到target中保存。
target.setRequest(request);
// 分别保存target和request在RequestManager的set集合中,并且启动request加载。
requestManager.track(target, request);
return target;
}
在该方法中创建了Request,之后判断ViewTarget中是否设置了相同的Request,若存在且不在请求进行中,则启动之前设置的请求。若ViewTarget未设置过相同的Request,则清除ViewTarget中的Request,并将本次的Request设置给ViewTarget,接着启动本次Request。
into方法最终返回ViewTarget,我们可以利用进行它取消或重新加载展示资源等操作,也可以不进行任何处理。
Request创建和开始加载
在into方法中通过buildRequest方法创建了一个Request:
private Request buildRequest(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
RequestOptions requestOptions) {
return buildRequestRecursive(
target,
targetListener,
/*parentCoordinator=*/ null,
transitionOptions,
requestOptions.getPriority(),
requestOptions.getOverrideWidth(),
requestOptions.getOverrideHeight(),
requestOptions);
}
又调用buildRequestRecursive方法:
private Request buildRequestRecursive(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
RequestOptions requestOptions) {
// Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
ErrorRequestCoordinator errorRequestCoordinator = null;
// errorBuilder通过error方法设置,默认为空。
if (errorBuilder != null) {
errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}
// 创建Request对象
Request mainRequest =
buildThumbnailRequestRecursive(
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions);
// errorRequestCoordinator默认为空,直接返回mainRequest。
if (errorRequestCoordinator == null) {
return mainRequest;
}
// 创建加载错误情况下的二次请求,默认为空,先忽略。
···
}
继续看buildThumbnailRequestRecursive方法:
private Request buildThumbnailRequestRecursive(
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
RequestOptions requestOptions) {
if (thumbnailBuilder != null) {
// thumbnailBuilder通过thumbnail方法设置,默认为空。
// 创建加载缩略图的递归请求。
···
return coordinator;
} else if (thumbSizeMultiplier != null) {
// thumbSizeMultiplier通过thumbnail方法设置,默认为空。
// 创建加载缩略图的递归请求。
···
return coordinator;
} else {
// Base case: no thumbnail.
// 默认情况下执行到这个case里。
return obtainRequest(
target,
targetListener,
requestOptions,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight);
}
}
接着看obtainRequest方法:
private Request obtainRequest(
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
RequestOptions requestOptions,
RequestCoordinator requestCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight) {
return SingleRequest.obtain(
context,
glideContext,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
targetListener,
requestListeners,
requestCoordinator,
glideContext.getEngine(),
transitionOptions.getTransitionFactory());
}
可以看到创建Request过程最终是返回了SingleRequest实例。 在这个过程中还可以根据用户设置创建递归请求,当一个请求结束时通知下一个请求。
回到into方法中,在获取到Request之后,会通过RequestManager启动Request:
void track(@NonNull Target<?> target, @NonNull Request request) {
// targetTracker中维护了一个Set集合,用来保存Target,并实现
// LifecycleListener接口,转发生命周期事件给Set中的Target。
targetTracker.track(target);
// requestTracker用于跟踪、取消和重新启动正在进行、已完成和失败的请求。
requestTracker.runRequest(request);
}
看runRequest方法:
public void runRequest(@NonNull Request request) {
// 添加至Set集合中保存
requests.add(request);
// RequestManager是否暂停或释放所有进行中的请求。
if (!isPaused) {
// 开始加载。
request.begin();
} else {
// 取消加载并释放资源。
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
// 加入等待队列。
pendingRequests.add(request);
}
}
这里调用了SingleRequest的begin方法。接下来才开始真正发起加载请求:
public void begin() {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
// 检查load方法传入的数据源是否为空。
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);
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)) {
// 存在有效宽高,回调尺寸确认成功。默认都为-1,不会直接回调。
onSizeReady(overrideWidth, overrideHeight);
} else {
// 通过target获取有效宽高。
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
// 状态为运行中或等待确认尺寸中时调用target的开始加载回调方法。
// 该回调方法中会添加OnAttachStateChangeListener监听和将获取设置的占位图填充到view上。
target.onLoadStarted(getPlaceholderDrawable());
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
begin方法中会进行状态校验,根据当前status执行对应的case。
SingleRequest包含以下几个状态:
status | description |
---|---|
PENDING | 已创建但尚未运行 |
RUNNING | 运行获取资源中 |
WAITING_FOR_SIZE | 等待确认目标尺寸 |
COMPLETE | 加载资源成功 |
FAILED | 加载资源失败 |
CLEARED | 取消加载,或替换占位图加载 |
这里当首次开始加载图片时,将调用target的getSize方法确认尺寸(本例中target为DrawableImageViewTarget对象)。在target中又使用SizeDeterminer辅助类获取尺寸,其中通过给view注册ViewTreeObserver监听布局绘制,获取LayoutParams和padding来确定尺寸,并且处理了自定义父布局LayoutParams获取不正确时通过view.getWidth()来确定尺寸。若view设置了WRAP_CONTENT,则获取屏幕尺寸最长的长度作为尺寸,避免图片过大OOM。当尺寸确定后就会调用SingleRequest的onSizeReady方法,传入宽和高。
接下来进入onSizeReady方法:
@Override
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
if (IS_VERBOSE_LOGGABLE) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
// 将状态置为RUNNING。
status = Status.RUNNING;
// 将width、height乘以乘数,乘数默认为1f。
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));
}
// 调用Engine的load方法开始加载,并且返回LoadStatus对象(LoadStatus可用于移除这里传入的ResourceCallback回调,
// 使之不再监听加载结果)。
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));
}
}
SingleRequest在获取到目标尺寸后,即通过Engine开始进行资源加载。
Engine启动
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) {
Util.assertMainThread();
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
// 利用各个参数构建用于缓存的Key。
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
// 从活动的资源缓存中获取EngineResource对象,EngineResource为资源的装饰者对象,它还含有一个引用计数,
// 此时获取的缓存资源的同时会增加计数。
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
// 回调通知外层资源可用。
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
// 从非活动的资源缓存中获取资源对象,并将其移至活动的资源缓存中,同时增加引用计数。
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
// 回调通知外层资源可用。
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
// 从任务集合中查找相同key的任务。若存在相同的任务,则复用这个任务,添加回调。
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
// 添加回调时,若任务已获取到资源,则直接回调资源加载成功,若已失败,则直接回调加载失败。
current.addCallback(cb);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
// 创建EngineJob,负责管理添加和移除加载的回调,和加载完成的回调通知。
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
// 创建DecodeJob,负责解码和转换资源。
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);
}
该方法中首先从活动的资源缓存中查找资源,活动的资源即为被引用的资源,比如某个view中在使用的bitmap或drawable。若不存在,接着从非活动的资源缓存中查找,找到后会移至活动资源缓存中,并主动增加该资源的引用计数。若不存在缓存,则判断是否存在相同的任务,有则复用该任务,添加回调等待加载完成。若仍不存在,最终新建任务,启动加载。
该方法中如果没有找到缓存,最后会返回LoadStatus对象,该对象包含EngineJob和ResourceCallback,可用于从EngineJob中移除添加的ResourceCallback。
假设本例中尚未存在缓存,需要从远程加载资源。往下看EngineJob的start方法:
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
// 这里使用diskCacheExecutor线程池启动DecodeJob。
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
EngineJob中又通过线程池启动了DecodeJob,DecodeJob实现了Runnable接口,在它的run方法又调用了runWrapped方法:
private void runWrapped() {
// runReason初始值为INITIALIZE。
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
runWrapped方法中有3个case,分别执行不同的操作:
- INITIALIZE:初始阶段,需要获取下一个阶段,执行下个阶段对应的操作。
- SWITCH_TO_SOURCE_SERVICE:需要从磁盘缓存获取数据切换到从数据源获取数据。
- DECODE_DATA:需要对获取到的数据进行解码转换。
接着看getNextStage方法,该方法返回下一个状态:
private Stage getNextStage(Stage current) {
// current初始值为INITIALIZE。
switch (current) {
case INITIALIZE:
// 是否尝试从加工过的资源的磁盘缓存中获取,若否则返回下个阶段。本例中diskCacheStrategy为DiskCacheStrategy.AUTOMATIC,返回RESOURCE_CACHE。
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
// 是否尝试从未加工的资源的磁盘缓存中获取。返回DATA_CACHE。
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
// onlyRetrieveFromCache默认为false,这里返回SOURCE。
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
这里有5个阶段:
- INITIALIZE:初始阶段。
- RESOURCE_CACHE:从缓存的资源解码(磁盘缓存的经过采样、转换的加工过的资源)。
- DATA_CACHE:从缓存的源数据解码(磁盘缓存的未经过加工的原始资源)。
- SOURCE:从获取到的源数据解码(从传入的数据源model加载的资源)。
- ENCODE:成功加载后对转换后的资源进行解码。
- FINISHED:最后阶段,结束。
ps:Engine.load方法中是对内存缓存中的资源进行检索,DecodeJob中是对磁盘缓存中的资源进行检索。磁盘缓存中又分为加工和未加工的资源,未加工即从mode加载来的原始资源,加工即对加载来的资源进行采样、缩放、裁剪等操作的资源。
回到runWrapped方法,在返回stage后,调用getNextGenerator方法:
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
该方法就是根据不同的阶段创建对应的DataFetcherGenerator。
返回DataFetcherGenerator之后就调用runGenerators方法:
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
// 遍历执行currentGenerator.startNext,若成功处理返回true,否则尝试下个阶段
// 的DataFetcherGenerator。
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
// 获取下个阶段和阶段对应的DataFetcherGenerator。
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
// 到了SOURCE阶段,直接执行reschedule方法,不再执行后续代码。
reschedule();
return;
}
}
// We've run out of stages and generators, give up.
// 若到了最后阶段或已取消,且仍没有DataFetcherGenerator成功处理,则通知加载失败。
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
// Otherwise a generator started a new load and we expect to be called back in
// onDataFetcherReady.
}
假设此时是首次加载图片,无任何缓存,则会执行到SOURCE阶段,直接看reschedule方法:
@Override
public void reschedule() {
// 将runReason赋值为SWITCH_TO_SOURCE_SERVICE,表示需要从传入model加载数据。
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
// 这里的callback为EngineJob实例,在DecodeJob创建时传入,EngineJob实现了DecodeJob.Callback接口。
callback.reschedule(this);
}
在callback(即EngineJob实例)的reschedule方法里又通过线程池执行DecodeJob的run方法,这样又回到上面的流程。回到runWrapped方法中,此时的runReason已赋值成SWITCH_TO_SOURCE_SERVICE,在这个case中,直接调用runGenerators方法。在runGenerators方法中依然执行currentGenerator.startNext(此时的currentGenerator已赋值为SourceGenerator实例)。
下面进入SourceGenerator.startNext方法中:
@Override
public boolean startNext() {
// 源数据是否加载完成,若完成则进行缓存。默认为null。
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
// 当执行上一步cacheData时,sourceCacheGenerator会被创建用来从缓存中获取数据。默认为null。
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
// 遍历LoadData,逐个尝试。
while (!started && hasNextModelLoader()) {
// 从Registry中查找。
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
// 找到可用的LoadData,开始拉取源数据。
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
// 若找到可用的LoadData,将返回true。
return started;
}
LoadData由ModelLoader生成,它包含一个Source Key和一组备用Key和一个DataFetcher对象。DataFetcher负责真正执行获取数据。
假设第一次加载图片不存在缓存,直接看while循环部分。
hasNextModelLoader方法中判断索引是否超出LoadData列表:
private boolean hasNextModelLoader() {
return loadDataListIndex < helper.getLoadData().size();
}
接着看helper.getLoadData方法:
List<LoadData<?>> getLoadData() {
// 是否有缓存,有则直接返回,否则重新查找。
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
// 根据model的类类型从Registry中查找所有对应的ModelLoader。
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = modelLoaders.size(); i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
// 由ModelLoader创建LoadData。
LoadData<?> current =
modelLoader.buildLoadData(model, width, height, options);
if (current != null) {
loadData.add(current);
}
}
}
return loadData;
}
ModelLoader筛选
获取所有model对应的ModelLoader是从Registry的子表ModelLoaderRegistry中查找:
@NonNull
public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
// 获取model的类类型,然后获取该类型对应的所有ModelLoader。
List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
int size = modelLoaders.size();
boolean isEmpty = true;
List<ModelLoader<A, ?>> filteredLoaders = Collections.emptyList();
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0; i < size; i++) {
ModelLoader<A, ?> loader = modelLoaders.get(i);
// 通过ModelLoader的handles判断是否支持处理该model(每个ModelLoader实现不一样)。
if (loader.handles(model)) {
if (isEmpty) {
filteredLoaders = new ArrayList<>(size - i);
isEmpty = false;
}
// 保存进集合。
filteredLoaders.add(loader);
}
}
// 返回筛选后的ModelLoader列表。
return filteredLoaders;
}
在Glide初始化的时候,Registry注册了一些列的ModelLoaderFactory,详细见ModelLoader映射表。在getModelLoadersForClass方法中会依次遍历这些ModelLoaderFactory,若注册时设置的model类型是当前传入的model类型的超类,则调用build方法构建ModelLoader。
例如本例从url加载图片,传入的model类型为String.class,则匹配的ModelLoaderFactory有DataUrlLoader.StreamFactory、StringLoader.StreamFactory、StringLoader.FileDescriptorFactory、StringLoader.AssetFileDescriptorFactory。
获取到ModelLoader列表后,再进一步对其进行筛选,这里依次看DataUrlLoader和StringLoader的handles方法:
/** DataUrlLoader */
@Override
public boolean handles(@NonNull Model model) {
// We expect Model to be a Uri or a String, both of which implement toString() efficiently. We
// should reconsider this implementation before adding any new Model types.
// 本例中的model为"http"开头,而DATA_SCHEME_IMAGE为"data:image",因此不匹配。
return model.toString().startsWith(DATA_SCHEME_IMAGE);
}
/** StringLoader */
@Override
public boolean handles(@NonNull String model) {
// Avoid parsing the Uri twice and simply return null from buildLoadData if we don't handle this
// particular Uri type.
return true;
}
经过筛选后,getModelLoaders方法最终返回的ModelLoader列表仅包含3个StringLoader实例。
回到DecodeHelper的getLoadData方法,在for循环中依次调用ModelLoader的buildLoadData方法创建LoadData。看StringLoader:
@Override
public LoadData<Data> buildLoadData(@NonNull String model, int width, int height,
@NonNull Options options) {
Uri uri = parseUri(model);
// 在StringLoader中又包含ModelLoader的成员变量,这里实际是调用了这个ModelLoader,
// 相当于代理模式。
if (uri == null || !uriLoader.handles(uri)) {
return null;
}
return uriLoader.buildLoadData(uri, width, height, options);
}
StringLoader中的uriLoader是在构造函数中被赋值,可以查看StringLoader中的3个工厂类的build方法,创建了MultiModelLoader类型的ModelLoader,其中又包含了多个ModelLoader,同样是根据model类型和data类型进行筛选。
在MultiModelLoader的buildLoadData方法中会依次调用包含的ModelLoader的buildLoadData方法:
@Override
public LoadData<Data> buildLoadData(@NonNull Model model, int width, int height,
@NonNull Options options) {
Key sourceKey = null;
int size = modelLoaders.size();
// 收集由ModelLoader构建的LoadData中的DataFetcher。
List<DataFetcher<Data>> fetchers = new ArrayList<>(size);
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0; i < size; i++) {
ModelLoader<Model, Data> modelLoader = modelLoaders.get(i);
// 匹配支持model的ModelLoader。
if (modelLoader.handles(model)) {
LoadData<Data> loadData = modelLoader.buildLoadData(model, width, height, options);
if (loadData != null) {
sourceKey = loadData.sourceKey;
// 添加DataFetcher。
fetchers.add(loadData.fetcher);
}
}
}
// 重新new一个LoadData,保存收集的DataFetcher。
return !fetchers.isEmpty() && sourceKey != null
? new LoadData<>(sourceKey, new MultiFetcher<>(fetchers, exceptionListPool)) : null;
}
该方法中,依次匹配model对应的ModelLoader,然后构建LoadData。但是并不直接使用构建的LoadData,仅取出其中的DataFetcher。最后利用MultiFetcher对象保存DataFetcher列表,新new一个LoadData返回。
当model为String类型时,经过层层筛选后,最终仅剩一个LoadData。LoadData中的fetcher成员为MultiFetcher实例,包含两个HttpUrlFetcher实例。
回到SourceGenerator的startNext方法的while循环体中:
public boolean startNext() {
···
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
// DiskCacheStrategy.AUTOMATIC的isDataCacheable方法中判断dataSource是否为DataSource.REMOTE,
// 这里getDataSource方法调用的是HttpUrlFetcher的getDataSource,返回一致,if条件判断通过。
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
···
}
下面进入DataFetcher的loadData方法,开始从model拉取源数据。
DataFetcher加载数据
从上文得知此处的loadData.fetcher为MultiFetcher实例,查看它的loadData方法:
@Override
public void loadData(
@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {
this.priority = priority;
this.callback = callback;
exceptions = throwableListPool.acquire();
// 根据当前索引获取DataFetcher,currentIndex初始为0。
fetchers.get(currentIndex).loadData(priority, this);
}
从上文得知此时fetchers保存了两个HttpUrlFetcher实例,继续看HttpUrlFetcher的loadData方法:
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
// 获取到源数据的InputStream。
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
// 将InputStream回传给SourceGenerator处理。
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));
}
}
}
接着看loadDataWithRedirects方法:
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
Map<String, String> headers) throws IOException {
// 判断重定向次数是否达到了5次。
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
// 产生重定向时,lastUrl会传入上一次的url,将前后url进行比较,如果是一样的,表示陷入了死循环中。
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new HttpException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
// 调用url的openConnection方法,返回HttpURLConnection实例。
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);
// Stop the urlConnection instance of HttpUrlConnection from following redirects so that
// redirects will be handled by recursive calls to this method, loadDataWithRedirects.
// 禁用HttpURLConnection的自动重定向,自行处理重定向。
urlConnection.setInstanceFollowRedirects(false);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
stream = urlConnection.getInputStream();
// 若view从detached或cancel加载,则返回null。外层会根据空值进行相应处理。
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (isHttpOk(statusCode)) {
// 状态码为2xx表示成功,从urlConnection获取InputStream返回。
return getStreamForSuccessfulRequest(urlConnection);
} else if (isHttpRedirect(statusCode)) {
// 状态码为3xx表示重定向,从表头获取"Location"字段值作为重定向地址。
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);
}
}
HttpUrlFetcher中就是使用我们熟悉的HttpURLConnection网络操作代码,返回InputStream后,通过回调接口传给SourceGenerator处理,看它的onDataReady方法:
@Override
public void onDataReady(Object data) {
// 根据磁盘缓存策略决定是先进行缓存还是直接回调出去。
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
// diskCacheStrategy默认为DiskCacheStrategy.AUTOMATIC,判断dataSource是否为DataSource.REMOTE,这里满足条件。
// 因此只要data不为空,就先进行缓存。
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
// dataToCache此时赋值为InputStream。
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
// 此处的cb即为SourceGenerator创建时传入的DecodeJob实例。
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
又回到了DecodeJob的reschedule方法中,前面分析过,此方法最终又回DecodeJob的runGenerators的方法中,因为当前阶段仍为SOURCE未改变,所以又执行了SourceGenerator的startNext方法:
public boolean startNext() {
// 经过上面的源数据获取,此时dataToCache为InputStream。
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
···
}
进入cacheData方法:
private void cacheData(Object dataToCache) {
longstartTime = LogTime.getLogTime();
try {
// 从Registry中查找dataToCache的类型对应的Encoder(Encoder负责写入磁盘缓存),此处得到StreamEncoder实例。
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
// 构造缓存key。
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
// 获取DiskCache,默认返回DiskLruCacheWrapper。在它的put方法中通过writer将数据写入File,然后记录操作日志到
// journal文件中(常规的磁盘缓存操作)。而在writer中又是通过StreamEncoder从InputStream写入到File。
helper.getDiskCache().put(originalKey, writer);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished encoding source to cache"
+ ", key: " + originalKey
+ ", data: " + dataToCache
+ ", encoder: " + encoder
+ ", duration: " + LogTime.getElapsedMillis(startTime));
}
} finally {
// 完成磁盘缓存后,关闭流和释放连接。
loadData.fetcher.cleanup();
}
// 给sourceCacheGenerator成员赋值。
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
执行完cacheData方法,返回startNext方法。往下执行,此时sourceCacheGenerator已赋值,执行它的startNext方法:
@Override
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
// PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times
// and the actions it performs are much more expensive than a single allocation.
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
// 获取到前面写入的缓存文件。
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
// 获取到File类型对应的ModelLoader集合。
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
// 依次获取ModelLoader创建LoadData,尝试可用的LoadData。
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData =
modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
helper.getOptions());
// 判断LoadData中的DataFetcher尝试获取的数据的类型是否存在对应的LoadPath。
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
// 通过LoadData中的DataFetcher加载数据。
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
该方法中获取的ModelLoader有ByteBufferFileLoader、FileLoader、FileLoader、UnitModelLoader,通过ByteBufferFileLoader创建的LoadData就匹配上了,由它创建的ByteBufferFetcher获取DataClass为ByteBuffer.class,进入hasLoadPath方法:
boolean hasLoadPath(Class<?> dataClass) {
return getLoadPath(dataClass) != null;
}
<Data> LoadPath<Data, ?, Transcode> getLoadPath(Class<Data> dataClass) {
// 从Registry中查找。
return glideContext.getRegistry().getLoadPath(dataClass, resourceClass, transcodeClass);
}
看Registry的getLoadPath方法,在本例中,此处dataClass为ByteBuffer.class,resourceClass为Object.class,transcodeClass为Drawable.class:
@Nullable
public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
@NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass,
@NonNull Class<Transcode> transcodeClass) {
LoadPath<Data, TResource, Transcode> result =
loadPathCache.get(dataClass, resourceClass, transcodeClass);
if (loadPathCache.isEmptyLoadPath(result)) {
return null;
} else if (result == null) {
// 收集DecodePath集合。
List<DecodePath<Data, TResource, Transcode>> decodePaths =
getDecodePaths(dataClass, resourceClass, transcodeClass);
// It's possible there is no way to decode or transcode to the desired types from a given
// data class.
if (decodePaths.isEmpty()) {
result = null;
} else {
// 创建LoadPath对象,将收集到的DecodePath集合包装进去。
result =
new LoadPath<>(
dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
}
// 将创建的LoadPath保存起来。
loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
}
return result;
}
接着看getDecodePaths方法:
@NonNull
private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths(
@NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass,
@NonNull Class<Transcode> transcodeClass) {
List<DecodePath<Data, TResource, Transcode>> decodePaths = new ArrayList<>();
// 利用dataClass和resourceClass检索出注册的解码资源类型。
// 需要满足注册Data类型为dataClass的超类且resourceClass为注册Resource类型的超类。
List<Class<TResource>> registeredResourceClasses =
decoderRegistry.getResourceClasses(dataClass, resourceClass);
// 遍历注册的解码资源类型。
for (Class<TResource> registeredResourceClass : registeredResourceClasses) {
// 利用registeredResourceClass和transcodeClass检索出注册的转码资源类型。
// 需要满足注册Resource类型为registeredResourceClass的超类且transcodeClass为注册Transcode类型的超类,
// 或者transcodeClass为registeredResourceClass的超类,那么直接返回transcodeClass。
List<Class<Transcode>> registeredTranscodeClasses =
transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);
// 遍历注册的转码资源类型。
for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {
// 利用dataClass和registeredResourceClass检索出注册的ResourceDecoder。
// 匹配条件同检索出注册的解码资源类型一致。
List<ResourceDecoder<Data, TResource>> decoders =
decoderRegistry.getDecoders(dataClass, registeredResourceClass);
// 利用registeredResourceClass和registeredTranscodeClass检索出注册的ResourceTranscoder。
// 匹配条件同检索出注册的转码资源类型一致,若registeredTranscodeClass为registeredResourceClass超类,则返回UnitTranscoder对象。
ResourceTranscoder<TResource, Transcode> transcoder =
transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
// 创建DecodePath,将检索到的ResourceDecoder集合和ResourceTranscoder包装进去。
DecodePath<Data, TResource, Transcode> path =
new DecodePath<>(dataClass, registeredResourceClass, registeredTranscodeClass,
decoders, transcoder, throwableListPool);
// 将DecodePath添加集合保存。
decodePaths.add(path);
}
}
return decodePaths;
}
该方法中通过<Data, TResource, Transcode>类型从Registry中检索出匹配的ResourceDecoder和ResourceTranscoder,使之能够将数据从Data类型解码转换成Transcode类型,然后利用DecodePath将它们包装进去。
ps:Glide注册的ResourceDecoder、ResourceTranscoder和<Data, TResource, Transcode>的对应关系可以查看ResourceDecoder映射表、ResourceTranscoder映射表。
结合本文章例子,这里检索它们的作用是通过ByteBufferFileLoader将资源从File读取到ByteBuffer,然后通过ByteBufferBitmapDecoder将资源从ByteBuffer解码成Bitmap,再通过BitmapDrawableTranscoder对Bitmap进行处理,最终转换成BitmapDrawable。
返回到DataCacheGenerator的startNext方法中,执行完hasLoadPath方法满足条件,接下来就是调用fetcher的loadData方法(通过上面的分析得知fetcher为ByteBufferFetcher实例):
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super ByteBuffer> callback) {
ByteBuffer result;
try {
// File -> MappedByteBuffer
result = ByteBufferUtil.fromFile(file);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to obtain ByteBuffer for file", e);
}
callback.onLoadFailed(e);
return;
}
// 回调传出MappedByteBuffer。
callback.onDataReady(result);
}
@Override
public void onDataReady(Object data) {
// 此时的cb为SourceGenerator实例。
cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
}
看SourceGenerator的onDataFetcherReady回调:
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
// This data fetcher will be loading from a File and provide the wrong data source, so override
// with the data source of the original fetcher
// 此时的cb为DecodeJob实例。替换数据来源回REMOTE。
cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);
}
继续看DecodeJob的onDataFetcherReady回调:
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
this.currentSourceKey = sourceKey;
// 此时的data为MappedByteBuffer。
this.currentData = data;
// 此时的fetcher为ByteBufferFetcher。
this.currentFetcher = fetcher;
this.currentDataSource = dataSource;
this.currentAttemptingKey = attemptedKey;
if (Thread.currentThread() != currentThread) {
// 若当前线程和之前runGenerators方法执行时的线程不一致,则切换runReason,
// 通过线程池再启动,在run方法中会执行decodeFromRetrievedData方法。
runReason = RunReason.DECODE_DATA;
callback.reschedule(this);
} else {
GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
try {
// 解码数据。
decodeFromRetrievedData();
} finally {
GlideTrace.endSection();
}
}
}
这里直接执行decodeFromRetrievedData方法:
private void decodeFromRetrievedData() {
if(Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Retrieved data", startFetchTime,
"data: " + currentData
+ ", cache key: " + currentSourceKey
+ ", fetcher: " + currentFetcher);
}
Resource<R> resource = null;
try {
// 进行解码,返回资源的包装类。
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
// 通知资源处理成功。
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
接下来对数据进行解码转换处理,见《Glide v4 源码浅析(4)-into(下)》。