一个简单的使用示例以下:算法
// 须要注意的是,占位符不是异步加载的
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),若是不够,则按比例分配。
线程池主要分了三个: