之前对Glide的认知一直停留在一行代码就能够完成图片加载,如今就来尝试探索下这一行代码下,Glide到底作了些什么。本文基于Glide4.8.0java
以加载一张普通的网络图片为例缓存
val jpg = "http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg"
Glide.with(this)
.load(img)
.into(iv)
复制代码
就这么核心的一行代码一张图片就会加载到iv中去了,下面从with开始分析下它的源码bash
with方法提供了不少方法重载,入参都是一些能直接或间接取到Context实例的类型,其基本流程以下网络
下面根据这个流程来看看源码app
// Glide.java
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
return Glide.get(context).getRequestManagerRetriever();
}
public static Glide get(@NonNull Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context);
}
}
}
return glide;
}
private static void checkAndInitializeGlide(@NonNull Context context) {
initializeGlide(context);
}
private static void initializeGlide(@NonNull Context context) {
initializeGlide(context, new GlideBuilder());
}
private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
RequestManagerRetriever.RequestManagerFactory factory = null;
builder.setRequestManagerFactory(factory);
Glide glide = builder.build(applicationContext);
applicationContext.registerComponentCallbacks(glide);
Glide.glide = glide;
}
复制代码
经过源码发现getRetriever方法内部其实就是建立了Glide实例,而且调用其getRequestManagerRetriever,先来看看Glide实例的构建过程ide
Glide build(@NonNull Context context) {
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(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);
return new Glide(...);
}
复制代码
能够看到内部先是构建了三个线程池,而后新建了一个Engine类实例,建立了RequestManagerRetriever实例,该实例就是调用getRequestManagerRetriever获取到的实例。接着看看Glide的构造方法。oop
Glide(...) {
registry = new Registry();
// 这里注册了不少东西,好比编码器、解码器、工厂类、转换器。这里只是挑了几个
registry
// 注册Encoder
.append(ByteBuffer.class, new ByteBufferEncoder())
// 注册Decoder
.append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
// 注册ModelLoaderFactory,三个参数分别是Model类型,Data类型,ModelLoader工厂
.append(String.class, InputStream.class, new StringLoader.StreamFactory())
// 注册Transcoders
.register(Bitmap.class,BitmapDrawable.class,new BitmapDrawableTranscoder(resources))
...
ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
glideContext = new GlideContext(...);
}
复制代码
获取到了RequestManagerRetriever实例后继续调用其get方法fetch
public RequestManager get(@NonNull FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
复制代码
这里先分析主线程调用的状况,所以接着调用supportFragmentGetui
private RequestManager supportFragmentGet( @NonNull Context context, @NonNull FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
SupportRequestManagerFragment current =
getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
private SupportRequestManagerFragment getSupportRequestManagerFragment( @NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
// 首先看看当前页面是否已经有指定Fragment了,若是没有那么就新建一个而且加入到当前页面中去
SupportRequestManagerFragment current =
(SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingSupportRequestManagerFragments.get(fm);
if (current == null) {
current = new SupportRequestManagerFragment();
current.setParentFragmentHint(parentHint);
if (isParentVisible) {
current.getGlideLifecycle().onStart();
}
pendingSupportRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}
复制代码
能够看到supportFragmentGet首先将一个Fragment添加到当前页面(用于观察Activity的生命周期),而后建立一个RequestManager实例并返回,with方法结束而后看看load方法this
load方法位于RequestManager中用于将指定资源做为数据源这里以参数为String为例
下面根据这个流程来看看源码
// RequestManager.java
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
public <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
复制代码
asDrawable内部只是建立了一个RequestBuilder实例,接着再调用其load方法
// RequestBuilder.java
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
复制代码
load方法内部也就是进行了简单赋值就将本身返回了,至此load方法也已经结束了,接着看看重点into方法
into方法一样定义在RequestBuilder中,主要用于将加载到的图片传递给指定Target实例,这个方法内部至关复杂,也是本文的重点。
// RequestBuilder.java
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
RequestOptions requestOptions = this.requestOptions;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
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:
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}
复制代码
这里会对RequestOptions作一些转化,而后调用buildImageViewTarget构造指定的target。
// GlideContext.java
public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
// ImageViewTargetFactory.java
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)");
}
}
复制代码
因为刚才调用了asDrawable因此会新建一个DrawableImageViewTarget实例返回,接着继续看看into方法
private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, @NonNull RequestOptions options) {
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
options = options.autoClone();
Request request = buildRequest(target, targetListener, options);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
复制代码
into方法内部首先判断了是否调用了load方法而后调用buildRequest建立Request实例,而后调用track方法执行Request
private Request buildRequest( Target<TranscodeType> target, @Nullable RequestListener<TranscodeType> targetListener, RequestOptions requestOptions) {
return buildRequestRecursive(...);
}
private Request buildRequestRecursive(...) {
Request mainRequest =
buildThumbnailRequestRecursive(...);
if (errorRequestCoordinator == null) {
return mainRequest;
}
Request errorRequest = errorBuilder.buildRequestRecursive(...);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
}
private Request buildThumbnailRequestRecursive(...) {
// 只考虑没有缩略图的状况了
return obtainRequest(...);
}
private Request obtainRequest(...) {
return SingleRequest.obtain(...);
}
复制代码
能够看到当没有缩略图的时候会经过调用SingleRequest.obtain新建一个SingleRequest实例,接着看看requestManager.track是怎么执行Request的
// RequestManager.java
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();
pendingRequests.add(request);
}
}
复制代码
若是没有被暂定的话就接着继续执行SingleRequest的begin方法
// SingleRequest.java
public void begin() {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
}
复制代码
根据是否设置了宽高来决定是调用onSizeReady仍是target.getSize,先假设没有设置宽高来看看DrawableImageViewTarget实例的getSize方法
// getSize继承自ViewTarget
public void getSize(@NonNull SizeReadyCallback cb) {
sizeDeterminer.getSize(cb);
}
// SizeDeterminer.java
void getSize(@NonNull SizeReadyCallback cb) {
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
return;
}
if (!cbs.contains(cb)) {
cbs.add(cb);
}
if (layoutListener == null) {
ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
}
}
private int getTargetWidth() {
int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight();
LayoutParams layoutParams = view.getLayoutParams();
int layoutParamSize = layoutParams != null ? layoutParams.width : PENDING_SIZE;
return getTargetDimen(view.getWidth(), layoutParamSize, horizontalPadding);
}
private int getTargetHeight() {
int verticalPadding = view.getPaddingTop() + view.getPaddingBottom();
LayoutParams layoutParams = view.getLayoutParams();
int layoutParamSize = layoutParams != null ? layoutParams.height : PENDING_SIZE;
return getTargetDimen(view.getHeight(), layoutParamSize, verticalPadding);
}
private int getTargetDimen(int viewSize, int paramSize, int paddingSize) {
int adjustedParamSize = paramSize - paddingSize;
if (adjustedParamSize > 0) {
return adjustedParamSize;
}
if (waitForLayout && view.isLayoutRequested()) {
return PENDING_SIZE;
}
int adjustedViewSize = viewSize - paddingSize;
if (adjustedViewSize > 0) {
return adjustedViewSize;
}
if (!view.isLayoutRequested() && paramSize == LayoutParams.WRAP_CONTENT) {
return getMaxDisplayLength(view.getContext());
}
return PENDING_SIZE;
}
复制代码
getSize内部经过获取View的长宽减去对应的padding作为目标宽高,而后又回调了onSizeReady方法,注意若是在此时目标ImageView尚未进行三大流程(也就是说获取不到宽高)那么会注册回调,在回调中会调用onSizeReady方法这里就不展开了,所以不论是否指定了宽高最终都会调用SingleRequest的onSizeReady方法
// SingleRequest.java
public void onSizeReady(int width, int height) {
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
loadStatus = engine.load(...);
}
复制代码
onSizeReady内部就分为三步首先改变状态为Running而后计算最终须要加载的宽高,最后调用engine.load加载资源
public <R> LoadStatus load(...) {
EngineKey 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);
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;
}
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<R> engineJob =
engineJobFactory.build(...);
DecodeJob<R> decodeJob =
decodeJobFactory.build(...);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
复制代码
load方法首先构建一个EngineKey实例,内部只是重写了hashCode和equal方法,接着若是容许读取内存缓存就尝试从ActivityResources中读取读不到再从MemoryCache中读取,Tip: 暂时没分清这二者的区别。若是已经有了当前key对应的EngineJob就直接返回,接着建立一个EngineJob和一个DecodeJob实例,而后调用engineJob.start
// EngineJob.java
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
// DecodeJob.java
boolean willDecodeFromCache() {
Stage firstStage = getNextStage(Stage.INITIALIZE);
return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
复制代码
首先根据是否从缓存中解码选取线程池,接着使用线程池执行DecodeJob,而若是没有设置DiskCacheStrategy那么默认的DiskCacheStarategy.Automatic的decodeCachedResource返回true,因此willDecodeFromCache会返回true,因此最终executor会是diskCacheExecutor,而这个线程池就是在构建Glide实例的时候建立的diskCacheExecutor,接着看看DecodeJob的run方法
// DecodeJob.java
public void run() {
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (Throwable t) {
...
}
}
复制代码
若是取消了,就通知下失败,不然主要就是调用了runWrapped
// DecodeJob.java
private void runWrapped() {
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);
}
}
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);
}
}
复制代码
因为DecodeJob实例刚刚建立因此runReason为构造器时初始化的RunReason.INITIALIZE状态,getNextStage返回Stage.RESOURCE_CACHE,getNextGenerator返回一个ResourceCacheGenerator实例,接着看看runGenerator方法
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
}
复制代码
内部调用了startNext方法,并根据其返回值判断是否进入下一步,先来看看ResourceCacheGenerator的startNext方法
// ResourceCacheGenerator.java
public boolean startNext() {
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;
}
}
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
if (resourceClassIndex >= resourceClasses.size()) {
sourceIdIndex++;
if (sourceIdIndex >= sourceIds.size()) {
return false;
}
resourceClassIndex = 0;
}
// 获取GlideUrl
Key sourceId = sourceIds.get(sourceIdIndex);
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation<?> transformation = helper.getTransformation(resourceClass);
currentKey =
new ResourceCacheKey(...);
cacheFile = helper.getDiskCache().get(currentKey);
if (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 = 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;
}
复制代码
getCacheKey这个方法内部很是复杂,一步步来进行分析
// DecoderHelp.java
List<Key> getCacheKeys() {
if (!isCacheKeysSet) {
isCacheKeysSet = true;
cacheKeys.clear();
List<LoadData<?>> loadData = getLoadData();
for (int i = 0, size = loadData.size(); i < size; i++) {
LoadData<?> data = loadData.get(i);
if (!cacheKeys.contains(data.sourceKey)) {
cacheKeys.add(data.sourceKey);
}
for (int j = 0; j < data.alternateKeys.size(); j++) {
if (!cacheKeys.contains(data.alternateKeys.get(j))) {
cacheKeys.add(data.alternateKeys.get(j));
}
}
}
}
return cacheKeys;
}
List<LoadData<?>> getLoadData() {
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
for (int i = 0, size = modelLoaders.size(); i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
LoadData<?> current =
modelLoader.buildLoadData(model, width, height, options);
if (current != null) {
loadData.add(current);
}
}
}
return loadData;
}
// Registry.java
public <Model> List<ModelLoader<Model, ?>> getModelLoaders(@NonNull Model model) {
List<ModelLoader<Model, ?>> result = modelLoaderRegistry.getModelLoaders(model);
if (result.isEmpty()) {
throw new NoModelLoaderAvailableException(model);
}
return result;
}
public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
int size = modelLoaders.size();
boolean isEmpty = true;
List<ModelLoader<A, ?>> filteredLoaders = Collections.emptyList();
for (int i = 0; i < size; i++) {
ModelLoader<A, ?> loader = modelLoaders.get(i);
if (loader.handles(model)) {
if (isEmpty) {
filteredLoaders = new ArrayList<>(size - i);
isEmpty = false;
}
filteredLoaders.add(loader);
}
}
return filteredLoaders;
}
private synchronized <A> List<ModelLoader<A, ?>> getModelLoadersForClass(
@NonNull Class<A> modelClass) {
List<ModelLoader<A, ?>> loaders = cache.get(modelClass);
if (loaders == null) {
loaders = Collections.unmodifiableList(multiModelLoaderFactory.build(modelClass));
cache.put(modelClass, loaders);
}
return loaders;
}
// MultiModelLoaderFactory.java
synchronized <Model> List<ModelLoader<Model, ?>> build(@NonNull Class<Model> modelClass) {
try {
List<ModelLoader<Model, ?>> loaders = new ArrayList<>();
for (Entry<?, ?> entry : entries) {
if (alreadyUsedEntries.contains(entry)) {
continue;
}
if (entry.handles(modelClass)) {
alreadyUsedEntries.add(entry);
// 这个地方可能会触发递归,为了防止栈溢出,用过的entry不容许再用
loaders.add(this.<Model, Object>build(entry));
alreadyUsedEntries.remove(entry);
}
}
return loaders;
} catch (Throwable t) {
alreadyUsedEntries.clear();
throw t;
}
}
private <Model, Data> ModelLoader<Model, Data> build(@NonNull Entry<?, ?> entry) {
return (ModelLoader<Model, Data>) Preconditions.checkNotNull(entry.factory.build(this));
}
复制代码
getCacheKeys内部的一个getLoadData的代码就很长,通过层层调用最终会调用到build方法从Glide实例一建立就注册的的全部Factory中找寻到全部能处理Model类型为String的Factory,也就是以下四个
.append(String.class, InputStream.class, new DataUrlLoader.StreamFactory<String>())
.append(String.class, InputStream.class, new StringLoader.StreamFactory())
.append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
.append(String.class, AssetFileDescriptor.class, new StringLoader.AssetFileDescriptorFactory())
复制代码
这四个来一个个进行分析(为何要一个个分析接着看就知道了),首先会调用到DataUrlLoader.StreamFactory的build方法
// DataUrlLoader.StreamFactory.java
public ModelLoader<Model, InputStream> build( @NonNull MultiModelLoaderFactory multiFactory) {
return new DataUrlLoader<>(opener);
}
复制代码
内部简单的建立了一个DataUrlLoader实例,而后将其放入到了loaders中。
接着看看第二个StringLoader.StreamFactory的build方法
// StringLoader.StreamFactory.java
public ModelLoader<String, InputStream> build( @NonNull MultiModelLoaderFactory multiFactory) {
return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
}
复制代码
此次跟DataUrlLoader.StreamFactory不同了,内部建立了一个StringLoader实例,可是又回调了MultiModelLoaderFactory两个参数的build方法,而且明确指定了要寻找能将Model类型为Uri的处理成Data类型为InputStream的Factory,先来看看两个参数的build方法
// MultiModelLoaderFactory.java
public synchronized <Model, Data> ModelLoader<Model, Data> build(Class<Model> modelClass, Class<DatdataClass) {
try {
List<ModelLoader<Model, Data>> loaders = new ArrayList<>();
boolean ignoredAnyEntries = false;
for (Entry<?, ?> entry : entries) {
if (alreadyUsedEntries.contains(entry)) {
ignoredAnyEntries = true;
continue;
}
if (entry.handles(modelClass, dataClass)) {
alreadyUsedEntries.add(entry);
loaders.add(this.<Model, Data>build(entry));
alreadyUsedEntries.remove(entry);
}
}
if (loaders.size() > 1) {
return factory.build(loaders, throwableListPool);
} else if (loaders.size() == 1) {
return loaders.get(0);
} else {
if (ignoredAnyEntries) {
return emptyModelLoader();
} else {
throw new NoModelLoaderAvailableException(modelClass, dataClass);
}
}
} catch (Throwable t) {
alreadyUsedEntries.clear();
throw t;
}
}
复制代码
代码基本与一个参数的build方法相似,不一样点在于明确指定了目标Data类型和返回类型,那么以前在注册列表中寻找Model类型为Uri,Data类型为InputStream的factory,以下所示
.append(Uri.class, InputStream.class, new DataUrlLoader.StreamFactory<Uri>())
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
.append(Uri.class, InputStream.class, new AssetUriLoader.StreamFactory(context.getAssets()))
.append(Uri.class, InputStream.class, new MediaStoreImageThumbLoader.Factory(context))
.append(Uri.class, InputStream.class, new MediaStoreVideoThumbLoader.Factory(context))
.append(Uri.class, InputStream.class, new UriLoader.StreamFactory(contentResolver))
.append(Uri.class, InputStream.class, new UrlUriLoader.StreamFactory())
复制代码
有7个Factory知足条件,那么依次再来看这7个Factory的build方法
// DataUrlLoader.StreamFactory.java
public ModelLoader<Model, InputStream> build( @NonNull MultiModelLoaderFactory multiFactory) {
return new DataUrlLoader<>(opener);
}
复制代码
这个没什么特别的接着看下一个
// HttpUriLoader.Factory.java
public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new HttpUriLoader(multiFactory.build(GlideUrl.class, InputStream.class));
}
复制代码
构建HttpUrlLoader的时候又要去寻找能将Model类型为GlideUrl处理成Data类型为InputStream的Factory,那么接着看,经过查询注册,只发现了如下一个。
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
复制代码
那么直接看看其build方法
// HttpGlideUrlLoader.Factory.java
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new HttpGlideUrlLoader(modelCache);
}
复制代码
只是建立了一个HttpGlideUrlLoader实例就返回(这个实例才是真正的去请求网络数据),接着看其他5个能将Model(Uri)处理成Data(InputStream)的Factory
// AssetUriLoader.StreamFactory.java
public DataFetcher<InputStream> buildFetcher(AssetManager assetManager, String assetPath) {
return new StreamAssetPathFetcher(assetManager, assetPath);
}
复制代码
方法内部仅仅建立了一个StreamAssetPathFetcher实例接着看看下一个
// MediaStoreImageThumbLoader.Factory
public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new MediaStoreImageThumbLoader(context);
}
复制代码
方法内部仅仅建立了一个MediaStoreImageThumbLoader实例接着看看下一个
// MediaStoreVideoThumbLoader.java
public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new MediaStoreVideoThumbLoader(context);
}
复制代码
方法内部仅仅建立了一个MediaStoreVideoThumbLoader实例接着看看下一个
public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new UriLoader<>(this);
}
复制代码
方法内部仅仅建立了一个UriLoader实例接着看看最后一个
public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new UrlUriLoader<>(multiFactory.build(GlideUrl.class, InputStream.class));
}
复制代码
内部又寻找能将GlideUrl处理成InputStream的Factory,通过上面的分析也就只有HttpGlideUrlLoader.Factory能进行处理这里进再也不展开。
接着看能将Model(String)处理成Data(Any)的第三个Factory(StringLoader.FileDescriptorFactory)
// StringLoader.FileDescriptorFactory
public ModelLoader<String, ParcelFileDescriptor> build( @NonNull MultiModelLoaderFactory multiFactory) {
return new StringLoader<>(multiFactory.build(Uri.class, ParcelFileDescriptor.class));
}
复制代码
内部再去寻找能将Uri处理成ParcelFileDescriptor的Factory,经过查询有如下两个
.append(Uri.class,ParcelFileDescriptor.class, new AssetUriLoader.FileDescriptorFactory(context.getAssets()))
.append(Uri.class, ParcelFileDescriptor.class, new UriLoader.FileDescriptorFactory(contentResolver))
复制代码
再来分别看看
// AssetUriLoader.FileDescriptorFactory.java
public ModelLoader<Uri, ParcelFileDescriptor> build(MultiModelLoaderFactory multiFactory) {
return new AssetUriLoader<>(assetManager, this);
}
复制代码
内部仅仅建立了AssetUriLoader,接着看下一个
// UriLoader.FileDescriptorFactory.java
public ModelLoader<Uri, ParcelFileDescriptor> build(MultiModelLoaderFactory multiFactory) {
return new UriLoader<>(this);
}
复制代码
接着看能将Model(String)处理成Data(Any)的最后一个Factory(StringLoader.AssetFileDescriptorFactory)
// StringLoader.AssetFileDescriptorFactory.java
public ModelLoader<String, AssetFileDescriptor> build( @NonNull MultiModelLoaderFactory multiFactory) {
return new StringLoader<>(multiFactory.build(Uri.class, AssetFileDescriptor.class));
}
复制代码
内部又去寻找能将Uri处理成AssetFileDescriptor的Factory,经过查询就找到了下面这一个
.append(Uri.class, AssetFileDescriptor.class, new UriLoader.AssetFileDescriptorFactory(contentResolver))
复制代码
// UriLoad.AssetFileDescriptorFactory.java
public ModelLoader<Uri, AssetFileDescriptor> build(MultiModelLoaderFactory multiFactory) {
return new UriLoader<>(this);
}
复制代码
而后调用入参为Entry的build方法,内部调用了entry.factory.build(this),也就是分别调用上述4个Factory的build方法。到如今为止也就刚刚分析好了MultiModelLoaderFactory.build(String.class)这么一个方法调用,先来总结下。
能够看到Glide会寻找全部能处理Model类型String的Factory调用其build方建立ModelLoader,可是有些ModelLoader单独也不能完成一个转换,所以这些ModelLoader一般会有另外一个能帮助其完成转换的ModelLoader实例,来举个例子,StringLoader没办法将Model(String)处理成Data(InputSteam),所以其持有了一个能将Model(Uri)处理能Data(InputStream)的ModelLoad,StringLoader主要功能就是在外界调用buildLoadData
时将String类型的Url转换为Uri而后调用那个持有的ModelLoader的buildLoadData
方法,最终可能的转化路线以下所示
接着再回到getModelLoaders里面执行剩余代码
public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
int size = modelLoaders.size();
boolean isEmpty = true;
List<ModelLoader<A, ?>> filteredLoaders = Collections.emptyList();
for (int i = 0; i < size; i++) {
ModelLoader<A, ?> loader = modelLoaders.get(i);
if (loader.handles(model)) {
if (isEmpty) {
filteredLoaders = new ArrayList<>(size - i);
isEmpty = false;
}
filteredLoaders.add(loader);
}
}
return filteredLoaders;
}
// DataUrlLoader.java
public boolean handles(@NonNull Model model) {
return model.toString().startsWith(DATA_SCHEME_IMAGE);
}
复制代码
MultiModelLoaderFactory.build只是从全部Factory中找寻出全部能处理Model(String)的Factory,可是有些ModelLoader虽然说也能处理Model(String),可是拥有其局限性好比DataUrlLoader.StreamFactory.build产生的DataUrlLoader其只能处理以data:image开头的字符串,所以当该方法返回时还余下3个ModelLoader,接着再回到DecodeHelp.getLoadData中
// DecodeHelper.java
List<LoadData<?>> getLoadData() {
...
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
for (int i = 0, size = modelLoaders.size(); i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
LoadData<?> current =
modelLoader.buildLoadData(model, width, height, options);
if (current != null) {
loadData.add(current);
}
}
return loadData;
}
复制代码
内部分别调用了三个StringLoader的buildLoadData
来构建LoadData实例
public LoadData<Data> buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) {
Uri uri = parseUri(model);
if (uri == null || !uriLoader.handles(uri)) {
return null;
}
return uriLoader.buildLoadData(uri, width, height, options);
}
复制代码
能够看到首先将传入的String类型的url转化为Uri实例,而后调用uriLoader.buildLoadData构建LoadData,一个典型的代理模式,通过前面的分析uriLoader实际上是一个MutiModelLoader(内部有7个ModelLoader,这7个都能将Model(Uri)处理成Data(InputStream))
// MultiModelLoader.java
public LoadData<Data> buildLoadData(@NonNull Model model, int width, int height, @NonNull Options options) {
Key sourceKey = null;
int size = modelLoaders.size();
List<DataFetcher<Data>> fetchers = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
ModelLoader<Model, Data> modelLoader = modelLoaders.get(i);
if (modelLoader.handles(model)) {
LoadData<Data> loadData = modelLoader.buildLoadData(model, width, height, options);
if (loadData != null) {
sourceKey = loadData.sourceKey;
fetchers.add(loadData.fetcher);
}
}
}
return !fetchers.isEmpty() && sourceKey != null
? new LoadData<>(sourceKey, new MultiFetcher<>(fetchers, exceptionListPool)) : null;
}
复制代码
首先第一个DataUrlLoader因为只能处理data:image开头的字符串,因此留下6个,来看看第二个HttpUriLoader
public LoadData<InputStream> buildLoadData(@NonNull Uri model, int width, int height, @NonNull Options options) {
return urlLoader.buildLoadData(new GlideUrl(model.toString()), width, height, options);
}
复制代码
哎,又跑去调用urlLoader.buildLoadData一层层真深,这个urlLoader仍是包含了HttpGlideUrlLoader对应于前面讲的2.2.2.1
// HttpGlideUrlLoader.java
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height, @NonNull Options options) {
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
int timeout = options.get(TIMEOUT);
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}
复制代码
千辛万苦找到了返回的第一个LoadData,注意内部传入了一个HttpUrlFetcher,后面会用于网络请求,继续第三个AssetUriLoader
// AssetUriLoader.java
public boolean handles(@NonNull Uri model) {
return ContentResolver.SCHEME_FILE.equals(model.getScheme()) && !model.getPathSegments()
.isEmpty() && ASSET_PATH_SEGMENT.equals(model.getPathSegments().get(0));
}
复制代码
很明显model的scheme是http因此AssetUriLoader不知足,相似的后面三个MediaStoreImageThumbLoader、MediaStoreVideoThumbLoader、UriLoader也无法处理,直接看最后一个UrlUriLoader
// UrlUriLoader.java
private static final Set<String> SCHEMES = Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
"http",
"https"
)
)
);
public boolean handles(@NonNull Uri uri) {
return SCHEMES.contains(uri.getScheme());
}
复制代码
很明显能够处理,继续看其buildLoadData方法
public LoadData<Data> buildLoadData(@NonNull Uri uri, int width, int height, @NonNull Options options) {
GlideUrl glideUrl = new GlideUrl(uri.toString());
return urlLoader.buildLoadData(glideUrl, width, height, options);
}
复制代码
构建了一个GlideUrl实例,这里的urlLoader是一个HttpGlideUrlLoader实例对应于2.2.7,这个结果与第二个HttpUriLoader处理结果一致,第一个StringLoader到此结束,一共返回两个DataFetcher,都是HttpUrlFetcher,封装成一个LoadData(MultiFetcher)返回,后面的第2、第三个ModelLoader都不符合条件,因此最后getLoadData只会返回一个LoadData,那么getCacheKeys也就只会返回拥有一个Key的列表,继续回到ResourceCacheGenerator.startNext,方法后面根据全部注册的decoder、modelLoader、transCodeClass查询到全部能够将Data转化为对应Resource的类,最后因为没有缓存startNext最终会返回false,DataCacheGenerator也是一样的道理,最后走到SourceGenerator中
// SourceGenerator.java
public boolean startNext() {
...
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
复制代码
注意这里helper.getLoadData()获取到的就是刚开始ResourceCacheGenerator调用getLoadData保存在其中的一个列表,接着会调用fetcher.loadData,前面已经分析过fetcher就是MultiFetcher,内部拥有两个HttpUrlFetcher
// MultiFetcher.java
public void loadData( @NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {
this.priority = priority;
this.callback = callback;
exceptions = throwableListPool.acquire();
fetchers.get(currentIndex).loadData(priority, this);
}
复制代码
这里只会启动第一个HttpUrlFetcher,第二个只会在第一个失败的时候才会调用,继续看看HttpUrlFetcher.loadData
public 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) {
callback.onLoadFailed(e);
}
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {
...
urlConnection = connectionFactory.build(url);
...
urlConnection.setInstanceFollowRedirects(false);
urlConnection.connect();
stream = urlConnection.getInputStream();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (isHttpOk(statusCode)) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (isHttpRedirect(statusCode)) {
...
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
}
...
}
复制代码
内部也就是直接使用了HttpUrlConnection,而且支持重定向(最多5次),若是请求成功了则会返回一个InputStream实例,而且回调callback.onDataReady,这里的callback就是MultiFetcher
// MultiFetcher.java
public void onDataReady(@Nullable Data data) {
if (data != null) {
callback.onDataReady(data);
} else {
startNextOrFail();
}
}
复制代码
若是加载成功了那么再回调onDataReady,否则就使用下一个Fetcher请求数据或者只会返回错误,这里只看正返回成功的状况,callback是SourceGenerator
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
复制代码
默认的DiskCacheStrategy是AUTOMATIC,因此传入的REMOTE知足第一个If条件,cb是DecodeJob实例
// DecodeJob.java
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
public void reschedule(DecodeJob<?> job) {
getActiveSourceExecutor().execute(job);
}
复制代码
接着又会执行这个DecodeJob,不过此次与第一次不一样,此次调用是DecodeJob不是新建的其成员都维持着 执行顺序 DecodeJob.run -> runWrapped -> runGenerators -> SourceGenerator.startNext
// SourceGenerator.java
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
}
复制代码
此次dataToCache不是null了而是一个InputStream实例
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
helper.getDiskCache().put(originalKey, writer);
...
} finally {
loadData.fetcher.cleanup();
}
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
复制代码
首先获取到能处理InoutStream的Encoder也就是StreamEncoder,接着内部会调用到DiskLruCacheWrapper.put,方法内部又会执行Encoder.encode将输入流写入到文件中,这就不展开了。而且最后将sourceCacheGenerator进行了赋值(注意在建立DataCacheGenerator时传入的callback是SourceGenerator自己),因此会执行DataCacheGenerator.startNext
// DataCacheGenerator.java
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
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;
}
复制代码
内部根据缓存Key获取到刚刚保存的缓存文件,而后获取全部能处理Model(File)类型的ModelLoader,接着将其遍历构建LoadData,这里就不看getModelLoaders内部实现逻辑,直接用ByteBufferFileLoader进行分析,经过调用buildLoadData会构建出一个ByteBufferFetcher实例,接着看看其loadData方法
// ByteBufferFetcher.java
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {
ByteBuffer result;
try {
result = ByteBufferUtil.fromFile(file);
} catch (IOException e) {
callback.onLoadFailed(e);
return;
}
callback.onDataReady(result);
}
复制代码
将文件中全部的内容所有读取到ByteBuffer中后回调onDataReady方法内部通过一层层调用最终调用了DecodeJob.decodeFromRetrievedData,到如今为止数据还所有在这个byteBuffer中去,接下来就是寻找对应的decoder将其加载成一个Bitmap而后设置给ImageView,通过一系列判断,最终会寻找到ByteBufferBitmapDecoder
public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height, @NonNull Options options) throws IOException {
InputStream is = ByteBufferUtil.toStream(source);
return downsampler.decode(is, width, height, options);
}
复制代码
downSampler里面作了真正的操做,好比缩放图片适应当前的ImageView大小等,接着判断是否要将转化后的图片进行缓存,最后就通知外界图片加载成功。
// DecodeJob.java
private void notifyComplete(Resource<R> resource, DataSource dataSource) {
setNotifiedOrThrow();
callback.onResourceReady(resource, dataSource);
}
// EngineJob.java
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
this.resource = resource;
this.dataSource = dataSource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
private static final Handler MAIN_THREAD_HANDLER =
new Handler(Looper.getMainLooper(), new MainThreadCallback());
复制代码
那么最后接受消息的就是MainThreadCallback
public boolean handleMessage(Message message) {
EngineJob<?> job = (EngineJob<?>) message.obj;
switch (message.what) {
case MSG_COMPLETE:
job.handleResultOnMainThread();
break;
...
default:
throw new IllegalStateException("Unrecognized message: " + message.what);
}
return true;
}
复制代码
这里最终通过一层层回调会在主线程调用ImageViewTarget.onResourceReady
// ImageViewTarget.java
public 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) {
setResource(resource);
maybeUpdateAnimatable(resource);
}
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
复制代码
到此为止一张图片就加载完毕了