一個簡單的使用示例以下:算法
// 須要注意的是,佔位符不是異步加載的
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.ic_launcher_background)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
複製代碼
若是但願只獲取資源,而不顯示,可使用 CustomTarget:數組
Glide.with(context
.load(url)
.into(new CustomTarget<Drawable>() {
@Override
public void onResourceReady(Drawable resource, Transition<Drawable> transition) {
// Do something with the Drawable here.
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
// Remove the Drawable provided in onResourceReady from any Views and ensure
// no references to it remain.
}
});
複製代碼
若是須要在後臺線程加載,可使用 submit 獲取一個 FutureTarget:緩存
FutureTarget<Bitmap> futureTarget = Glide.with(context)
.asBitmap()
.load(url)
.submit(width, height);
Bitmap bitmap = futureTarget.get();
// Do something with the Bitmap and then when you're done with it:
Glide.with(context).clear(futureTarget);
複製代碼
Glide 源碼中最關鍵的 5 個接口以下:服務器
Request,用於表明一個圖片加載請求,相關的類有 RequestOptions(用於指定圓角、佔位圖等選項)、RequestListener(用於監聽執行結果)、RequestCoordinator(用於協調主圖、縮略圖、出錯圖的加載工做)、RequestManager(用於在生命週期回調時控制圖片加載請求的執行)等markdown
DataFecher,用於獲取數據,數據源多是本地文件、Asset 文件、服務器文件等網絡
Resource,表明一個具體的圖片資源,好比 Bitmap、Drawable 等架構
ResourceDecoder,用於讀取數據並轉爲對應的資源類型,例如將文件轉化爲 Bitmapapp
Target,圖片加載的目標,好比 ImageViewTarget異步
緩存相關的接口以下:socket
輔助接口以下:
還有一些重要的類以下:
所以,一個完整的圖片加載流程大體以下:
方法 Glide.with 用於返回一個 RequestManager,同時添加一個 Fragment 到對應的 Activity/Fragment 上,以監聽生命週期:
public class RequestManagerRetriever implements Handler.Callback {
static final String FRAGMENT_TAG = "com.bumptech.glide.manager";
@NonNull
private RequestManagerFragment getRequestManagerFragment(...) {
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
// 建立 Fragment,並添加到當前 Activity/Fragment 上
current = new RequestManagerFragment();
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
}
return current;
}
}
複製代碼
這樣,RequestManager 就能在 Activity/Fragment 生命週期回調時執行相應的操做,好比暫停/恢復執行網絡加載請求、清理資源等:
public class RequestManager implements LifecycleListener {
@Override
public synchronized void onStart() {
resumeRequests(); // 恢復加載
}
@Override
public synchronized void onStop() {
pauseRequests(); // 暫停加載
}
@Override
public synchronized void onDestroy() {
// 清除資源
for (Target<?> target : targetTracker.getAll()) {
clear(target);
}
requestTracker.clearRequests();
lifecycle.removeListener(this);
}
}
複製代碼
RequestManager 的 load 方法用於返回一個 RequestBuilder,在執行 into 方法時才正式構建請求並執行。
由於一個圖片加載請求可能附加了縮略圖或出錯時顯示的圖片,爲了協調多張圖片請求,Glide 將加載請求分爲 SingleRequest 、ErrorRequestCoordinator、ThumbnailRequestCoordinator 三種。
協調的方法很簡單:
其中,縮略圖、錯圖都是一個獨立的 SingleRequest,都有一套完整的加載流程,而且在加載完成後將資源發送給 Target。
爲何不須要協調佔位圖?由於佔位圖是直接在主線程中從 Android Resource 中加載的,而且第一時間就會加載並顯示,所以不須要協調。
圖片的加載流程是從 Request 的 begin 方法開始的,在正式加載數據以前,Glide 須要獲取 Target 的尺寸,以便在加載完成以後對圖片進行縮放。此外,佔位圖也是在這時設置的:
public final class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback {
@Override
public void begin() {
status = Status.WAITING_FOR_SIZE;
// 獲取 Target 的尺寸,獲取完成後回調 SizeReadyCallback
target.getSize(this);
// 設置佔位圖
target.onLoadStarted(getPlaceholderDrawable());
}
}
複製代碼
以 ViewTarget 爲例,它是經過監聽 ViewTreeObserver 來拿到自身寬高的:
public abstract class ViewTarget<T extends View, Z> extends BaseTarget<Z> {
void getSize(@NonNull SizeReadyCallback cb) {
cbs.add(cb);
// 在 View 即將被渲染時回調
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnPreDrawListener(layoutListener);
}
// 獲取尺寸並回調
void onPreDraw() {
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
notifyCbs(currentWidth, currentHeight);
}
}
複製代碼
獲取到尺寸以後,就能夠正式執行加載操做了:
@Override
public void onSizeReady(int width, int height) {
status = Status.RUNNING;
// 加載
engine.load(...);
}
複製代碼
加載的第一步是嘗試從內存緩存中獲取:
public class Engine {
public <R> LoadStatus load(...) {
EngineKey key = keyFactory.buildKey(...);
// 檢查內存緩存
EngineResource<?> memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource != null) { // 若是能找到,直接返回便可
cb.onResourceReady(...);
return null
}
// 不然建立新的加載工做
return waitForExistingOrStartNewJob(...);
}
}
複製代碼
Glide 的內存緩存包括兩部分:
@Nullable
private EngineResource<?> loadFromMemory(...) {
// 活躍的資源
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
return active;
}
// 內存緩存
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
return cached;
}
return null;
}
複製代碼
Glide 使用 Job 表明一個圖片加載工做,在建立新的 Job 對象以前,須要先檢查是否有相同的工做正在執行,若是有,則添加回調並返回,不然建立新的 Job 對象並執行:
private <R> LoadStatus waitForExistingOrStartNewJob(...) {
// 檢查是否有正在執行的相同的工做
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) { // 若是有,則添加回調並返回
current.addCallback(cb, callbackExecutor);
return new LoadStatus(cb, current);
}
// 不然建立新的加載工做並執行
EngineJob<R> engineJob = engineJobFactory.build(...);
// 執行
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
複製代碼
圖片加載的第二步是嘗試從磁盤緩存中獲取數據,Glide 的磁盤緩存分爲兩類:
Glide 首先會從縮放、變換後的文件緩存中查找,若是找不到,再到原始的圖片緩存中查找。
以 ResourceCache 爲例,它首先會經過 DiskCache 獲取文件:
class ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> {
@Override
public boolean startNext() {
currentKey = new ResourceCacheKey(...);
// 獲取文件
cacheFile = helper.getDiskCache().get(currentKey);
// 獲取數據
loadData.fetcher.loadData(helper.getPriority(), this);
return started;
}
}
複製代碼
接着再經過 DataFetcher 獲取數據:
private static final class FileFetcher<Data> implements DataFetcher<Data> {
@Override
public void loadData(...) {
try {
data = new InputStream(file);
callback.onDataReady(data);
} catch (FileNotFoundException e) {
callback.onLoadFailed(e);
}
}
}
複製代碼
若是磁盤緩存中找不到對應的圖片,則須要到服務器中獲取數據。以 OkHttp 爲例:
public class OkHttpStreamFetcher implements DataFetcher<InputStream>, okhttp3.Callback {
@Override
public void loadData(...) {
// 執行網絡請求
Request request = new Request.Builder().url(url.toStringUrl()).build;
call = client.newCall(request);
call.enqueue(this);
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
// 獲取 socket I/O 流
responseBody = response.body();
if (response.isSuccessful()) {
long contentLength = responseBody.contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
callback.onDataReady(stream);
} else {
callback.onLoadFailed(new HttpException(response.message(), response.code()));
}
}
}
複製代碼
成功獲取到數據以後,DataFetcher 的 Callback 就會將原始數據回調給 SourceGenerator,並緩存到磁盤文件中:
class SourceGenerator implements DataFetcherGenerator, DataFetcherGenerator.FetcherReadyCallback {
void onDataReadyInternal(LoadData<?> loadData, Object data) {
// 根據緩存策略判斷是否能夠緩存
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
cb.reschedule();
}
}
@Override
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
...
}
// 寫入到磁盤
private void cacheData(Object dataToCache) {
helper.getDiskCache().put(originalKey, writer);
}
}
複製代碼
從磁盤緩存/遠程服務器中獲取到的是數據流,還須要解碼爲 Bitmap、Gif 等資源類型。這個工做是 ResourceDecoder 來完成的:
public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {
@Override
public Resource<Bitmap> decode(...) {
return downsampler.decode(...); // 縮放
}
}
複製代碼
public final class Downsampler {
private Resource<Bitmap> decode(...) {
Bitmap result = decodeFromWrappedStreams(...);
return BitmapResource.obtain(result, bitmapPool);
}
}
複製代碼
在解碼過程當中,若是檢查到設備支持,則使用 GPU 存儲 Bitmap 數據:
boolean setHardwareConfigIfAllowed(...) {
boolean result = isHardwareConfigAllowed(...);
if (result) {
optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;
optionsWithScaling.inMutable = false;
}
return result;
}
複製代碼
不然使用 ARGB_8888 存儲:
public enum DecodeFormat {
PREFER_ARGB_8888,
PREFER_RGB_565;
public static final DecodeFormat DEFAULT = PREFER_ARGB_8888;
}
複製代碼
值得注意的是,此時 Glide 會判斷數據的來源,若是是否是從變換後的文件緩存中獲取的,就須要將圓角、旋轉等效果應用到 Bitmap 上:
class DecodeJob<R> {
<Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
Resource<Z> transformed = decoded;
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
transformed = appliedTransformation.transform(glideContext, decoded, width, height);
}
return result;
}
}
複製代碼
解碼完成以後,首先會將結果回調給 Engine、Target 等對象,接着將變換後的圖片數據寫入到磁盤緩存,最後清理資源:
class DecodeJob<R> {
private void decodeFromRetrievedData() {
Resource<R> resource = decodeFromData(currentFetcher, currentData, currentDataSource);
notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
}
private void notifyEncodeAndRelease(...) {
// 將結果回調給 Engine、SingleRequest 等對象
notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey);
// 將數據寫入到磁盤緩存
deferredEncodeManager.encode(diskCacheProvider, options);
// 清理資源
onEncodeComplete();
}
private static class DeferredEncodeManager<Z> {
void encode(DiskCacheProvider diskCacheProvider, Options options) {
// 這裏緩存的是變換後的圖片數據
diskCacheProvider
.getDiskCache()
.put(key, new DataCacheWriter<>(encoder, toEncode, options));
}
}
}
複製代碼
Engine 收到回調後會使用 ActiveResources 記錄該資源,並移除當前加載工做:
public class Engine {
@Override
public synchronized void onEngineJobComplete(...) {
activeResources.activate(key, resource);
jobs.removeIfCurrent(key, engineJob);
}
}
複製代碼
以 ImageView 爲例,Target 收到回調後首先判斷是否須要執行過渡動畫,接着將圖片資源設置到 View 裏面,最後判斷 Resource 是否爲 gif 動畫,若是是,則開始執行動畫:
public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> {
@Override
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);
maybeUpdateAnimatable(resource);
}
private void maybeUpdateAnimatable(@Nullable Z resource) {
if (resource instanceof Animatable) { // gif 動畫
animatable = (Animatable) resource;
animatable.start();
} else {
animatable = null;
}
}
protected abstract void setResource(@Nullable Z resource);
}
複製代碼
在使用內存緩存、對象池以前,Glide 首先會根據設備狀況分配對應的內存:
public final class GlideBuilder {
Glide build(@NonNull Context context) {
bitmapPool = new LruBitmapPool(memorySizeCalculator.getBitmapPoolSize());
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
}
複製代碼
能夠看到,具體的內存大小是 MemorySizeCaculator 計算的。
對於 ArrayPool,若是是低內存設備,則默認分配 2MB,不然分配 4MB:
public final class MemorySizeCalculator {
private static final int LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR = 2;
MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
arrayPoolSize = isLowMemoryDevice(builder.activityManager)
? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
: builder.arrayPoolSizeBytes;
}
static boolean isLowMemoryDevice(ActivityManager activityManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return activityManager.isLowRamDevice();
} else {
return true;
}
}
public static final class Builder {
// 4MB.
static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024;
int arrayPoolSizeBytes = ARRAY_POOL_SIZE_BYTES;
}
}
複製代碼
對於 Bitmap 對象池,由於 Glide 在 API 26 以上統一使用硬件(GPU)存儲 Bitmap,所以對內存的需求小了不少,只須要一個屏幕分辨率(ARGB),對於 1920x1080 的手機,大約是 8MB,而在 API 26 以前,則須要 4 個屏幕分辨率,也就是 32 MB。
static final int BITMAP_POOL_TARGET_SCREENS =
Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 4 : 1;
int widthPixels = builder.screenDimensions.getWidthPixels();
int heightPixels = builder.screenDimensions.getHeightPixels();
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;
int targetBitmapPoolSize = Math.round(screenSize * BITMAP_POOL_TARGET_SCREENS);
複製代碼
內存緩存則默認使用 2 個屏幕分辨率,對於 1920x1080 的手機,大約是 16MB:
static final int MEMORY_CACHE_TARGET_SCREENS = 2;
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
複製代碼
另外,Bitmap 對象池和內存緩存受到應用最大可用緩存的限制:
public class ActivityManager {
public int getMemoryClass() {
return staticGetMemoryClass();
}
}
複製代碼
但如今的手機性能已經很高了,這個值通常比較大,即便 Glide 會在此基礎之上乘以一個係數(0.4 或 0.33),也基本夠用,所以就不考慮了。
BitmapPool 使用 LRU 算法實現,主要接口以下:
public interface BitmapPool {
void put(Bitmap bitmap);
// 返回寬高、config 如出一轍的對象,同時將像素數據清除,若是找不到,就分配一個新的
Bitmap get(int width, int height, Bitmap.Config config);
// 返回寬高、config 如出一轍的對象,若是找不到,就分配一個新的
Bitmap getDirty(int width, int height, Bitmap.Config config);
// 清除全部對象,在設備內存低時(onLowMemory)調用
void clearMemory();
// 根據內存狀況選擇移除一半或全部對象
void trimMemory(int level);
}
複製代碼
在回收 Bitmap 對象時就會將它移入對象池中:
public class BitmapResource implements Resource<Bitmap>, Initializable {
@Override
public void recycle() {
bitmapPool.put(bitmap);
}
}
複製代碼
重用時機主要是在執行變換效果時:
public final class TransformationUtils {
public static Bitmap centerCrop(...) {
...
Bitmap result = pool.get(width, height, getNonNullConfig(inBitmap));
applyMatrix(inBitmap, result, m);
return result;
}
}
複製代碼
活躍的資源指已加載完成,而且未被釋放的資源:
public class Engine {
private final ActiveResources activeResources;
@Override
public synchronized void onEngineJobComplete(...) {
activeResources.activate(key, resource);
}
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
}
}
複製代碼
釋放的時機主要有三個:
此外,縮略圖會在主圖加載完成後釋放。所以,活躍的資源能夠簡單理解爲正在顯示的資源。
這些資源使用引用計數的方式管理,計數爲 0 時釋放:
class EngineResource<Z> implements Resource<Z> {
private int acquired;
synchronized void acquire() {
++acquired;
}
void release() {
boolean release = false;
synchronized (this) {
if (--acquired == 0) {
release = true;
}
}
if (release) {
listener.onResourceReleased(key, this);
}
}
}
複製代碼
MemoryCache 的接口和 BitmapPool、ArrayPool 基本同樣:
public interface MemoryCache {
Resource<?> remove(@NonNull Key key);
Resource<?> put(@NonNull Key key, @Nullable Resource<?> resource);
// 移除全部對象,在設備內存低時(onLowMemory)調用
void clearMemory();
// 根據內存狀況選擇移除一半或全部對象
void trimMemory(int level);
}
複製代碼
在資源釋放時,若是可使用內存緩存(可經過 RequestOptions 配置,默承認以),則緩存,不然回收:
public class Engine {
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
}
}
}
複製代碼
回收時會根據資源類型執行不一樣的操做,若是是 Bitmap,則移入 Bitmap 對象池中,若是不是,要麼清除資源,要麼什麼都不作。
內存緩存使用的 Key 和活躍資源同樣,都是 EngineKey,主要根據圖片寬高、變換信息、資源類型信息來區分:
class EngineKey implements Key {
private final int width;
private final int height;
private final Class<?> resourceClass;
private final Class<?> transcodeClass;
private final Map<Class<?>, Transformation<?>> transformations;
}
複製代碼
DiskCache 主要提供了三個接口:
public interface DiskCache {
interface Factory {
/**
* 默認緩存大小爲 250 MB
*/
int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
/**
* 默認緩存文件夾
*/
String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
DiskCache build();
}
File get(Key key);
void put(Key key, Writer writer);
void clear();
}
複製代碼
其中,緩存的文件分爲兩種:
具體的文件寫入操做是由 Encoder 完成的,以 Bitmap 爲例,緩存 Bitmap 時經過 compress 方法寫入到文件便可:
public class BitmapEncoder implements ResourceEncoder<Bitmap> {
@Override
public boolean encode(...) {
final Bitmap bitmap = resource.get();
boolean success = false;
OutputStream os = new FileOutputStream(file);
bitmap.compress(format, quality, os);
os.close();
}
}
複製代碼
若是須要清除文件緩存,能夠調用 Glide 的 tearDown 方法:
public class Glide {
public static void tearDown() {
glide.engine.shutdown();
}
}
複製代碼
public class Engine {
public void shutdown() {
diskCacheProvider.clearDiskCacheIfCreated();
}
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
synchronized void clearDiskCacheIfCreated() {
diskCache.clear();
}
}
}
複製代碼
磁盤緩存使用的 Key 能夠分爲三類:
Glide 主要分了三個線程池:
) 用於執行 gif 動畫,在 CPU 個數大於等於 4 時,線程數爲 2,不然爲 1
public final class GlideBuilder {
private GlideExecutor sourceExecutor;
private GlideExecutor diskCacheExecutor;
private GlideExecutor animationExecutor;
Glide build(@NonNull Context context) {
sourceExecutor = GlideExecutor.newSourceExecutor();
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
animationExecutor = GlideExecutor.newAnimationExecutor();
}
}
複製代碼
總的來講,圖片加載流程大體可分爲 4 步:
準備工做包括:
內存緩存包括兩部分:
數據加載包括兩部分:
磁盤緩存又分爲兩部分:
目標地址有不少種,好比本地文件路徑、文件句柄、Asset、服務器連接等。
成功獲取到數據以後:
Target 顯示圖片時還會判斷是否須要執行過渡動畫,若是須要,則執行過渡動畫後再顯示資源。顯示資源時,會判斷該資源類型是否爲 gif 圖,若是是,則開始執行動畫,不然直接顯示便可。
Glide 的緩存機制主要分爲三部分:
其中,活躍資源使用引用計數的方式來判斷釋放的時機,除此以外的其它緩存機制都是經過 LRU 算法來管理數據的。
活躍資源指已加載完成,而且未被釋放的資源,能夠簡單地理解爲正在顯示的資源。
資源釋放的時機主要有三個:
資源釋放時,首先會判斷是否可使用內存緩存(可經過 RequestOptions 配置,默承認以),若是能夠,則使用內存緩存資源,不然回收資源。回收時會根據資源類型執行不一樣的操做,若是是 Bitmap,則移入 Bitmap 對象池中,若是不是,要麼清除資源,要麼什麼都不作。
內存/磁盤分配:
另外,Bitmap 對象池和內存緩存受到應用最大可用緩存的限制(在此基礎之上乘以係數 0.4 或 0.33),若是不夠,則按比例分配。
線程池主要分了三個: