您的位置:首页 > 理论基础 > 计算机网络

Glide源码分析4 -- 缓存,编解码和网络请求

2016-08-07 16:14 549 查看

1. 概述和核心类

Glide源码分析 – request创建与发送过程一文中,我们谈到request最终通过GenericRequest的onSizeReady()方法进行,其中调用了engine.load()方法去实际获取数据。本文主要讲述engine.load()之后发生的那些事,让大家能够对底层数据获取有个更清晰的认识。从这点也可以看出Glide设计分层的精妙。主要涉及的核心类如下

1)GenericRequest:定义了很多对request的处理方法,我们比较关心的是request的发送,它的入口是begin(),会调用到onSizeReady(),最终调用到engine.load(),也就是数据获取部分的入口

2)Engine:封装了数据获取的很多关键方法,向request层提供这些API,比如load(), release(), clearDiskCache()等方法。可以认为是一个外观模式。

3)MemoryCache:内存缓存类,先从缓存中获取数据,如果没有才做后面的工作。这是第一级缓存。Glide采用了两级缓存模式。第二级缓存为DiskLruCache,为磁盘缓存。获取磁盘缓存比较耗时,需要在子线程中进行,故而在DecodeJob中得到调用。此处不会调用磁盘缓存。

4)EngineJob, EngineRunnable:EngineJob是一个控制类,作为EngineRunnable的管理者,提供start(), cancel()等很多操作runnable的方法。一般会以线程池的方式向子线程提交EngineRunnable任务。而EngineRunnable就是我们在子线程中需要执行的任务,也是特别关键的一个类。

5)DecodeJob,DataFetcher,ResourceDecoder,Transformation:DecodeJob流程为从缓存或网络或本地获取数据,然后转码为所需的格式,最后编码并保存到DiskLruCache中。这是数据获取阶段很关键的一个类。DataFetcher负责根据不同途径数据获取(如本地File,url,URI等),ResourceDecoder负责根据不同文件格式解码(如Bitmap,GIF等)Transformation负责编码为不同格式文件(如Bitmap,GIF等)。后面在类层次关系中会详细讲解这几个类的关系以及它们的子类。现在只需要知道这几个类的作用就可以了。

上面讲解了五个方面,大概十多个类。我们可以把整个过程分为两个阶段:任务提交阶段和任务执行阶段。为了更清晰的理清逻辑关系,可以看下面这张图。

任务提交阶段:



任务执行阶段



2. 任务提交阶段源码分析

1)GenericRequest的begin()方法是整个提交阶段的入口,它会调用engine来完成任务的提交,并回调一些listener。这些listener是我们经常使用的,从下面的源码中我们可以清晰的看见这些listener的调用时机。

public void begin() {
startTime = LogTime.getLogTime();
if (model == null) {
// loadModel为空时,会回调requestListener的onException()
onException(null);
return;
}

status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
// size验证通过后,会提交请求
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}

if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
// onLoadStarted回调时机,任务提交最开始的时候
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}

public void onSizeReady(int width, int height) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;

// 计算尺寸
width = Math.round(sizeMultiplier * width);
height = Math.round(sizeMultiplier * height);

ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
// 获取DataFetcher,我们可以自定义DataFetcher
final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);

if (dataFetcher == null) {
// 会回调requestListener的onException()
onException(new Exception("Failed to load model: \'" + model + "\'"));
return;
}
ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
// 默认使用内存缓存
loadedFromMemoryCache = true;
// 进入Engine的入口,十分关键
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this);
loadedFromMemoryCache = resource != null;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}


2)engine.load(), 先尝试从内存缓存获取数据,再尝试从当前活跃Resources中获取数据,再看看这个任务是否当前已经提交过了。这些都没有的话,最后提交任务。它规范了整个任务提交的流程,可以看做是一个模板方法。

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();

final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());

// 先尝试从内存缓存获取数据
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
// 获取数据成功,会回调target的onResourceReady()
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}

// 在尝试从活动Resources map中获取,它表示的是当前正在使用的Resources
// 它也是在内存中,与内存缓存不同之处是clear缓存时不会clear它。
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}

// 然后看看当前jobs中是否包含这个任务了,如果包含说明任务之前已经提交了,正在执行
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 engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
// runnable是关键,它是任务执行阶段的入口
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
// 开始提交job
engineJob.start(runnable);

if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}


3)engineJob.start(runnable),提交任务到线程池

class EngineJob implements EngineRunnable.EngineRunnableManager {
public void start(EngineRunnable engineRunnable) {
this.engineRunnable = engineRunnable;
// 提交任务,diskCacheService默认是一个AbstractExecutorService
future = diskCacheService.submit(engineRunnable);
}
}

public abstract class AbstractExecutorService implements ExecutorService {
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 线程池中执行子线程的任务,子线程得到调用后任务就可以执行了
execute(ftask);
return ftask;
}
}


3. 任务执行阶段源码分析

1)EngineRunnable的run(),它是任务执行的入口

class EngineRunnable implements Runnable, Prioritized {
public void run() {
if (isCancelled) {
return;
}

Exception exception = null;
Resource<?> resource = null;
try {
// 使用DecodeJob来完成数据的获取,编解码等
resource = decode();
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Exception decoding", e);
}
exception = e;
}

if (isCancelled) {
if (resource != null) {
// 取消则回收各种资源防止内存泄露,此处的子类一般采用对象池方式来回收,防止反复的创建和回收。如BitmapPool
resource.recycle();
}
return;
}

if (resource == null) {
// 任务执行最终失败则回调onLoadFailed,可以从此处分析出target callback的最终回调时机
onLoadFailed(exception);
} else {
// 任务执行最终成功则回调onLoadComplete
onLoadComplete(resource);
}
}

private Resource<?> decode() throws Exception {
if (isDecodingFromCache()) {
// 从DiskLruCache中获取数据并解码,比较简单,读者可自行分析
return decodeFromCache();
} else {
// 从其他途径获取数据并解码,如网络,本地File,数据流等
return decodeFromSource();
}
}

private Resource<?> decodeFromSource() throws Exception {
// 调用decodeJob来完成数据获取和编解码
return decodeJob.decodeFromSource();
}
}


2)decodeJob.decodeFromSource()

class DecodeJob<A, T, Z> {
public Resource<Z> decodeFromSource() throws Exception {
// 获取数据,解码
Resource<T> decoded = decodeSource();
// 编码并保存到DiskLruCache中
return transformEncodeAndTranscode(decoded);
}

private Resource<T> decodeSource() throws Exception {
Resource<T> decoded = null;
try {
long startTime = LogTime.getLogTime();
// 利用不同的DataFetcher来获取数据,如网络,本地File,流文件等
final A data = fetcher.loadData(priority);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Fetched data", startTime);
}
if (isCancelled) {
return null;
}
// 解码为所需的格式
decoded = decodeFromSourceData(data);
} finally {
fetcher.cleanup();
}
return decoded;
}

private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
long startTime = LogTime.getLogTime();
// 根据ImageView的scaleType等参数计算真正被ImageView使用的图片宽高,并保存真正宽高的图片。
// 比如centerCrop并且图片超出被ImageView裁剪时,我们没必要保存原图的宽高,而应该是裁剪之后的宽高,这样节省存储空间。
// 这也是Glide相对于Picasso的一个很大的优势
Resource<T> transformed = transform(decoded);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transformed resource from source", startTime);
}

// 写入到DiskLruCache中,下次就可以直接从它里面拿了
writeTransformedToCache(transformed);

startTime = LogTime.getLogTime();
// 转码,将源图片转码为ImageView所需的图片格式
Resource<Z> result = transcode(transformed);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transcoded transformed from source", startTime);
}
return result;
}
}


3)DataFetcher的loadData(priority), DataFetcher的子类很多,参见概述和核心类部分。此处我们分析下从url获取的情形,这是我们碰到最多的情形。从url获取的DataFetcher是HttpUrlFetcher,它采用了android原生的HttpURLConnection网络库,如下

public class HttpUrlFetcher implements DataFetcher<InputStream> {

// 采用HttpURLConnection作为网络库,
// 我们可以自定义DataFetcher,从而使用其他网络库,如OkHttp
private HttpURLConnection urlConnection;

public InputStream loadData(Priority priority) throws Exception {
return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
}

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
throws IOException {
if (redirects >= MAXIMUM_REDIRECTS) {
throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html. try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new IOException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
// 静态工厂模式创建HttpURLConnection对象
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
// Do our best to avoid gzip since it's both inefficient for images and also makes it more
// difficult for us to detect and prevent partial content rendering. See #440.
if (TextUtils.isEmpty(urlConnection.getRequestProperty(ENCODING_HEADER))) {
urlConnection.setRequestProperty(ENCODING_HEADER, DEFAULT_ENCODING);
}
// 设置HttpURLConnection的参数
urlConnection.setConnectTimeout(2500);
urlConnection.setReadTimeout(2500);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);

// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (statusCode / 100 == 2) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (statusCode / 100 == 3) {
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new IOException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else {
if (statusCode == -1) {
throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
}
throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
}
}
}


4)ResourceDecoder的decode(),ResourceDecoder的子类也很多,同样参见概述和核心类部分。我们分析下FileDescriptorBitmapDecoder,它是decode bitmap的关键所在

public class FileDescriptorBitmapDecoder implements ResourceDecoder<ParcelFileDescriptor, Bitmap> {
public Resource<Bitmap> decode(ParcelFileDescriptor source, int width, int height) throws IOException {
Bitmap bitmap = bitmapDecoder.decode(source, bitmapPool, width, height, decodeFormat);
// 对象池管理方式,从bitmapPool中获取一个BitmapResource对象
return BitmapResource.obtain(bitmap, bitmapPool);
}

public Bitmap decode(ParcelFileDescriptor resource, BitmapPool bitmapPool, int outWidth, int outHeight,
DecodeFormat decodeFormat)
throws IOException {
// 通过MediaMetadataRetriever来解码
MediaMetadataRetriever mediaMetadataRetriever = factory.build();
mediaMetadataRetriever.setDataSource(resource.getFileDescriptor());
Bitmap result;
if (frame >= 0) {
result = mediaMetadataRetriever.getFrameAtTime(frame);
} else {
result = mediaMetadataRetriever.getFrameAtTime();
}
mediaMetadataRetriever.release();
resource.close();
return result;
}
}


5)Transformation的transform(), 根据ImageView的实际宽高来裁剪图片数据。这样可以减小保存到DiskLruCache中的数据大小。它的子类也很多,我们分析下BitmapTransformation。

public abstract class BitmapTransformation implements Transformation<Bitmap> {
public final Resource<Bitmap> transform(Resource<Bitmap> resource, int outWidth, int outHeight) {
if (!Util.isValidDimensions(outWidth, outHeight)) {
throw new IllegalArgumentException("Cannot apply transformation on width: " + outWidth + " or height: "
+ outHeight + " less than or equal to zero and not Target.SIZE_ORIGINAL");
}
Bitmap toTransform = resource.get();
int targetWidth = outWidth == Target.SIZE_ORIGINAL ? toTransform.getWidth() : outWidth;
int targetHeight = outHeight == Target.SIZE_ORIGINAL ? toTransform.getHeight() : outHeight;
// 裁剪,根据CenterCrop和fitCenter有两种裁剪方式
Bitmap transformed = transform(bitmapPool, toTransform, targetWidth, targetHeight);

final Resource<Bitmap> result;
if (toTransform.equals(transformed)) {
result = resource;
} else {
// 从bitmapPool对象池中获取BitmapResource
result = BitmapResource.obtain(transformed, bitmapPool);
}

return result;
}
}

public class CenterCrop extends BitmapTransformation {
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
final Bitmap toReuse = pool.get(outWidth, outHeight, toTransform.getConfig() != null
? toTransform.getConfig() : Bitmap.Config.ARGB_8888);
// 按centerCrop方式裁剪
Bitmap transformed = TransformationUtils.centerCrop(toReuse, toTransform, outWidth, outHeight);
if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) {
toReuse.recycle();
}
return transformed;
}
}


6)ResourceTranscoder的transcode(), 编码。有兴趣的读者可以自行分析

4 总结

Glide的Engine部分分为数据获取任务提交阶段和任务执行阶段。提交阶段先从内存缓存和当前活动Resources中获取,然后再在线程池中新开子线程,之后就只需要等待子线程得到执行了。任务执行阶段先尝试从DiskLruCache中获取Resources,然后利用DataFetcher获取数据,利用ResourceDecoder解码,利用Transformation裁剪图片数据,利用ResourceTranscoder转码为ImageView所需格式,这样就获取到了最终所需的Resources。

这一篇也是Glide源码解析的最终篇,感谢大家能看完我写的这些。不正确的地方,还希望指出来。谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息