背景
目前得物的图片库主要是基于Fresco
的自研图片加载库,但是Glide
源码的精妙之处我们也有很多值得学习的地方。今天跟一下源码,和小伙伴们分享下我的理解。
基操
Glide.with(context).load("http://cdnxxx.png").into(imgThumb)
什么导包啥的我们就不赘述了,直接干,拿到ImageView去展示服务端图片。大概步骤如下
下面我们追踪一下这3个函数:
with()
跟到一个有代表性的重载方法里面看:
public RequestManager get(@NonNull View view) {
//如果是在子线程,则生命图片加载的生命周期为application
if (Util.isOnBackgroundThread()) {
return get(view.getContext().getApplicationContext());
}
//.......省略代码.......
Activity activity = findActivity(view.getContext());
// The view might be somewhere else, like a service.
//这个代码也很好理解,不赘述
if (activity == null) {
return get(view.getContext().getApplicationContext());
//下面两个是对称的,兼容support库(androidx)中的fragment和.app下面的fragment
if (activity instanceof FragmentActivity) {
Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);
return fragment != null ? get(fragment) : get(activity);
}
android.app.Fragment fragment = findFragment(view, activity);
if (fragment == null) {
return get(activity);
}
return get(fragment);
}
private RequestManagerFragment getRequestManagerFragment(
@NonNull final android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
//创建一个空白fragment来监听生命周期,和jetpack里面的lifeCycle类似
//这里考虑到fm的commit是一个异步操作,所以有多层check
//先从map里面去取,取不到再创建对象,然后塞到map里面,防止此时再Glide.with()进来
//造成创建多个Fragment来监听生命周期的bug
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
if (isParentVisible) {
current.getGlideLifecycle().onStart();
}
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}
空白fragment
通过lifecyle
感知后通知给到所有实现了该接口的类
load()
又是一堆重载,但是可以看到很明显有我们常用的load("http://xxxcdn/.png")
跟下来只是在RequestBuilder
类里面就结束了。意犹未尽,其实下面的into
才是重点
into()
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
//........省略成吨代码...........
//这里把配置的requestOption、imageViewTarget、线程池往下传
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
}
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()");
}
//这里构建了一个req,即代表有一个图片需要显示
Request request = buildRequest(target, targetListener, options, callbackExecutor);
//......省略.......
requestManager.clear(target);
target.setRequest(request);
//跟主线流程>>>>>>>>
requestManager.track(target, request);
return target;
}
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
public void runRequest(@NonNull Request request) {
//把当前请求加入到set集合,如果
requests.add(request);
//这里的isPaused稍微跟一下代码,就可以发现是activity/fragment的onStop被触发
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}
public synchronized void begin() {
//.....省略.....
//状态是已解码完成状态,则直接回调到资源准备好了,可以显示
if (status == Status.COMPLETE) {
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
}
public synchronized void onSizeReady(int width, int height) {
//.......省略...........
loadStatus =
engine.load(
glideContext,
//...省略...
callbackExecutor);
//.......省略.........
}
public synchronized <R> LoadStatus load(
GlideContext glideContext,
//...省略....
Executor callbackExecutor) {
//这里可以看到Glide的Key是怎么生成的!明显可以看到和数据、签名、宽高、配置都有关系
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//这里第一步就是从活动内存里面看看是否已经缓存了
//final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
//有缓存了,立马回调回去说数据准备好了!
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}
//如果活动缓存里面没有,那么从内存缓存里面查找,这里可以看到,会从内存中找到后剪切到活动内存!!
//private EngineResource<?> getEngineResourceFromCache(Key key) {
//Resource<?> cached = cache.remove(key);
//}
//activeResources.activate(key, cached);
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
return new LoadStatus(cb, current);
}
//这里多态用的炉火纯青!
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
//一顿追踪,终于看到通过url获取到InputStream了!!!
try {
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
//拿到inputStream后立马回调出去
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);
final int statusCode = urlConnection.getResponseCode();
if (isHttpOk(statusCode)) {
return getStreamForSuccessfulRequest(urlConnection);
}
}
终于看到网络接口的请求了,这里用的是HttpUrlConnection
,累惨了!绕了几十个弯
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
try {
//拿到inputStream之后回调到这里,开始解码图片
decodeFromRetrievedData();
}
}
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);
}
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
最终。。。。走到了imageview.setImageDrawable()。中间我忽略了很多显而易见的代码,完整的into逻辑方法时序图可以参考下面
时序图
思考
通过上面代码跟一跟,可以了解我们开发过程中可能遇到的问题
- 加载图片内存泄漏的问题
- 图片资源被回收造成的崩溃问题
参考
《享学课堂》Derry老师的Glide讲解
https://muyangmin.github.io/glide-docs-cn/
https://www.jianshu.com/p/9bb50924d42a