接上文重學Android——Glide4.x源碼分析(1)html
接上一文,昨天講到圖片加載,最終調用到了onSizeReady的方法,調用了其中的engine.load方法java
@Override
public synchronized void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
...
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,//對應rul,圖片地址
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),//默認是Object.class
transcodeClass,//默認是Drawable.class
priority,
requestOptions.getDiskCacheStrategy(),//緩存策略,默認是AUTOMATIC
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);
}
複製代碼
咱們再直接跟進Engin.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;
//1建立資源索引key,內存緩存的惟一鍵值
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
//2首先從內存中找當前正在顯示的資源緩存
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;
}
//3沒有找到,就從內存緩存資源中加載圖片
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;
}
//4仍是沒找到,若是這個任務已經在隊列中,獲取已經存在的加載任務
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);
}
//5尚未找到,若是是新建加載任務,建立EngineJob和DecodeJob,而後開始任務
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
//6新建解碼任務,真正執行數據加載和解碼的類
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
//7緩存加載任務
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);
}
複製代碼
這個方法代碼有點長,倒是Glide緩存機制的核心,先說一下幾個名詞:bash
活動資源Active Resources 正在顯示的資源服務器
內存緩存Memory cache 顯示過的資源網絡
資源類型Resources 被解碼、轉換後的資源app
數據來源Data 源文件(未處理過的資源)框架
能夠看到,這其實就是磁盤+內存+網絡三級緩存!ide
再來分析一下上面load方法的執行流程:oop
因此咱們能夠直接到engineJob.start()方法
//start方法就是根據diskCacheStrategy策略獲取一個executor來執行DecodeJob
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
//這裏根據緩存策略,決定使用哪個Executor,默認狀況返回DiskCacheExecutor
//共有三種執行器,diskcacheExecutor,sourceExecutor,sourceUnlimitedExecutor
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
複製代碼
能夠看到,在start方法直接啓用了線程池的execute方法,能夠知道,DecodeJob類必定是個runnable,咱們去看它的run方法。
//DecodeJob.java
@Override
public void run() {
...
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
//重點在這
runWrapped();
} catch (CallbackException e) {
...
}
}
複製代碼
若是一切正常,那麼會進入runWrapped方法
private void runWrapped() {
switch (runReason) {
//默認狀態爲INITISLIZE
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
複製代碼
默認的狀態是INITIALIZE,那咱們來看它調用的幾個方法
//獲取任務執行階段:初始化,讀取轉換後的緩存,讀取原文件(未處理)緩存,遠程圖片加載,結束狀態
private Stage getNextStage(Stage current) {
switch (current) {
//初始狀態
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
//狀態2,讀取轉換後的緩存
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
//狀態3,讀取原文件緩存
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);
}
}
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);
}
}
//這裏開始加載執行
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
//這裏Generator.startNext方法中就是加載過程,若是成功就返回true並跳出循環,不然切換Generator繼續執行
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
//若是任務執行到去遠程加載,且切換任務執行環境
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
}
@Override
public void reschedule() {
//更改執行目標爲:SOURCE服務。固然也只有在stage == Stage.SOURCE的狀況下會被調用。
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);//這裏callback正是EngineJob。
}
//代碼跟進EngineJob類中,能夠看到實現方法。
@Override
public void reschedule(DecodeJob<?> job) {
// 能夠看到,這裏獲取的SourceExecutor來執行decodeJob。
//也就巧妙地將此decodeJob任務從cacheExecutor切換到了SourceExecutor,這樣分工協做更加高效。
getActiveSourceExecutor().execute(job);
}
複製代碼
能夠看到,這裏幾個方法構成了Glide的解碼流程:嘗試從轉換過的本地資源加載圖片;嘗試從沒有處理過的原始資源中加載圖片;嘗試遠程加載圖片。經過狀態的切換來尋找下一個加載器,直到加載到這張圖,返回成功,若是找不到,返回失敗。
再捋一捋:
從上面咱們能夠知道,SourceGenerator能夠加載網絡與本地圖片,那麼咱們直接看SourceGenerator調用startNext方法
/** * SourceGenerator * DataFetcher的簡介:Fetcher的意思是抓取,因此該類能夠稱爲數據抓取器 * 做用就是根據不一樣的數據來源(本地,網絡,Asset等) * 以及讀取方式(Stream,ByteBuffer等)來提取並解碼數據資源,實現類以下 * AssetPathFetcher:加載Asset數據 * HttpUrlFetcher:加載網絡數據 * LocalUriFetcher:加載本地數據 * 其餘實現類... * */
@Override
public boolean startNext() {
//忽略緩存部分邏輯
...
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
//是否有更多的ModelLoader
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.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
複製代碼
繼續跟進代碼,private volatile ModelLoader.LoadData<?> loadData,是一個接口,就須要咱們來找出它的實現類了
回到Glide的初始化中
Glide(...) {
...
registry
...
.append(String.class, InputStream.class, new StringLoader.StreamFactory())
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
....
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
...
.register(GifDrawable.class, byte[].class, gifDrawableBytesTranscoder);
ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
glideContext =
new GlideContext(
context,
arrayPool,
registry,
imageViewTargetFactory,
defaultRequestOptions,
defaultTransitionOptions,
defaultRequestListeners,
engine,
isLoggingRequestOriginsEnabled,
logLevel);
}
複製代碼
這構造方法是很長的,咱們只看咱們關心的網絡相關的部分,能夠看到,咱們將String.class Uri.class GlideUri.class三種類型注入了不一樣的Factory,這個Factory就是用來建立ModelLoader的,ModelLoader就是用來加載圖片的。
public class Registry {
//各類功能類註冊器。加載、轉換、解碼、加密等。
private final ModelLoaderRegistry modelLoaderRegistry;
private final EncoderRegistry encoderRegistry;
private final ResourceDecoderRegistry decoderRegistry;
private final ResourceEncoderRegistry resourceEncoderRegistry;
private final DataRewinderRegistry dataRewinderRegistry;
private final TranscoderRegistry transcoderRegistry;
private final ImageHeaderParserRegistry imageHeaderParserRegistry;
...
//modelLoader註冊
public <Model, Data> Registry append(Class<Model> modelClass, Class<Data> dataClass, ModelLoaderFactory<Model, Data> factory) {
modelLoaderRegistry.append(modelClass, dataClass, factory);
return this;
}
...
}
//繼續跟進代碼,ModelLoaderRegistry類中
public synchronized <Model, Data> void append(Class<Model> modelClass, Class<Data> dataClass, ModelLoaderFactory<Model, Data> factory) {
multiModelLoaderFactory.append(modelClass, dataClass, factory);
cache.clear();
}
//最後進入MultiModelLoaderFactory類中的add方法
private <Model, Data> void add(Class<Model> modelClass, Class<Data> dataClass, ModelLoaderFactory<Model, Data> factory, boolean append) {
Entry<Model, Data> entry = new Entry<>(modelClass, dataClass, factory);
//entries是一個list。因此,到這裏就知道註冊的LoaderFactory被緩存到了列表中,以便後面取用。
entries.add(append ? entries.size() : 0, entry);
}
複製代碼
經過以上代碼,知道了ModelLoaderFactory在Glide初始化時註冊到了一個列表中,之後使用。在分析DecodeJob的代碼裏時,咱們使用SourceGenerator加載遠程圖片,並分析到了loadData.fetcher.loadData(helper.getPriority(), this);
是真正加載數據的地方。
咱們先看loadData在哪賦值的
loadData = helper.getLoadData().get(loadDataListIndex++);
複製代碼
能夠看到,是使用Helper來調用方法。
//DecodeHelper
List<LoadData<?>> getLoadData() {
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
//根據model類型,經過Glide對應的registry獲取ModelLoader列表
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = modelLoaders.size(); i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
LoadData<?> current =
modelLoader.buildLoadData(model, width, height, options);
//循環建立出LoadData,用戶後面加載
if (current != null) {
loadData.add(current);
}
}
}
return loadData;
}
複製代碼
能夠看到,這實際上是一個列表,能夠知道,在
.append(String.class, InputStream.class, new StringLoader.StreamFactory()
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
複製代碼
這三個Factory中,由於咱們load的是一個String,因此咱們確定是StringLoader中
public class StringLoader<Data> implements ModelLoader<String, Data> {
private final ModelLoader<Uri, Data> uriLoader;
// Public API.
@SuppressWarnings("WeakerAccess")
public StringLoader(ModelLoader<Uri, Data> uriLoader) {
this.uriLoader = uriLoader;
}
@Override
public LoadData<Data> buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) {
Uri uri = parseUri(model);
if (uri == null || !uriLoader.handles(uri)) {
return null;
}
return uriLoader.buildLoadData(uri, width, height, options);
}
...
/** * Factory for loading {@link InputStream}s from Strings. */
public static class StreamFactory implements ModelLoaderFactory<String, InputStream> {
@NonNull
@Override
public ModelLoader<String, InputStream> build( @NonNull MultiModelLoaderFactory multiFactory) {
//關鍵在這兒
return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
}
@Override
public void teardown() {
// Do nothing.
}
}
}
複製代碼
能夠看到,其實它在裏面MultiModelLoaderFactory經過Uri.class和InputStream.class建立一個ModelLoader給StringLoader,因此StringLoader的加載功能轉移了。並且根據註冊關係知道轉移到了HttpUriLoader中。而它對應的Fetcher就是HttpUrlFetcher。
那麼能夠直接看HttpUrlFetcher的load代碼
/** * HttpUrlFetcher * HttpUrlFetcher的簡介:網絡數據抓取器,通俗的來說就是去服務器上下載圖片,支持地址重定向(最多5次) * */
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
} catch (IOException e) {
...
}
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {
//重定向次數過多
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException("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.
//經過URL的equals方法來比較會致使網絡IO開銷,通常會有問題
//能夠參考http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new HttpException("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());
}
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Stop the urlConnection instance of HttpUrlConnection from following redirects so that
// redirects will be handled by recursive calls to this method, loadDataWithRedirects.
urlConnection.setInstanceFollowRedirects(false);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
stream = urlConnection.getInputStream();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (isHttpOk(statusCode)) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (isHttpRedirect(statusCode)) {
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new HttpException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
// Closing the stream specifically is required to avoid leaking ResponseBodys in addition
// to disconnecting the url connection below. See #2352.
cleanup();
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == INVALID_STATUS_CODE) {
throw new HttpException(statusCode);
} else {
throw new HttpException(urlConnection.getResponseMessage(), statusCode);
}
}
複製代碼
看到這,終於找到了網絡通信的代碼,就是經過HttpUrlConnection來獲取數據流並返回。固然也能夠自定義使用OkHttp。能夠參考Glide4.8集成現有OkHttpClient並加載https圖片blog.csdn.net/ysy950803/a…
分析源碼的時候,咱們只應該關注主流程,不要太過於糾結每個細節。Glide的源碼很是值得仔細反覆閱讀,每次都能學到很多東西,固然也是一個考驗耐心的事情。
參考
Glide最全解析系列文章 - 郭神基於3.x版本的。
下面是個人公衆號,歡迎你們關注我