Glide核心逻辑解析
分三个模块进行,分别是Registry(注册器), Engine(请求),Cache(缓存)。
Registry模块
Registry的职责是注册Glide的所有组件,是Glide扩展的基石,目前在Registry中注册的组件有ModelLoaderFactory,ResourceDecoder, DataRewinder, Encoder。
我们看下个组件职责:
ModelLoaderFactory与ModelLoader
用于构造ModelLoader,ModelLoader会指定两种类型。第一个类型是Model,即要加载的数据类型,第二个类型是请求到的数据类型,比如常见的Http请求,那么它的数据类型是InputStream,下面看下定义。
public interface ModelLoader<Model, Data> {
@Nullable
LoadData<Data> buildLoadData(@NonNull Model model, int width, int height,
@NonNull Options options);
boolean handles(@NonNull Model model);
}
我们看下一个常见的实现,处理Http请求,并获取InputStream。
public class HttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
// ...
}
这个是Glide发起Http请求的ModelLoader,类似的请求还有很多,可以看到,这个ModelLoader需要处理的Model是GlideUrl,而我们一般传入的数据是String,这个时候Glide会通过一个MultiModelLoader进行过滤,最终将Model由String转成GlideUrl,使用这个ModelLoader进行处理。
如果要是好替换Glide的网络请求实现,就是通过替换这个ModelLoader实现的,具体可以看下Glide的其他集成库(intergration中的okhttp)。
@GlideModule
public final class OkHttpLibraryGlideModule extends LibraryGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide,
@NonNull Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
ResourceDecoder
ResourceDecoder的作用是将ModelLoader加载出来的数据,进行解码,解码成Bitmap,或者BitmapDrawable之类的。接口定义如下:
public interface ResourceDecoder<T, Z> {
boolean handles(@NonNull T source, @NonNull Options options) throws IOException;
Resource<Z> decode(@NonNull T source, int width, int height, @NonNull Options options)
throws IOException;
}
Glide中常用的Decoder有两个,其他都是将这两个Decoder进行包装,它们分别是ByteBufferBitmapDecoder和StreamBitmapDecoder。
我们看下StreamBitmapDecoder的实现:
将InputStream转成Bitmap。
public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {
}
解码过程中也有一些细节问题需要注意:
- 如果要使用ARGB_565进行解码图片,最好使用GlideModule中进行配置默认的RequestOptions。但是就是配置了使用ARGB_565进行加载,Glide根据文件头判断这个图片是否含有alpha通道,如果该图片是png的,那么仍然会使用ARGB_8888进行解码,所以为了获取最小的内存占用,需要和后端尽量返回jpeg的图片。
- Android O以后的设备会默认使用HARDWRAE这个格式进行解码,这种格式
DataRewinder
Rewinder担任的是ModelLoader到ResourceDecoder的桥梁的角色,DecodeJob将ModelLoader获得的数据,构造出DataRewinder,然后使用Rewinder将数据传给ResourceDecoder进行解码。
下面看下DataRewinder接口定义。
public interface DataRewinder<T> {
}
Encoder
Encoder的作用是将数据转换成文件,用来配合Glide硬盘缓存。
public interface Encoder<T> {
boolean encode(@NonNull T data, @NonNull File file, @NonNull Options options);
}
最常用的实现是StreamEncoder,使用StreamEncoder将InputSteam转换成文件,完成文件缓存。
StreamEncoder中使用ArrayPool将字节缓冲区进行缓存,避免了每次都创建字节数组(缓冲区)。
public class StreamEncoder implements Encoder<InputStream> {
private static final String TAG = "StreamEncoder";
private final ArrayPool byteArrayPool;
public StreamEncoder(ArrayPool byteArrayPool) {
this.byteArrayPool = byteArrayPool;
}
@Override
public boolean encode(@NonNull InputStream data, @NonNull File file, @NonNull Options options) {
// 将图片编码成图片
// 使用InputStream进行编码图片,不用每次使用new byte初始化缓冲区
// 缓冲区默认大小64KB
final byte[] buffer = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
boolean success = false;
OutputStream os = null;
try {
os = new FileOutputStream(file);
int read;
while ((read = data.read(buffer)) != -1) {
os.write(buffer, 0, read);
}
os.close();
success = true;
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to encode data onto the OutputStream", e);
}
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
// Do nothing.
}
}
byteArrayPool.put(buffer);
}
return success;
}
}
RequetBuilder#into()
Glide的加载流程起源于RequestBuilder的into()方法,下面逐层对into()方法进行分析。
指定加载的Target,开始执行请求
首先清除这个Target上的原有请求(如果请求相同,并且可以使用内存缓存除外),然后使用RequestManager记录此请求引用并启动。
下面分析加载的起点into()。
Glide.with(context).load(url).into(imageview);
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
@NonNull RequestOptions options) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
options = options.autoClone();
// 根据之前的Option,创建Request
Request request = buildRequest(target, targetListener, options);
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启动请求,并进行记录
requestManager.track(target, request);
return target;
}
buildRequest的过程
buildRequest 中会创键多个请求,比如如果设置了请求错误的Request,或者缩略图Request会一起创键。
Glide.with(this).load(URL)
.apply(new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true))
// 如果图片错误,请求这个资源
.error(Glide.with(this).load(errorUrl))
.into(img2);
// 递归的创键请求对象
private Request buildRequestRecursive(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
RequestOptions requestOptions) {
// Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
// 创建当网络请求失败时的Request
ErrorRequestCoordinator errorRequestCoordinator = null;
if (errorBuilder != null) {
errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}
// 创建缩略图Request
Request mainRequest =
buildThumbnailRequestRecursive(
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions);
if (errorRequestCoordinator == null) {
return mainRequest;
}
int errorOverrideWidth = errorBuilder.requestOptions.getOverrideWidth();
int errorOverrideHeight = errorBuilder.requestOptions.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight)
&& !errorBuilder.requestOptions.isValidOverride()) {
errorOverrideWidth = requestOptions.getOverrideWidth();
errorOverrideHeight = requestOptions.getOverrideHeight();
}
// 错误Request和主Request组合起来
Request errorRequest = errorBuilder.buildRequestRecursive(
target,
targetListener,
errorRequestCoordinator,
errorBuilder.transitionOptions,
errorBuilder.requestOptions.getPriority(),
errorOverrideWidth,
errorOverrideHeight,
errorBuilder.requestOptions);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
}
此时如果当前的状态不是Pause的话,那么直接调用Request#begin()
/**
* Starts tracking the given request.
*/
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
Request的实现是SingleRequest类,我们看下SingleRequest的begin()如何执行
@Override
public 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;
}
// 如果Request已经运行,再次调用begin()会产生异常
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 {
// 使用Callback进行获取
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));
}
}
SizeDeterminer获取控件尺寸
这里的逻辑是如果Model不为null,那么就去将状态改成WATIINT_FOR_SIZE,然后使用ViewTarget的getSize()方法去获取尺寸。
这里获取尺寸的方法逻辑如下,如果该View的LayoutParams宽高都不是MATCH_PARENT或者WRAP_CONTENT(必须已经发生LayoutRequested),而是具体的数值,那么直接使用这个尺寸,否则,会使用ViewTreeObserver中的onPreDraw()进行监听尺寸。
void getSize(@NonNull SizeReadyCallback cb) {
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
return;
}
// We want to notify callbacks in the order they were added and we only expect one or two
// callbacks to be added a time, so a List is a reasonable choice.
if (!cbs.contains(cb)) {
cbs.add(cb);
}
// 添加onPredraw监听
if (layoutListener == null) {
ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
}
}
从onPreDraw中获取到尺寸后, 会开始Glide真正的图片加载流程。
Glide一系列加载流程起源于Engine类,Engine类是个单例,存在于GlideContext中,我们看下Engine的初始化。
// GlideBuild #build()
//....
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}
//....
这里有个技巧,如果一个ImageView的大小是WRAP_CONTENT,并且没有发生layoutRquested,即没有通过addView()之类的方法添加,那么会使用屏幕的宽或高(最大的)进行采样,而不是控件的大小。解决办法是强制使用waitForLayout()
Glide.with(this).load(URL).into(img1).waitForLayout();
或者使用override()指定宽高。
开始加载图片
/**
* A callback method that should never be invoked directly.
*/
@Override
public void onSizeReady(int width, int height) {
//...
// 获取到尺寸以后,将尺寸传给Engine,进行加载资源
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);
//...
}
Engine加载流程(核心)
Engine的核心是load方法,这个方法也是Glide的核心,下面我们重点分析下这个方法。
- 从当前的的弱引用中进行查找
- 从内存缓存中进行查找
- 从已经开始的网络请求中进行查找
- 创建一个新的网络请求
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,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
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 (Log.isLoggable(TAG, Log.VERBOSE)) {
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 (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
// 网络请求是否存在
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
// 创建一个网络请求对象
//http://www.lightskystreet.com/2015/10/12/glide_source_analysis/
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);
// 开始获取资源并解码
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
在方法前面会检测是否能够使用内存缓存,Glide的内存缓存分为两种,一种是ActiveResources
使用WeakReference存储,一种是MemoryCache
使用LRU策略进行存储。
如果能直接取出来缓存的话,是不需要进行解码的,直接回调onResourceReady(),如果缓存没命中,这时Glide会检测是否有正在进行的请求,如果有,直接添加个回调然后返回。
如果以上都没有,那么会直接启动新的加载流程,代码如下:
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
// 从磁盘缓存中取数据
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
EngineJob的start()方法,会开始一个新的解码流程。
会先在磁盘线程池加载磁盘缓存,如果没有,那么使用网络线程池加载。
DecodeJob的run()如下:
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;
}
}
// We've run out of stages and generators, give up.
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
// Otherwise a generator started a new load and we expect to be called back in
// onDataFetcherReady.
}
核心逻辑是getNextStage()
和getNextGenerator()
,这两个方法描述的是当前加载过程的状态转换。
getNextGenerator()方法会根据当前状态创建对应的Generator。
// 根据状态切换,生成DataGenerator
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);
}
}
我们看下这个Stage的获取,这个可以理解为Glide的核心了。
/**
* DecodeJob的状态切换,先从带尺寸缓存->缓存->网络->完成状态
*/
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:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
状态切换: 带尺寸缓存->缓存->网络->完成状态
每个状态均使用DiskCacheStragey进行判断,如果DiskCacheStragey不支持直接返回下一个状态。
先看下DataFetcherGenerator的定义。
interface DataFetcherGenerator {
// 启动DataFetcher进行加载图片,如果启动,返回true,如果没有启动,返回false
boolean startNext();
void cancel();
}
从上面生成DataFetcherGenerator的方法可以看出,DataFetcherGenerator的作用就是发起加载流程,结束之后使用回调返回结果,回调定义如下:
interface FetcherReadyCallback {
/**
* Requests that we call startNext() again on a Glide owned thread.
*/
void reschedule();
/**
* Notifies the callback that the load is complete.
*
* @param sourceKey The id of the loaded data.
* @param data The loaded data, or null if the load failed.
* @param fetcher The data fetcher we attempted to load from.
* @param dataSource The data source we were loading from.
* @param attemptedKey The key we were loading data from (may be an alternate).
*/
void onDataFetcherReady(Key sourceKey, @Nullable Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey);
/**
* Notifies the callback when the load fails.
*
* @param attemptedKey The key we were using to load (may be an alternate).
* @param e The exception that caused the load to fail.
* @param fetcher The fetcher we were loading from.
* @param dataSource The data source we were loading from.
*/
void onDataFetcherFailed(Key attemptedKey, Exception e, DataFetcher<?> fetcher,
DataSource dataSource);
}
下面看下SourceGenerator的实现,其他的实现大多类似,都是获取LoadData,然后使用LoadData的DataFetcher进行加载图片。
class SourceGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback<Object>,
DataFetcherGenerator.FetcherReadyCallback {
@Override
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
// reschedule调用过来的,有CacheData,先写入文件缓存,在从缓存中读出来,最后返回
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
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;
}
}
SourceGenerator的逻辑大概是这样,先通过Registgry获取DataFetcher,然后使用DataFetcher(这里是HttpUrlFetcher)加载,
如果加载成功会调用onDataReady(),我们看下SourceGenerator的 onDataReady调用。
@Override
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
// 如果该数据能够缓存,那么重新调度到Source线程池,进行缓存
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
rechedule()调用之后,会继续调用startNext(),会调用到cacheData()
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);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished encoding source to cache"
+ ", key: " + originalKey
+ ", data: " + dataToCache
+ ", encoder: " + encoder
+ ", duration: " + LogTime.getElapsedMillis(startTime));
}
} finally {
loadData.fetcher.cleanup();
}
// 重新生成一个Generator,这个DataCacheGenerator从文件中读出,然后调用onResourceReady()回调
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
// This data fetcher will be loading from a File and provide the wrong data source, so override
// with the data source of the original fetcher
cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);
}
回调到DecodeJob的onDataFetcherReady()
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
this.currentSourceKey = sourceKey;
this.currentData = data;
this.currentFetcher = fetcher;
this.currentDataSource = dataSource;
this.currentAttemptingKey = attemptedKey;
if (Thread.currentThread() != currentThread) {
// 收到数据以后,如果不是Source线程,继续reschedule,然后更新状态为DECODE_DATA
runReason = RunReason.DECODE_DATA;
callback.reschedule(this);
} else {
GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
try {
// 开始解码
decodeFromRetrievedData();
} finally {
GlideTrace.endSection();
}
}
}
看下这个decodeResourceWithList()
private void decodeFromRetrievedData() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Retrieved data", startFetchTime,
"data: " + currentData
+ ", cache key: " + currentSourceKey
+ ", fetcher: " + currentFetcher);
}
Resource<R> resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
中间会回调到DecodeJob中的onResourceDecoded(),我们看下这个方法的实现,这个方法的作用是调用RequestOptions中的Transform处理图片,然后将ResourceCache的Key和Encode准备好(放在deferEncoderManager中),稍后进行写入缓存。
// 资源解码后的回调
@Synthetic
@NonNull
<Z> Resource<Z> onResourceDecoded(DataSource dataSource,
@NonNull Resource<Z> decoded) {
@SuppressWarnings("unchecked")
Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
Transformation<Z> appliedTransformation = null;
Resource<Z> transformed = decoded;
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
// 如果不是使用的本地Transformation缓存,那么将资源Transform
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
transformed = appliedTransformation.transform(glideContext, decoded, width, height);
}
// TODO: Make this the responsibility of the Transformation.
// 如果资源被重新创建,那么回收之前的
if (!decoded.equals(transformed)) {
decoded.recycle();
}
// 是否进行Encode?
final EncodeStrategy encodeStrategy;
final ResourceEncoder<Z> encoder;
// 之前在SourceGenerator中已经生成过DATA_CACHE了,这里会判断是否需要生成RESOURCE_CACHE(带尺寸的缓存)
if (decodeHelper.isResourceEncoderAvailable(transformed)) {
encoder = decodeHelper.getResultEncoder(transformed);
encodeStrategy = encoder.getEncodeStrategy(options);
} else {
encoder = null;
encodeStrategy = EncodeStrategy.NONE;
}
Resource<Z> result = transformed;
boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
// 是否使用Resource缓存
if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
encodeStrategy)) {
if (encoder == null) {
throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
}
final Key key;
// 根据Encode策略,创建不同的缓存Key
switch (encodeStrategy) {
case SOURCE:
key = new DataCacheKey(currentSourceKey, signature);
break;
case TRANSFORMED:
key =
new ResourceCacheKey(
decodeHelper.getArrayPool(),
currentSourceKey,
signature,
width,
height,
appliedTransformation,
resourceSubClass,
options);
break;
default:
throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
}
LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
deferredEncodeManager.init(key, encoder, lockedResult);
result = lockedResult;
}
return result;
}
接下来的逻辑是从Registry中寻找能够解码的Decoder,然后从Rewinder中获取数据,逐个尝试使用Decoder进行解码。
这里的Decoder实际上是StreamDecoder,实际解码类是Downsample。
一会在分析decoder实现,现在看下整体流程。
@NonNull
private Resource<ResourceType> decodeResourceWithList(DataRewinder<DataType> rewinder, int width,
int height, @NonNull Options options, List<Throwable> exceptions) throws GlideException {
Resource<ResourceType> result = null;
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = decoders.size(); i < size; i++) {
ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
try {
DataType data = rewinder.rewindAndGet();
if (decoder.handles(data, options)) {
data = rewinder.rewindAndGet();
result = decoder.decode(data, width, height, options);
}
// Some decoders throw unexpectedly. If they do, we shouldn't fail the entire load path, but
// instead log and continue. See #2406 for an example.
} catch (IOException | RuntimeException | OutOfMemoryError e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Failed to decode data for " + decoder, e);
}
exceptions.add(e);
}
if (result != null) {
break;
}
}
if (result == null) {
throw new GlideException(failureMessage, new ArrayList<>(exceptions));
}
return result;
}
解码完成后,调用notifyEncodeAndRelease()通知主线程处理图片,如果还需要处理encode的话,进行处理。
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
if (resource instanceof Initializable) {
((Initializable) resource).initialize();
}
Resource<R> result = resource;
LockedResource<R> lockedResource = null;
if (deferredEncodeManager.hasResourceToEncode()) {
lockedResource = LockedResource.obtain(resource);
result = lockedResource;
}
// 通知主线程回调,加载图片
notifyComplete(result, dataSource);
// 更新状态为编码
stage = Stage.ENCODE;
// 处理DeferredEncode容器中需要Encode的资源
try {
if (deferredEncodeManager.hasResourceToEncode()) {
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
if (lockedResource != null) {
lockedResource.unlock();
}
}
// Call onEncodeComplete outside the finally block so that it's not called if the encode process
// throws.
onEncodeComplete();
}
主线程收到回调,会回调下面的方法。
// 主线程执行回调
@Synthetic
void handleResultOnMainThread() {
stateVerifier.throwIfRecycled();
if (isCancelled) {
resource.recycle();
release(false /*isRemovedFromQueue*/);
return;
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received a resource without any callbacks to notify");
} else if (hasResource) {
throw new IllegalStateException("Already have resource");
}
// 将Resource进行包装,方便对使用此资源进行计数s
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
// Hold on to resource for duration of request so we don't recycle it in the middle of
// notifying if it synchronously released by one of the callbacks.
engineResource.acquire();
listener.onEngineJobComplete(this, key, engineResource);
// 逐个回调各Callback
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = cbs.size(); i < size; i++) {
ResourceCallback cb = cbs.get(i);
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource, dataSource);
}
}
// Our request is complete, so we can release the resource.
engineResource.release();
// 清理资源,并且放入Cache中
release(false /*isRemovedFromQueue*/);
}
cb.onResourceReady()完成后,Glide加载图片的工作就大部分完成了。剩下的就是通知Traget的回调了。
Glide 缓存
Glide缓存分成三个部分,分别是ArrayPool,BitmapPool,LruResourceCache。
LruResourceCache
Glide 中最直接的内存缓存,我们看下Key的实现。
class EngineKey implements Key {
private final Object model;
private final int width;
private final int height;
private final Class<?> resourceClass;
private final Class<?> transcodeClass;
private final Key signature;
private final Map<Class<?>, Transformation<?>> transformations;
private final Options options;
private int hashCode;
//......
}
这是Key的所有字段,可以看出,出了与Model有关,还有Transform之类的因素,这也是Glide的高效之处,直接可以将处理过的图片进行缓存。
LruResourceCache在Engine#load()中进行获取,即如果图片存在LruResourceCache中,会直接返回,不会创建DecodeJob进行加载。
LruResourceCache是Glide自实现的Lru容器,存放的数据是资源的包装类Resource<?>。
/**
* An LRU in memory cache for {@link com.bumptech.glide.load.engine.Resource}s.
*/
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
private ResourceRemovedListener listener;
/**
* Constructor for LruResourceCache.
*
* @param size The maximum size in bytes the in memory cache can use.
*/
public LruResourceCache(long size) {
super(size);
}
@Override
public void setResourceRemovedListener(@NonNull ResourceRemovedListener listener) {
this.listener = listener;
}
@Override
protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
if (listener != null && item != null) {
listener.onResourceRemoved(item);
}
}
@Override
protected int getSize(@Nullable Resource<?> item) {
if (item == null) {
return super.getSize(null);
} else {
return item.getSize();
}
}
@SuppressLint("InlinedApi")
@Override
public void trimMemory(int level) {
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Entering list of cached background apps
// Evict our entire bitmap cache
clearMemory();
} else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
|| level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
// The app's UI is no longer visible, or app is in the foreground but system is running
// critically low on memory
// Evict oldest half of our bitmap cache
trimToSize(getMaxSize() / 2);
}
}
}
BitmapPool
BitmapPool是Glide内存缓存的重要成员。这个BitmapPool的作用是提供一个空的Bitmap供Glide解码使用(Config.inBitmap属性)。
同时,也可以作为提供Bitmap的容器,如Transformation中的使用。
Bitmap的获取通过一个LruPoolStrage接口实现,用于区分Android版本实现。
interface LruPoolStrategy {
void put(Bitmap bitmap);
@Nullable
Bitmap get(int width, int height, Bitmap.Config config);
@Nullable
Bitmap removeLast();
String logBitmap(Bitmap bitmap);
String logBitmap(int width, int height, Bitmap.Config config);
int getSize(Bitmap bitmap);
}
Strategy的创建策略如下
// 创建Lru缓存策略
private static LruPoolStrategy getDefaultStrategy() {
final LruPoolStrategy strategy;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
strategy = new SizeConfigStrategy();
} else {
strategy = new AttributeStrategy();
}
return strategy;
}
如果api大于19,那么使用SizeConfigStrategy,这些版本只要满足被回收的bitmap内存占用大于要解码的bitmap即可。
SizeConfigStrategy查找合适的Bitmap逻辑如下
// 查找较大尺寸的Bitmap通过TreeMap进行实现的
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;
}
如果api小于等于19,那么只有在bitmap尺寸(width,height)和Config都相同的时候才能够被复用,所以该BitmapPool的实现较为简单,只是一个KeyPool和GroupLinkedMap。
@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
final Key key = keyPool.get(width, height, config);
return groupedMap.get(key);
}
BitmapPool再Downsample中有频繁的使用,Downsample类是Decoder的核心。
解码前专门设置inBitmap属性。
private static void setInBitmap(
BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
@Nullable Bitmap.Config expectedConfig = null;
// Avoid short circuiting, it appears to break on some devices.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (options.inPreferredConfig == Config.HARDWARE) {
return;
}
// On API 26 outConfig may be null for some images even if the image is valid, can be decoded
// and outWidth/outHeight/outColorSpace are populated (see b/71513049).
expectedConfig = options.outConfig;
}
if (expectedConfig == null) {
// We're going to guess that BitmapFactory will return us the config we're requesting. This
// isn't always the case, even though our guesses tend to be conservative and prefer configs
// of larger sizes so that the Bitmap will fit our image anyway. If we're wrong here and the
// config we choose is too small, our initial decode will fail, but we will retry with no
// inBitmap which will succeed so if we're wrong here, we're less efficient but still correct.
expectedConfig = options.inPreferredConfig;
}
// BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
}
ArrayPool
ArrayPool也是Glide的内存缓存,它的实现类是LruArrayPool,它的作用是在Glide解码图片和解析文件时提供缓冲区(byte[])的复用逻辑,这个Volley的代码里也有,意思就是我们不需要在每次读取文件的时候手动new byte[]了。
java中默认的缓冲区是4kb,如果频繁的申请这4kb内存,势必会造成内存抖动,从而影响流畅性。
我们先看下ArrayPool的使用,在Downsample类中。
public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
Options options, DecodeCallbacks callbacks) throws IOException {
Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
+ " mark()");
byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
// 解码所用缓冲区
bitmapFactoryOptions.inTempStorage = bytesForOptions;
// 获取DecodeFormat
DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
// 解码策略
DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
// 是否使用Bitmap硬件缓存
boolean isHardwareConfigAllowed =
options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);
try {
// 将InputStream解码成Bitmap
Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
requestedHeight, fixBitmapToRequestedDimensions, callbacks);
return BitmapResource.obtain(result, bitmapPool);
} finally {
// Option对象进行复用
releaseOptions(bitmapFactoryOptions);
byteArrayPool.put(bytesForOptions);
}
}
其中byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
就是从缓存中获取缓冲区。
ArrayPool的实现与BitmapPool类似,只不过变成了获取byte[]或者int[],这里不多分析了。
总结
通过阅读Glide源码,收获还是很多的,下面记录下Glide的一些使用技巧,帮助我们更好地使用Glide。
-
Glide推荐使用ARGB_565进行解码,需要在DefaultRequestOptions中进行配置,但这不是绝对的,如果你的图片是png或者其他含有alpha的图片,那么仍然会使用ARGB_8888进行解码,所以如果想省点内存的话,最好限制下图片格式。
-
Android O以后,会优先使用HARDWARE格式进行解码,但这些图不能对像素进行操作,需要小心。
-
加载图片的大小最好指明尺寸,如果使用WRAP_CONTENT的ImageView加载图片,那么有可能图片会按照屏幕的最大尺寸进行采样,从而造成内存浪费。解决办法就是使用具体尺寸或者MATCH_PARENT,或者使用override()指明图片大小。
-
如果项目代码中要初始化Bitmap,不妨试试从BitmapPool中进行获取
Glide.get(context).getBitmapPool().get(...)
,如果有缓存的Bitmap,那么就不用创建新的了。 -
使用Glide加载的图片,如果没有设置skipMemory(),那么不要调用Bitmap的recycler()(该图片已在内存缓存中),否则其他模块使用会造成Crash。
参考
https://damondaliy.com/?p=51
https://juejin.im/entry/5aa11f826fb9a028d663bfe0
http://www.voidcn.com/article/p-thodukjp-brq.html