一、Glide几个基本概念
-
Model
表示数据的来源;加载图片的时候需要告诉Glide从哪获取这个图片,可以是url类型、本地文件类型、资源ID类型。不管什么类型,Glide都将其称为Model。 -
Data
从数据源中获取到model之后,把它加工成原始数据,一般就是InputStream输入流,Glide把 它们称之为Data。负责从数据源中获取原始数据的功能角色称之为ModelLoader。 -
Resource
负责将得到的原始数据进行解码,比如将InputStream解码成bitmap,而解码之后的资源称之为Resource。而负责解码的功能角色称之为Decoder。 -
TransformedResource
把Resouce进行变换,比如图片要进行裁剪,调用fitcenter、centerCrop等等方法转换,在Glide方法中采用Resource Transform功能来进行转换,而转换后的资源就称之为TransformedResource。 -
TranscodedResource
转码,Glide不仅能加载静态图之外,还能加载Gif动态图,但是解码之后的bitmap和gif drawable其实类型都是统一的,为了逻辑方便处理,Glide会把bitmap转换成GlideBitmapDrawable,这样类型就统一了,负责转码的角色称为Transcode,而转码之后的角色称为TranscodedResource -
Target
最终将图片显示到目标上,比如ImageView上,这时候Glide就会将要显示的目标封装成Target。
二、Glide基本使用和参数说明:
Glide.with(getApplicationContext()) //指定Context
.load(url) //指定图片的URL
.placeholder(R.mipmap.ic_launcher) //指定图片未成功加载前显示的图片
.error(R.mipmap.ic_launcher) //指定图片加载失败显示的图片
.override(300, 300) //指定图片的尺寸
.fitCenter() //指定图片缩放类型
.centerCrop() //指定图片缩放类型
.skipMemoryCache(true) //跳过内存缓存,默认为false
.crossFade(1000) //设置渐变式显示的时间
.diskCacheStrategy(DiskCacheStrategy.NONE) //表示不缓存任何内容,跳过磁盘缓存
.diskCacheStrategy(DiskCacheStrategy.DATA) //表示只缓存原始图片,即只缓存原来的全分辨率的图片
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) //表示只缓存转换后的图片
.diskCacheStrategy(DiskCacheStrategy.ALL) //表示即缓存原始图片,也缓存转换过后的图片
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) //表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)
.priority(Priority.HIGH) //指定优先级,Glide将会用他们作为一个准则,并尽可能的处理这些请求,但是它不一定能百分之百保证所有的图片都按照你优先级来加载
.into(imageView); //指定显示图片的ImageView
1. with流程解析
/**
* Begin a load with Glide by passing in a context.
*
* <p> Any requests started using a context will only have the application level options applied
* and will not be started or stopped based on lifecycle events. In general, loads should be
* started at the level the result will be used in. If the resource will be used in a view in a
* child fragment, the load should be started with {@link #with(android.app.Fragment)}} using that
* child fragment. Similarly, if the resource will be used in a view in the parent fragment, the
* load should be started with {@link #with(android.app.Fragment)} using the parent fragment. In
* the same vein, if the resource will be used in a view in an activity, the load should be
* started with {@link #with(android.app.Activity)}}. </p>
*
* <p> This method is appropriate for resources that will be used outside of the normal fragment
* or activity lifecycle (For example in services, or for notification thumbnails). </p>
*
* @param context Any context, will not be retained.
* @return A RequestManager for the top level application that can be used to start a load.
* @see #with(android.app.Activity)
* @see #with(android.app.Fragment)
* @see #with(android.support.v4.app.Fragment)
* @see #with(android.support.v4.app.FragmentActivity)
*/
@NonNull
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
/**
* Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle
* and that uses the given {@link Activity}'s default options.
*
* @param activity The activity to use.
* @return A RequestManager for the given activity that can be used to start a load.
*/
@NonNull
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
/**
* Begin a load with Glide that will tied to the give
* {@link android.support.v4.app.FragmentActivity}'s lifecycle and that uses the given
* {@link android.support.v4.app.FragmentActivity}'s default options.
*
* @param activity The activity to use.
* @return A RequestManager for the given FragmentActivity that can be used to start a load.
*/
@NonNull
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
/**
* Begin a load with Glide that will be tied to the given
* {@link android.support.v4.app.Fragment}'s lifecycle and that uses the given
* {@link android.support.v4.app.Fragment}'s default options.
*
* @param fragment The fragment to use.
* @return A RequestManager for the given Fragment that can be used to start a load.
*/
@NonNull
public static RequestManager with(@NonNull Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
/**
* Begin a load with Glide that will be tied to the given {@link android.app.Fragment}'s lifecycle
* and that uses the given {@link android.app.Fragment}'s default options.
*
* @param fragment The fragment to use.
* @return A RequestManager for the given Fragment that can be used to start a load.
* @deprecated Prefer support Fragments and {@link #with(Fragment)} instead,
* {@link android.app.Fragment} will be deprecated. See
* https://github.com/android/android-ktx/pull/161#issuecomment-363270555.
*/
@SuppressWarnings("deprecation")
@Deprecated
@NonNull
public static RequestManager with(@NonNull android.app.Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
/**
* Begin a load with Glide that will be tied to the lifecycle of the {@link Fragment},
* {@link android.app.Fragment}, or {@link Activity} that contains the View.
*
* <p>A {@link Fragment} or {@link android.app.Fragment} is assumed to contain a View if the View
* is a child of the View returned by the {@link Fragment#getView()}} method.
*
* <p>This method will not work if the View is not attached. Prefer the Activity and Fragment
* variants unless you're loading in a View subclass.
*
* <p>This method may be inefficient aways and is definitely inefficient for large hierarchies.
* Consider memoizing the result after the View is attached or again, prefer the Activity and
* Fragment variants whenever possible.
*
* <p>When used in Applications that use the non-support {@link android.app.Fragment} classes,
* calling this method will produce noisy logs from {@link android.app.FragmentManager}. Consider
* avoiding entirely or using the {@link Fragment}s from the support library instead.
*
* <p>If the support {@link FragmentActivity} class is used, this method will only attempt to
* discover support {@link Fragment}s. Any non-support {@link android.app.Fragment}s attached
* to the {@link FragmentActivity} will be ignored.
*
* @param view The view to search for a containing Fragment or Activity from.
* @return A RequestManager that can be used to start a load.
*/
@NonNull
public static RequestManager with(@NonNull View view) {
return getRetriever(view.getContext()).get(view);
}
with()方法参数可以为Context、Activity、FragmentActivity、Fragment、View。with设计了这么多重装方法就是为了可以灵活根据当前的上下文和组件进行不同图片的操作选择。将图片加载的过程与组件的生命周期进行挂钩。
下面以参数为Activity进行流程阐述:
/**
* Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle
* and that uses the given {@link Activity}'s default options.
*
* @param activity The activity to use.
* @return A RequestManager for the given activity that can be used to start a load.
*/
@NonNull
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
getRetriever(activity)是获取RequestManagerRetriever对象
@NonNull
public RequestManager get(@NonNull Activity activity) {
if (Util.isOnBackgroundThread()) { // ①
return get(activity.getApplicationContext());
} else { // ②
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
如果当前是后台线程(即子线程)就走流程①,否则走流程②。
- 流程①最后是通过getApplicationManager(context)语句DCL单例模式来获取RequestManager对象。
- 流程②
RequestManagerFragment 是一种没有UI界面的fragment
glide添加一种没有界面的fragment到Activity或fragment上,通过这种没有界面RequestManagerFragment 的这个fragment来监听activity或fragment的生命周期,因为glide是无法监听activity的生命周期,从而来完成绑定activity的生命周期,来去选择完成图片加载操作的过程。
通过RequestManager就可以控制整个界面生命周期的监听,用于监听整个组件的生命周期,根据这个生命周期来做图片的相应操作。
RequestManagerFragment 与 RequestManager是一 一对应的
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager); // 将RequestManagerFragment这个空界面的fragment与RequestManager图片加载进行绑定,目的就是为了将监听activity生命周期的来管理整个图片加载
}
return requestManager;
}
with方流程总结: 主要是为了获取RequestManager对象,该对象是用于管理请求;通过RequestManagerRetriever生产RequestManager这个类来处理的;
由于glide绑定了组件的生命周期,这样可以根据不同的生命周期来进行相应的处理
2. load流程解析
glide支持多种图片的来源,包含url、本地文件、资源ID等等,从而会重写不同参数类型的load()方法。
interface ModelTypes<T> {
@NonNull
@CheckResult
T load(@Nullable Bitmap bitmap);
@NonNull
@CheckResult
T load(@Nullable Drawable drawable);
@NonNull
@CheckResult
T load(@Nullable String string);
@NonNull
@CheckResult
T load(@Nullable Uri uri);
@NonNull
@CheckResult
T load(@Nullable File file);
@NonNull
@CheckResult
T load(@RawRes @DrawableRes @Nullable Integer resourceId);
@Deprecated
@CheckResult
T load(@Nullable URL url);
@NonNull
@CheckResult
T load(@Nullable byte[] model);
@NonNull
@CheckResult
@SuppressWarnings("unchecked")
T load(@Nullable Object model);
}
@Override
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
@NonNull
@Override
@CheckResult
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
**load总结:**其实也就是一些初始化的创建工作
3. init流程解析
/**
* Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into
* the view, and frees any resources Glide may have previously loaded into the view so they may be
* reused.
*
* @see RequestManager#clear(Target)
*
* @param view The view to cancel previous loads for and load the new resource into.
* @return The
* {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}.
*/
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
BaseRequestOptions<?> requestOptions = this;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
// Clone in this method so that if we use this RequestBuilder to load into a View and then
// into a different target, we don't retain the transformation applied based on the previous
// View's scale type.
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions = requestOptions.clone().optionalCenterCrop();
break;
case CENTER_INSIDE:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions = requestOptions.clone().optionalFitCenter();
break;
case FIT_XY:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case CENTER:
case MATRIX:
default:
// Do nothing.
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
}
Util.assertMainThread(); 这个语句就可以确定into方法必须要在主线程中进行操作,
glideContext.buildImageViewTarget(view, transcodeClass)就是为了构建一个target对象
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)");
}
}
into操作主要就是图片加载,
- 首先会对操作是否在主线程中进行判断,
- 之后创建所需要的target
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request request = buildRequest(target, targetListener, options, callbackExecutor);
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.clear(target);
target.setRequest(request); //设置tag,将tag与图片request进行绑定,防止图片错位
requestManager.track(target, request);
return target;
}
RequestManager.java
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
RequestTracker.java
/**
* Starts tracking the given request.
*/
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
SingleRequest.java
@Override
public synchronized void begin() {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Only log at more verbose log levels if the user has set a fallback drawable, because
// fallback Drawables indicate the user expects null models occasionally.
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
// If we're restarted after we're complete (usually via something like a notifyDataSetChanged
// that starts an identical request into the same Target or View), we can simply use the
// resource and size we retrieved the last time around and skip obtaining a new size, starting a
// new load etc. This does mean that users who want to restart a load because they expect that
// the view size has changed will need to explicitly clear the View or Target before starting
// the new load.
if (status == Status.COMPLETE) {
onResourceReady(resource, DataSource.MEMORY_CACHE);
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)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
SingleRequest.java
/**
* A callback method that should never be invoked directly.
*/
@Override
public synchronized 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;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
if (IS_VERBOSE_LOGGABLE) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
loadStatus =
engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);
// This is a hack that's only useful for testing right now where loads complete synchronously
// even though under any executor running on any thread but the main thread, the load would
// have completed asynchronously.
if (status != Status.RUNNING) {
loadStatus = null;
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
Engine.java
/**
* Starts a load for the given arguments.
*
* <p>Must be called on the main thread.
*
* <p>The flow for any request is as follows:
*
* <ul>
* <li>Check the current set of actively used resources, return the active resource if present,
* and move any newly inactive resources into the memory cache.
* <li>Check the memory cache and provide the cached resource if present.
* <li>Check the current set of in progress loads and add the cb to the in progress load if one
* is present.
* <li>Start a new load.
* </ul>
*
* <p>Active resources are those that have been provided to at least one request and have not yet
* been released. Once all consumers of a resource have released that resource, the resource then
* goes to cache. If the resource is ever returned to a new consumer from cache, it is re-added to
* the active resources. If the resource is evicted from the cache, its resources are recycled and
* re-used if possible and the resource is discarded. There is no strict requirement that
* consumers release their resources so active resources are held weakly.
*
* @param width The target width in pixels of the desired resource.
* @param height The target height in pixels of the desired resource.
* @param cb The callback that will be called when the load completes.
*/
public synchronized <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
EngineResource<?> 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, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}