前言

最近可能会写一系列文章,结合源码分析一些主流第三方库的设计与实现。为了条理清晰,每篇文章都会在前言里列出一些要点,然后按照这些要点来具体分析。

这次要分析的是当前最火热的图片加载框架GlideGlide最初的主旨是为了实现列表中图片的平滑加载,如今它已经发展成了功能全面的图片加载框架,而且star数也后来居上,超越了同为Android端主流图片加载框架的FrescoPicassoGlide这么受欢迎自然在设计上有属于自己的亮点,所以本次分析的内容要点包括:

  • Glide的生命周期绑定
  • Glide的缓存设计(三级缓存、Lru算法、Bitmap复用)
  • Glide的完整加载过程

生命周期绑定

Glide的一大特色是可以控制图片的加载状态与当前页面的生命周期同步,使整个加载过程随着页面的状态而启动/恢复、停止、销毁,在LifecycleListener接口中就定义了这么几种状态用来同步监听页面的生命周期事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface LifecycleListener {
/**
* Callback for when {@link android.app.Fragment#onStart()}} or {@link
* android.app.Activity#onStart()} is called.
*/
void onStart();
/**
* Callback for when {@link android.app.Fragment#onStop()}} or {@link
* android.app.Activity#onStop()}} is called.
*/
void onStop();
/**
* Callback for when {@link android.app.Fragment#onDestroy()}} or {@link
* android.app.Activity#onDestroy()} is called.
*/
void onDestroy();
}

那么这几种状态是如何同当前页面状态绑定的呢?显然和我们平时常用的Glide.with(context)有关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
* @see #with(android.app.Activity)
* @see #with(android.app.Fragment)
* @see #with(android.support.v4.app.Fragment)
* @see #with(android.support.v4.app.FragmentActivity)
*/
public static RequestManager with(Context context) {
return getRetriever(context).get(context);
}
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
Preconditions.checkNotNull(
"You cannot start a load on a not yet attached View or a Fragment where getActivity() "
+ "returns null (which usually occurs when getActivity() is called before the Fragment "
+ "is attached or after the Fragment is destroyed).");
return Glide.get(context).getRequestManagerRetriever();
}

with有多种重载,支持ActivityFragmentFragmentActivity等各种Context对象,这个方法最终会返回一个图片请求管理器RequestManager,而RequestManager是由Glide的成员变量RequestManagerRetriever执行get方法获得的,进入RequestManagerRetriever的源码中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public RequestManager get(Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
public RequestManager get(Activity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(activity, fm, null /*parentHint*/);
}
}
private RequestManager fragmentGet(Context context, android.app.FragmentManager fm,
android.app.Fragment parentHint) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
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.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
RequestManagerFragment getRequestManagerFragment(
final android.app.FragmentManager fm, android.app.Fragment parentHint) {
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}

可以看到根据Context的不同类型get方法也有许多重载,比较特殊的情况是return getApplicationManager(context),当你在子线程调用with方法或者传入的Context是Application,那么绑定的生命周期就是Application级别的,至于其他情况这里只截取了参数为Activityget方法,最终RequestManagerfragmentGet方法返回,在这个方法中通过getRequestManagerFragment方法创建并插入了一个RequestManagerFragment到当前页面中,注意创建时会先判断当前页面是否已经存在该Fragment,不存在时才会去new这个Fragment,该Fragment中没有添加View,是一个不可见的Fragment,接着进入它的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RequestManagerFragment extends Fragment {
private final ActivityFragmentLifecycle lifecycle;
@Override
public void onStart() {
super.onStart();
lifecycle.onStart();
}
@Override
public void onStop() {
super.onStop();
lifecycle.onStop();
}
@Override
public void onDestroy() {
super.onDestroy();
lifecycle.onDestroy();
}
}

在这个类中所有的生命周期事件都绑定到了ActivityFragmentLifecycle中的对应方法上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ActivityFragmentLifecycle implements Lifecycle {
private final Set<LifecycleListener> lifecycleListeners =
Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>());
void onStart() {
isStarted = true;
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onStart();
}
}
void onStop() {
isStarted = false;
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onStop();
}
}
void onDestroy() {
isDestroyed = true;
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onDestroy();
}
}
}

ActivityFragmentLifecycle则把生命周期时间通知给它所持有的所有LifecycleListener实现类,这里的实现类正是public class RequestManager implements LifecycleListener,这里是不是看到了观察者模式的影子?当前页面中不可见的RequestManagerFragment做为被观察者负责提供生命周期事件,当前页面下所有的图片请求管理器RequestManager做为观察者响应事件,处理逻辑,至此整个绑定流程就打通了。

此外在Glide的manager包下还有一个网络状态管理接口ConnectivityMonitor也继承了LifecycleListener,它的实现类DefaultConnectivityMonitor会在onStart的回调中注册监听网络状态的广播,在onStop中取消注册,并且在网络状态变化时把消息回调给RequestManager做相应处理,它作为RequestManager的一个成员对象会和RequestManager一起被注册到ActivityFragmentLifecycle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DefaultConnectivityMonitor implements ConnectivityMonitor {
private final BroadcastReceiver connectivityReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
boolean wasConnected = isConnected;
isConnected = isConnected(context);
if (wasConnected != isConnected) {
listener.onConnectivityChanged(isConnected);
}
}
};
@Override
public void onStart() {
register();
}
@Override
public void onStop() {
unregister();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class RequestManager implements LifecycleListener {
//构造函数中...省略部分代码
if (Util.isOnBackgroundThread()) {
mainHandler.post(addSelfToLifecycle);
} else {
lifecycle.addListener(this);
}
lifecycle.addListener(connectivityMonitor);
//触发各生命周期事件的回调
@Override
public void onStart() {
Util.assertMainThread();
requestTracker.resumeRequests();
targetTracker.onStart();
}
@Override
public void onStop() {
Util.assertMainThread();
requestTracker.pauseRequests();
targetTracker.onStop();
}
@Override
public void onDestroy() {
targetTracker.onDestroy();
for (Target<?> target : targetTracker.getAll()) {
clear(target);
}
targetTracker.clear();
requestTracker.clearRequests();
lifecycle.removeListener(this);
lifecycle.removeListener(connectivityMonitor);
mainHandler.removeCallbacks(addSelfToLifecycle);
glide.unregisterRequestManager(this);
}
}

总结:每个涉及Glide图片加载的页面都会插入一个不可见的RequestManagerFragment负责提供生命周期事件,包含一个ActivityFragmentLifecycle(Lifecycle)负责持有LifecycleListener并把事件分发给他们,包含一个或多个RequestManager(LifecycleListener)接收事件,并由RequestTracker类和TargetTracker类实际控制请求的停止或恢复。

缓存设计

在Glide中,资源的基本类型是Resource<Z>bitmap, bytes, drawable, file, gif等都可以视为Resource, 对于Resource资源采用了三级缓存策略,而其中针对bitmap又有专门的缓存池LruBitmapPool和复用策略LruPoolStrategy,下面针对这两种类型分别来做讨论:

0.Resource缓存设计

在Glide中负责资源实际加载与管理的类是Engine,该类的load方法反映了图片的加载过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean onlyRetrieveFromCache, ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
EngineJob<?> current = jobs.get(key);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable,
useUnlimitedSourceExecutorPool);
DecodeJob<R> decodeJob = decodeJobFactory.build(glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations,
isTransformationRequired, onlyRetrieveFromCache, options, engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}

首先是调用loadFromCache从内存缓存中加载资源,未命中则loadFromActiveResources从使用中的资源中加载,仍未命中则创建一个EngineJob

1
2
3
4
5
6
7
8
9
10
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
private GlideExecutor getActiveSourceExecutor() {
return useUnlimitedSourceGeneratorPool ? sourceUnlimitedExecutor : sourceExecutor;
}

EngineJob中判断是否尝试从磁盘缓存中读取,是的话则由diskCacheExecutor线程池去负责执行加载任务,否则从数据源中加载,由sourceExecutor负责,线程池实际执行的任务是实现了Runnable接口的DecodeJob<R>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//DecodeJob.java
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根据需要加载的类型是Cache还是Source创建不同的DataFetcherGenerator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SourceGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback<Object>,
DataFetcherGenerator.FetcherReadyCallback {
public boolean startNext() {
//省略
loadData.fetcher.loadData(helper.getPriority(), this);
}
@Override
public void onDataReady(Object data) {
//省略
}
@Override
public void onLoadFailed(Exception e) {
//省略
}
}

DataFetcherGenerator使用具有诸多实现类的DataFetcher完成数据的获取,并在onDataReady回调中接收结果,比如负责加载网络数据的Fetcher是HttpUrlFetcher,至此完成加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//HttpUrlFetcher.java
@Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
final InputStream result;
try {
result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/,
glideUrl.getHeaders());
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
return;
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)
+ " ms and loaded " + result);
}
callback.onDataReady(result);
}

根据上面的加载过程可以总结出Resource的缓存设计:

  • 首先尝试从MemoryCache(LruResourceCache)中取资源,存储结构是LinkedHashMap<T, Y>
  • 然后尝试从ActiveResources中取资源,存储结构是Map<Key, WeakReference<EngineResource<?>>>,这层相当于提供了一层更灵活的内存缓存,内存充足时缓存更多资源,紧张时则释放资源
  • 接着尝试从DiskCache(DiskLruCache)中取资源,存储结构是LinkedHashMap<String, Entry>
  • 最后从Source中取资源,可以是网络资源、本地原始资源等等

1. Bitmap缓存设计

为了实现对Bitmap的高效管理,Glide专门设置了LruBitmapPool用来缓存Bitmap,无论是原始资源解码出的Bitmap还是经过transform变换处理后的Bitmap都会存储在其中。同时还设置了复用策略,避免Bitmap反复创建。缓存池+复用策略使得Glide在列表控件上性能表现的很出色。

LruBitmapPool本身依旧是一个实现了Lru算法的容器,就不赘述了,复用策略LruPoolStrategy在Glide中有三种实现:

  • AttributeStrategy针对4.4以下版本的策略,当前图片必须和被复用图片大小一致
  • SizeStrategy4.4以上版本的策略,当前图片比被复用图片小即可复用
  • SizeConfigStrategy4.4以上版本的策略,当前图片比 被复用图片小且Config一致即可复用

SizeConfigStrategy为例,需要根据当前图片size的大小和Config去keyPool中找出满足复用条件的Bitmap的key,然后去BitmapPool中取到待复用的Bitmap资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private Key findBestKey(int size, Bitmap.Config config) {
Key result = keyPool.get(size, config);
for (Bitmap.Config possibleConfig : getInConfigs(config)) {
NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
if (possibleSize != size
|| (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
keyPool.offer(result);
result = keyPool.get(possibleSize, possibleConfig);
}
break;
}
}
return result;
}
public Bitmap get(int width, int height, Bitmap.Config config) {
int size = Util.getBitmapByteSize(width, height, config);
Key bestKey = findBestKey(size, config);
Bitmap result = groupedMap.get(bestKey);
if (result != null) {
// Decrement must be called before reconfigure.
decrementBitmapOfSize(bestKey.size, result);
result.reconfigure(width, height,
result.getConfig() != null ? result.getConfig() : Bitmap.Config.ARGB_8888);
}
return result;
}

把待复用Bitmap设置给当前Bitmap的options.inBitmap,实现复用

1
options.inBitmap = bitmapPool.getDirty(width, height, options.inPreferredConfig);

完整加载过程

上面已经聊过生命周期绑定和缓存策略了,现在该走通整个加载流程了,从load(String).into(ImageView)这里继续。

1
2
3
4
5
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}

load方法的内容很少,返回一个Build模式的Builder类,只是用来给Request对象提供参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request previous = target.getRequest();
if (previous != null) {
requestManager.clear(target);
}
requestOptions.lock();
Request request = buildRequest(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
public Target<TranscodeType> into(ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
if (requestOptions.isLocked()) {
requestOptions = requestOptions.clone();
}
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions.optionalCenterCrop();
break;
case CENTER_INSIDE:
requestOptions.optionalCenterInside();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions.optionalFitCenter();
break;
case FIT_XY:
requestOptions.optionalCenterInside();
break;
case CENTER:
case MATRIX:
default:
// Do nothing.
}
}
return into(context.buildImageViewTarget(view, transcodeClass));
}
public void setRequest(@Nullable Request request) {
setTag(request);
}
@Override
@Nullable
public Request getRequest() {
Object tag = getTag();
Request request = null;
if (tag != null) {
if (tag instanceof Request) {
request = (Request) tag;
} else {
throw new IllegalArgumentException(
"You must not call setTag() on a view Glide is targeting");
}
}
return request;
}

into有两个重载方法,其中一个会把ImageView封装成为Target并且根据scaleType属性对图片做处理,之后清除对象上之前可能存在的旧的请求(View复用时会留有之前的Request),创建新的请求,并和Target互相绑定。Target的getRequestsetRequest都会操作View的Tag,把Request记录到Tag里,这也是Glide不允许我们直接使用setTag的原因,最后由RequestManager发起请求,执行Request的begin方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Override
public void begin() {
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;
}
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 (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
public void onSizeReady(int width, int height) {
@Override
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
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 (Log.isLoggable(TAG, Log.VERBOSE)) {
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.getOptions(), requestOptions.isMemoryCacheable(), requestOptions.getUseUnlimitedSourceGeneratorsPool(), requestOptions.getOnlyRetrieveFromCache(), this);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
}

begin方法中确定Size的大小并产生onSizeReady回调,同时设置PlaceholderDrawable占位图,在onSizeReady中设置状态为Status.RUNNING并调用engine.load,至此就和前面的内容对接上了,在Resource缓存策略中已经介绍了Engine类的加载流程。Engine是图片加载的管理中心,暴露了一系列方法供Request操作。根据之前已经分析过的内容,在DecodeJob里最终会拿到我们需要的Resource<Z>

最后

纵观下来文章已经挺长了,但是目前也只是把Glide中最主体的加载过程过了一遍,还有很多细节处理没提到。Glide中运用了大量的设计模式,使得整个框架扩展性很好,各模块解耦充分,职责粒度很细,同时还包含了很多对于图片类的处理方法,是很不错的学习资料呢╮( ̄▽ ̄)╭

参考文章

Glide核心设计一
Glide核心设计二
拆 Glide 系列之 - Bitmap 复用
Glide源码详解

声明:本站所有文章均为原创或翻译,遵循署名-非商业性使用-禁止演绎 4.0 国际许可协议,如需转载请确保您对该协议有足够了解,并附上作者名(Est)及原贴地址