概述
Android开源的图片加载框架有很多,常见的四种分别是:ImageLoader、Picasso、Glide和Fresco。其实,这些框架加载普通的图片,使用方法都差不多。这篇文章,我也不会花很多的篇幅去比较几种框架的优缺点。
这几种框架我都使用过,都是比较简单的几行代码就可以实现图片的加载。在实际项目开发中,我基本是使用Glide。放一下GitHub上的star数。ImageLoader(16.5K),Picasso(16.9K),Glide(26.5K),Fresco(15.7K)。嗯,还是Glide的星星多一点。接下来,简单的总结一下Glide的基本使用。
一.Glide的导入
首先,Glide的GitHub地址:https://github.com/bumptech/glide,可以看到,当前最新版本是4.9.0。我们在gradle文件中添加依赖:
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
二.Glide的使用
1.Glide加载普通图片
Glide加载普通图片,需要三个参数:Context,URL,ImageView。无论是网络图片,还是drawable下的图片,还是手机内存里的图片,都只需要指定正确的URL即可。
Glide.with(LoginActivity.this)
.load(R.drawable.login_logo)
.into(mLoginLogo);
2.Glide加载圆角图片
Glide加载圆角图片,除了Context,url,ImageView外,还需要另一个参数指定圆角的半径:
Glide.with(LoginActivity.this)
.load(R.drawable.login_logo)
.apply(RequestOptions.bitmapTransform(new RoundedCorners(20)))
.into(mLoginLogo);
3.Glide加载圆形图片
Glide.with(LoginActivity.this)
.load(R.drawable.login_logo)
.apply(RequestOptions.bitmapTransform(new CircleCrop()))
.into(mLoginLogo);
4.Glide加载gif图片
Glide支持加载gif图片,使用方法与加载普通图片一样,也可以为其指定圆角或者圆形。
Glide.with(LoginActivity.this)
.load(R.drawable.test)
.into(mLoginLogo);
三.Glide源码分析
1.with()
通过上面对Glide的基本使用,我们对Glide的源码做一下分析。首先,我们看下.with()方法的源码。如我们前面所说的,with方法需要我们传入context,可以看到,context可以是任何context:activity,fragmentActivity,fragment等。然后,会通过传入的context通过getRetriever().get()方法返回一个RequestManager对象。也就是说,通过with方法传入需要使用Glide加载图片的Activity或者Fragment,然后为这个Activity或者fragment提供一个用于加载图片的RequestManager。
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
public static RequestManager with(@NonNull Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
public static RequestManager with(@NonNull android.app.Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
public static RequestManager with(@NonNull View view) {
return getRetriever(view.getContext()).get(view);
}
我们看一下RequestManager这个类的介绍:
/**
* A class for managing and starting requests for Glide. Can use activity, fragment and connectivity
* lifecycle events to intelligently stop, start, and restart requests. Retrieve either by
* instantiating a new object, or to take advantage built in Activity and Fragment lifecycle
* handling, use the static Glide.load methods with your Fragment or Activity.
一个为Glide管理和开始请求的类。可以通过activity,fragment等的生命周期来停止、开始和重新开始请求。所谓的请求,就是加载图片的请求。而RequestManager可以通过Activity等的生命周期来自动的管理图片的加载。例如,在Activity执行onDestroy的时候,Glide应该停止图片的加载。
2.load()
接下来,看一下load方法。通过前面的介绍,我们知道,load方法接收的是图片的路径,这个路径可以是任意的路径。通过源码我们可以知道,load接收的参数,可以是下面的任意一种:bitmap,drawable,string,uri,file,resourceId,url,byte数组,Object。
public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) {
return asDrawable().load(bitmap);
}
public RequestBuilder<Drawable> load(@Nullable Drawable drawable) {
return asDrawable().load(drawable);
}
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder<Drawable> load(@Nullable Uri uri) {
return asDrawable().load(uri);
}
public RequestBuilder<Drawable> load(@Nullable File file) {
return asDrawable().load(file);
}
public RequestBuilder<Drawable> load(@RawRes @DrawableRes @Nullable Integer resourceId) {
return asDrawable().load(resourceId);
}
public RequestBuilder<Drawable> load(@Nullable URL url) {
return asDrawable().load(url);
}
public RequestBuilder<Drawable> load(@Nullable byte[] model) {
return asDrawable().load(model);
}
public RequestBuilder<Drawable> load(@Nullable Object model) {
return asDrawable().load(model);
}
继续跟踪asDrawable().load()方法,先看一下第一个asDrawable方法:
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
继续跟踪as方法,可以看到,在这里获取了一个RequestBuilder对象。
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
我们跟踪第二个方法load方法,也是跳转到RequestBuilder类:
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
继续跟踪loadGeneric方法,通过这一系列的方法,最后,我们的url传到了这里,同时,isModelSet置为true,也就是标志着我们的数据源设置成功,最后返回RequestBuilder自己。
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
3.into()
最后,我们看下into方法的源码。这个方法里面,会获取imageview的ScaleType属性,根据不同的ScaleType得到不同的requestOptions参数。
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());
}
上面,最后return into(),我们继续跟踪。可以看到,前面通过load设置的isModelSet在这起作用了。如果我们没有调用load方法,也就是没有设置数据源,会抛出一个异常。
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);
requestManager.track(target, request);
return target;
}
上面的方法,我们看到一个target,首先,我们先跟踪一下target是个什么东西:
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
继续跟踪buildTarget方法,通过对图片类型的判断,创建并返回与图片来源对应的imageViewTarget:
public class ImageViewTargetFactory {
@NonNull
@SuppressWarnings("unchecked")
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我们分析到此为止,继续看一下into方法的其他代码,我们会发现其中这么两行代码:
Request request = buildRequest(target, targetListener, options, callbackExecutor);
target.setRequest(request);
requestManager.track(target, request);
继续看一下buildRequest方法,我们发现,这个加载图片的request里面有很多参数,例如target和宽高信息。后面,target.setRequest()。最后,调用RequestManager的track方法。
private Request buildRequest(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
return buildRequestRecursive(
target,
targetListener,
/*parentCoordinator=*/ null,
transitionOptions,
requestOptions.getPriority(),
requestOptions.getOverrideWidth(),
requestOptions.getOverrideHeight(),
requestOptions,
callbackExecutor);
}
前面我们就知道了,RequestManager是管理图片加载开始和停止的类。我们继续看一下track方法。track方法里,我们看到了runRequest方法。不管英语好不好,看到runRequest基本都能猜到意思:执行一个请求。
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
接下来,看一下runRequest方法。如果请求的标志不是暂停的,开始这个请求,如果请求的标志是暂停的,那么清除这个请求,也就是停止加载图片的请求。
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);
}
}
接下来,毫无疑问的,我们需要看一下begin这个方法,点进去,是Request接口,实现begin方法的是实现Request的SingleRequest类,我们看一下begin 方法:
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));
}
}
大体的过程如下:
(1)获取宽高,如果有重新设置宽高并且均大于零,那么宽高使用重新设置的宽高。
(2)宽高信息确定后,不论是处于RUNNING状态还是WAITING_FOR_SIZE状态,首先会加载占位图。
(3)在WAITING_FOR_SIZE下,如果宽高合法,那么会走onSizeReady方法。至此,我们还没看到解码图片的代码,我们跟踪onSizeReady方法:
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.load方法,继续跟踪:
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);
}
上面的代码,我们发现两个疑似加载图片的地方:loadFromActiveResources,loadFromCache。这其实是两种加载图片的方式,从弱引用加载和从缓存加载。这两个方法位于Engine类中,Engine类负责开始加载图片和管理弱引用和缓存的资源。
engineJob添加了一个回调接口:engineJob.addCallback(cb, callbackExecutor)。跟进去,最后实际上就是上面两种解码方式的onResourceReady回调:
public synchronized void onResourceReady(Resource<?> resource, DataSource dataSource) {
stateVerifier.throwIfRecycled();
loadStatus = null;
if (resource == null) {
GlideException exception = new GlideException("Expected to receive a Resource<R> with an "
+ "object of " + transcodeClass + " inside, but instead got null.");
onLoadFailed(exception);
return;
}
Object received = resource.get();
if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
releaseResource(resource);
GlideException exception = new GlideException("Expected to receive an object of "
+ transcodeClass + " but instead" + " got "
+ (received != null ? received.getClass() : "") + "{" + received + "} inside" + " "
+ "Resource{" + resource + "}."
+ (received != null ? "" : " " + "To indicate failure return a null Resource "
+ "object, rather than a Resource object containing null data."));
onLoadFailed(exception);
return;
}
if (!canSetResource()) {
releaseResource(resource);
// We can't put the status to complete before asking canSetResource().
status = Status.COMPLETE;
return;
}
onResourceReady((Resource<R>) resource, (R) received, dataSource);
}
核心还是最后一行代码,继续跟踪进去:
private synchronized void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
// We must call isFirstReadyResource before setting status.
boolean isFirstResource = isFirstReadyResource();
status = Status.COMPLETE;
this.resource = resource;
if (glideContext.getLogLevel() <= Log.DEBUG) {
Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from "
+ dataSource + " for " + model + " with size [" + width + "x" + height + "] in "
+ LogTime.getElapsedMillis(startTime) + " ms");
}
isCallingCallbacks = true;
try {
boolean anyListenerHandledUpdatingTarget = false;
if (requestListeners != null) {
for (RequestListener<R> listener : requestListeners) {
anyListenerHandledUpdatingTarget |=
listener.onResourceReady(result, model, target, dataSource, isFirstResource);
}
}
anyListenerHandledUpdatingTarget |=
targetListener != null
&& targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);
if (!anyListenerHandledUpdatingTarget) {
Transition<? super R> animation =
animationFactory.build(dataSource, isFirstResource);
target.onResourceReady(result, animation);
}
} finally {
isCallingCallbacks = false;
}
notifyLoadSuccess();
}
核心代码是target.onResourceReady(result, animation)。实际执行这个方法的是抽象类ImageViewTarget:
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
setResourceInternal(resource);
} else {
maybeUpdateAnimatable(resource);
}
}
继续跟踪方法setResourceInternal(resource):
private void setResourceInternal(@Nullable Z resource) {
// Order matters here. Set the resource first to make sure that the Drawable has a valid and
// non-null Callback before starting it.
setResource(resource);
maybeUpdateAnimatable(resource);
}
继续跟踪setResource方法,这是抽象类ImageViewTarget一个抽象方法,跟踪到实现类DrawableImageViewTarget。最后,我们发现,通过imageview的setDrawable方法,将drawable显示到ImageView中。
public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
public DrawableImageViewTarget(ImageView view) {
super(view);
}
/**
* @deprecated Use {@link #waitForLayout()} instead.
*/
// Public API.
@SuppressWarnings({"unused", "deprecation"})
@Deprecated
public DrawableImageViewTarget(ImageView view, boolean waitForLayout) {
super(view, waitForLayout);
}
@Override
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
}
后面,有个EnginJob和DecodeJob,并且通过engineJob.start(decodeJob)来开始解码,跟踪进去,最终是通过线程池来执行解码的任务:
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
最后,总结一下,使用Glide加载图片确实很方便,只需要几行代码即可。其实我们没有必要太去关注Glide的源码,只要会基本的使用就可以了,跟踪源码其实是一个很漫长很复杂的过程,跟踪到最后,好几个小时,都有点坚持不下去的感觉了。最后,坚持跟踪完into方法。好了,周末结束,洗漱睡觉,下次再见!
最后
以上就是潇洒魔镜为你收集整理的Android最火的框架系列(六)Glide的全部内容,希望文章能够帮你解决Android最火的框架系列(六)Glide所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复