本文首發於公眾號“AntDream”,歡迎微信搜索“AntDream”或掃描文章底部二維碼關注,和我一起每天進步一點點
Glide通過生命週期取消加載
生命週期回調過程
onStop
--->RequestManager.onStop
-->RequestTracker.pauseRequest
--> SingleRequest.pause
--> SingleRequest.cancel
---> Engine.cancel
---> EngineJob.removeCallback
---> 如果所有的回調監聽都移除了
--> DecodeJob.cancel
//EngineJob.class
synchronized void removeCallback(ResourceCallback cb) {
stateVerifier.throwIfRecycled();
cbs.remove(cb);
if (cbs.isEmpty()) {
cancel();
boolean isFinishedRunning = hasResource || hasLoadFailed;
if (isFinishedRunning && pendingCallbacks.get() == 0) {
release();
}
}
}
- 如果任務沒有執行,就從隊列裏移除,取消任務
- 如果任務已經執行,就利用isCancelled標記在停止流程
//DecodeJob.class
public void cancel() {
isCancelled = true;
DataFetcherGenerator local = currentGenerator;
if (local != null) {
local.cancel();
}
}
上面最終會調用到具體的獲取圖片的任務,比如從網絡獲取圖片的HttpUrlFetcher
//HttpUrlFetcher.class
private volatile boolean isCancelled;
@Override
public void cancel() {
// TODO: we should consider disconnecting the url connection here, but we can't do so
// directly because cancel is often called on the main thread.
isCancelled = true;
}
//內部是通過HttpURLConnection來從網絡獲取數據
private InputStream loadDataWithRedirects(
URL url, int redirects, URL lastUrl, Map<String, String> headers) throws HttpException {
...
urlConnection = buildAndConfigureConnection(url, headers);
try {
// 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();
} catch (IOException e) {
...
}
if (isCancelled) {
return null;
}
final int statusCode = getHttpStatusCodeOrInvalid(urlConnection);
if (isHttpOk(statusCode)) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (isHttpRedirect(statusCode)) {
String redirectUrlString = urlConnection.getHeaderField(REDIRECT_HEADER_FIELD);
...
cleanup();
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == INVALID_STATUS_CODE) {
throw new HttpException(statusCode);
} else {
...
}
}
由於cancel方法一般是在主線程調用的,所以這裏採用的是volatile關鍵字這種方式,在正式獲取網絡數據時會進行攔截;
- 如果攔截到了,那直接返回null;如果沒攔截到,就獲取到數據
- 以上最終都會回調到DecodeJob的onDataFetcherReady方法
onDataFetcherReady ---> decodeFromRetrievedData ---> decodeFromData
private void decodeFromRetrievedData() {
...
Resource<R> resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
...
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
} else {
//任務被攔截,嘗試其他的加載方式
runGenerators();
}
}
decodeFromData中會進行判斷,如果data為Null就直接返回Null(被攔截時會是null),這個時候會執行runGenerators方法
runGenerators方法實際上就是加載流程的流轉,比如先從文件中加載,文件中沒有,就去網絡加載,一個個去試這樣。當然這裏面肯定也有cancel攔截的
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
//被cancel攔截就不會嘗試其他加載方式,直接任務取消
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();
}
}
所以當加載數據的流程被攔截了,比如網絡請求返回null,那到這裏這個流程就攔截下來了,直接
notifyFailed任務失敗了。
那如果網絡加載的時候沒有攔截住呢?data就不會為null,就會走notifyEncodeAndRelease方法,最終會一直走到EngineJob的notifyCallbacksOfResult方法,這個方法裏面又會有cancel攔截。這樣也就是數據加載前加載後都被攔截下來了
Glide中的線程池
自定義ThreadFactory,主要的一個功能是實現限制部分線程網絡訪問,利用的是嚴苛模式
@Override
public Thread newThread(@NonNull final Runnable runnable) {
Thread newThread =
delegate.newThread(
new Runnable() {
@Override
public void run() {
if (preventNetworkOperations) {
StrictMode.setThreadPolicy(
new ThreadPolicy.Builder().detectNetwork().penaltyDeath().build());
}
try {
runnable.run();
} catch (Throwable t) {
uncaughtThrowableStrategy.handle(t);
}
}
});
newThread.setName("glide-" + name + "-thread-" + threadNum.getAndIncrement());
return newThread;
}
線程池主要有這幾種:
- AnimationExecutor:加載動畫相關,禁止訪問網絡;如果CPU核心數大於4,就是2個線程,否則是一個線程,核心線程數和最大線程數相同
- diskCacheExecutor:從磁盤加載圖片,禁止訪問網絡;線程數為1,核心線程數和最大線程數相同
- sourceExecutor:可以訪問網絡,線程數跟CPU核心數有關,不大於4,核心線程數和最大線程數相同
- newUnlimitedSourceExecutor:用於網絡請求圖片,沒有核心線程,只有非核心線程,類似CacheThreadPoll
Glide做了線程池優化,核心線程也會遵循超時策略
public GlideExecutor build() {
if (TextUtils.isEmpty(name)) {
throw new IllegalArgumentException(
"Name must be non-null and non-empty, but given: " + name);
}
ThreadPoolExecutor executor =
new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
/*keepAliveTime=*/ threadTimeoutMillis,
TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(),
new DefaultThreadFactory(
threadFactory, name, uncaughtThrowableStrategy, preventNetworkOperations));
if (threadTimeoutMillis != NO_THREAD_TIMEOUT) {
executor.allowCoreThreadTimeOut(true);
}
return new GlideExecutor(executor);
}
歡迎關注我的公眾號AntDream查看更多精彩文章!