使用
一个简单的使用示例如下:
// 需要注意的是,占位符不是异步加载的
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(用于在生命周期回调时控制图片加载请求的执行)等
-
DataFecher,用于获取数据,数据源可能是本地文件、Asset 文件、服务器文件等
-
Resource,代表一个具体的图片资源,比如 Bitmap、Drawable 等
-
ResourceDecoder,用于读取数据并转为对应的资源类型,例如将文件转化为 Bitmap
-
Target,图片加载的目标,比如 ImageViewTarget
缓存接口
缓存相关的接口如下:
- MemoryCache,内存缓存,使用 LruCache 实现
- DiskCache,本地文件缓存,使用 DiskLruCache 实现
- BitmapPool,用于缓存 Bitmap 对象
- ArrayPool,用于缓存数据对象
- Encoder,用于将数据持久化,例如将 Bitmap 写到本地文件
辅助接口
辅助接口如下:
- LifeCycleListener,用于监听 Activity/Fragment 的生命周期,以便 Target 控制动画效果、清除资源
- ConnectivityListener,用于监听设备网络情况,以执行对应的操作,比如重新开始加载图片
- Transformation,用于执行圆角、旋转等图片变换操作,需要注意的是,这些变换不会应用到占位符中
- Transition,用于执行过渡动画效果
- ResourceTranscoder,用于将转换资源类型,比如将 Bitmap 转换为 Drawable
其它
还有一些重要的类如下:
- Glide,用于提供统一的对外接口
- Engine,用于统筹所有的图片加载工作,相关的类有 EngineJob、EngineKey 等
- MemorySizeCalculator,用于根据设备分辨率计算内存缓存、BitmapPool、ArrayPool 的大小
因此,一个完整的图片加载流程大致如下:
图片加载流程
添加 Fragment,监听生命周期
方法 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 的内存缓存包括两部分:
- 活跃的资源,即加载完成后回调给 Target 并且未被释放的资源,可以简单理解为正在显示的图片
- 内存缓存,即不再显示,但仍处于内存中未被 LRU 算法移除的资源
@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 的磁盘缓存分为两类:
- ResourceCache,指执行了缩放、变换等操作后的图片
- DataCache,指原始图片缓存
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),也基本够用,因此就不考虑了。
重用 Bitmap
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);
}
}
释放的时机主要有三个:
- 生命周期方法 onStop 回调
- 生命周期方法 onDestroy 回调
- onTrimMemory 回调,级别为 MODERATE
此外,缩略图会在主图加载完成后释放。因此,活跃的资源可以简单理解为正在显示的资源。
这些资源使用引用计数的方式管理,计数为 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 可以分为三类:
- GlideUrl,在获取远程图片数据的时候使用,主要根据 url 地址和 headers 信息来区分
- ObjectKey,在获取本地图片数据的时候使用,主要根据 Uri、File 来区分
- ResourceCacheKey,在获取变换后的图片缓存时使用,主要根据图片宽高、变换信息、资源类型信息来区分
线程池
Glide 主要分了三个线程池:
- 用于获取数据,线程数为 CPU 个数,但不大于 4 个
- 用于执行读写缓存,线程数为 1 ) 用于执行 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 步:
- 准备工作
- 内存缓存
- 数据加载
- 解码显示
准备工作包括:
- 添加一个 Fragment 到对应的 Activity/Fragment 上面,监听生命周期。以控制请求的暂停执行、恢复执行,或清理资源
- 递归构建 Request。以协调主图、缩略图、出错图的加载请求。缩略图和主图同时加载,如果主图先加载完成,则取消缩略图的加载请求,否则先显示缩略图,等主图加载完成后再清除。出错图则是在主图加载失败后才开始加载并显示
- 获取目标尺寸。以 ImageView 为例,尺寸是在 ViewTreeObserver 的 onPreDraw 方法回调后获取的。
- 设置占位图。占位图是直接调用 setImageDrawable 方法设置的,因此不需要和主图协调
内存缓存包括两部分:
- 活跃的资源,即加载完成后已回调给 Target 并且未被释放的资源,可以简单理解为正在显示的图片,通过引用计数判断具体释放的时机
- 内存缓存,即不再显示,但仍处于内存中未被 LRU 算法移除的资源
数据加载包括两部分:
- 从磁盘缓存中加载
- 从目标地址中加载
磁盘缓存又分为两部分:
- 从缩放、变换后的文件缓存中查找
- 如果找不到,再到原始的图片缓存中查找
目标地址有很多种,比如本地文件路径、文件句柄、Asset、服务器链接等。
成功获取到数据之后:
- 首先需要将原始图像数据缓存到本地磁盘中
- 接着将数据解码为 Bitmap/Drawable 等资源类型,同时应用圆角等变换效果(如果不是从变换后的文件缓存中获取的话)
- 然后才能回调给 Target 并显示
- 最后还需要将缩放、变换后的图片数据写入到磁盘缓存,并清理资源
Target 显示图片时还会判断是否需要执行过渡动画,如果需要,则执行过渡动画后再显示资源。显示资源时,会判断该资源类型是否为 gif 图,如果是,则开始执行动画,否则直接显示即可。
缓存、资源重用机制
Glide 的缓存机制主要分为三部分:
- 资源重用(数组对象、Bitmap 对象、活跃资源)
- 内存缓存
- 磁盘缓存(原始图片数据、应用变换后的图片数据)
其中,活跃资源使用引用计数的方式来判断释放的时机,除此之外的其它缓存机制都是通过 LRU 算法来管理数据的。
活跃资源指已加载完成,并且未被释放的资源,可以简单地理解为正在显示的资源。
资源释放的时机主要有三个:
- 生命周期方法 onStop 回调
- 生命周期方法 onDestroy 回调
- onTrimMemory 回调,级别为 MODERATE
资源释放时,首先会判断是否可以使用内存缓存(可通过 RequestOptions 配置,默认可以),如果可以,则使用内存缓存资源,否则回收资源。回收时会根据资源类型执行不同的操作,如果是 Bitmap,则移入 Bitmap 对象池中,如果不是,要么清除资源,要么什么都不做。
内存/磁盘分配:
- 数组对象池,如果是低内存设备,则默认分配 2MB,否则分配 4MB
- Bitmap 对象池,API 26 之前分配四个屏幕分辨率(1920x1080 的手机大约是 32MB),API 26 之后统一使用 GPU 存储 Bitmap,因此只需要一个屏幕分辨率(1920x1080 的手机大约是 8MB)
- 内存缓存,默认使用 2 个屏幕分辨率(1920x1080 的手机大约是 16MB)
- 磁盘缓存默认最多缓存 250MB 数据
另外,Bitmap 对象池和内存缓存受到应用最大可用缓存的限制(在此基础之上乘以系数 0.4 或 0.33),如果不够,则按比例分配。
线程池主要分了三个:
- 用于获取数据,线程数为 CPU 个数,但不大于 4 个
- 用于执行读写缓存,线程数为 1
- 用于执行 gif 动画,在 CPU 个数大于等于 4 时,线程数为 2,否则为 1