这里以5.0.0-rc1版本为例,缓存基本离不开池化的概念,了解此概念有助于理解本篇文章
一、BitmapRecycle
bitmap回收池:
这里着重看BitmapPool,LruPoolStrategy,GroupedLinkedMap这三个以及其实现类即可,感觉设计理念和精髓就在其中!其它的有兴趣可以自己研究下;
1.1BitmapPool接口,以及其实现类LruBitmapPool
1.1.1 BitmapResource
采用obtain(类比handler)方式重用bitmap
public class BitmapResource implements Resource<Bitmap>, Initializable {
private final Bitmap bitmap;
private final BitmapPool bitmapPool;
/**
* Returns a new { @link BitmapResource} wrapping the given { @link Bitmap} if the Bitmap is
* non-null or null if the given Bitmap is null.
*
* @param bitmap A Bitmap.
* @param bitmapPool A non-null { @link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}.
*/
@Nullable
public static BitmapResource obtain(@Nullable Bitmap bitmap, @NonNull BitmapPool bitmapPool) {
if (bitmap == null) {
return null;
} else {
return new BitmapResource(bitmap, bitmapPool);
}
}
public BitmapResource(@NonNull Bitmap bitmap, @NonNull BitmapPool bitmapPool) {
this.bitmap = Preconditions.checkNotNull(bitmap, "Bitmap must not be null");
this.bitmapPool = Preconditions.checkNotNull(bitmapPool, "BitmapPool must not be null");
}
@NonNull
@Override
public Class<Bitmap> getResourceClass() {
return Bitmap.class;
}
@NonNull
@Override
public Bitmap get() {
return bitmap;
}
@Override
public int getSize() {
return Util.getBitmapByteSize(bitmap);
}
@Override
public void recycle() {
bitmapPool.put(bitmap);
}
@Override
public void initialize() {
bitmap.prepareToDraw();
}
}
1.1.2 BitmapPool
//返回当前bitmap pool的大小
long getMaxSize();
//动态扩大pool大小乘积
void setSizeMultiplier(float sizeMultiplier);
//存bitmap
void put(Bitmap bitmap);
//取bitmap,下方是具体实现方式之一,但是如果遇到非空但是key相同,需要擦除,否则会出现图片跳动问题
Bitmap get(int width, int height, Bitmap.Config config);
//是上面的具体实现
Bitmap getDirty(int width, int height, Bitmap.Config config);
//清缓存
void clearMemory();
void trimMemory(int level);
1.1.3 LruBitmapPool
实现类(config代码以及策略选择代码直接贴进去了,后面主要看策略):
LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set<Bitmap.Config> allowedConfigs) {
this.initialMaxSize = maxSize;//bitmap池大初始大小
this.maxSize = maxSize;//bitmap池最大
this.strategy = strategy;//缓存策略
this.allowedConfigs = allowedConfigs;//缓存配置
this.tracker = new NullBitmapTracker();//啥都没干,空实现,根据其定义接口应该是调试时统计数据判断算法的优劣与否
}
//根据版本的不同具体设置缓存策略的配置,在android O之上移除了位图
private static Set<Bitmap.Config> getDefaultAllowedConfigs() {
Set<Bitmap.Config> configs = new HashSet<>(Arrays.asList(Bitmap.Config.values()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// GIFs, among other types, end up with a native Bitmap config that doesn't map to a java
// config and is treated as null in java code. On KitKat+ these Bitmaps can be reconfigured
// and are suitable for re-use.
configs.add(null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
configs.remove(Bitmap.Config.HARDWARE);
}
return Collections.unmodifiableSet(configs);
}
//缓存策略选取,这里根据安卓版本号进行了具体的策略区分
private static LruPoolStrategy getDefaultStrategy() {
final LruPoolStrategy strategy;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
strategy = new SizeConfigStrategy();
} else {
strategy = new AttributeStrategy();
}
return strategy;
}
1.1.4 SizeConfigStrategy和AttributeStrategy的区别
核心代码区别(GroupedLinkedMap数据结构后面说):
//SizeStrategy @RequiresApi(Build.VERSION_CODES.KITKAT)
@Override
public void put(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
final Key key = keyPool.get(size);
groupedMap.put(key, bitmap);
Integer current = sortedSizes.get(key.size);
sortedSizes.put(key.size, current == null ? 1 : current + 1);
}
//另附:Util.getBitmapByteSize(bitmap),也就是为什么4.4是分界
@TargetApi(Build.VERSION_CODES.KITKAT)
public static int getBitmapByteSize(@NonNull Bitmap bitmap) {
// The return value of getAllocationByteCount silently changes for recycled bitmaps from the
// internal buffer size to row bytes * height. To avoid random inconsistencies in caches, we
// instead assert here.
if (bitmap.isRecycled()) {
throw new IllegalStateException(
"Cannot obtain size for recycled Bitmap: "
+ bitmap
+ "["
+ bitmap.getWidth()
+ "x"
+ bitmap.getHeight()
+ "] "
+ bitmap.getConfig());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// Workaround for KitKat initial release NPE in Bitmap, fixed in MR1. See issue #148.
try {
return bitmap.getAllocationByteCount();
} catch (
@SuppressWarnings("PMD.AvoidCatchingNPE")
NullPointerException e) {
// Do nothing.
}
}
return bitmap.getHeight() * bitmap.getRowBytes();
}
//AttributeStrategy
@Override
public void put(Bitmap bitmap) {
final Key key = keyPool.get(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
groupedMap.put(key, bitmap);
}
1.1.5 GroupedLinkedMap
代码就不放了,简单画个图看一下意思吧:
1.1.6 put操作
@Override
public synchronized void put(Bitmap bitmap) {
if (bitmap == null) {//判空
throw new NullPointerException("Bitmap must not be null");
}
if (bitmap.isRecycled()) {//是否被回收
throw new IllegalStateException("Cannot pool recycled bitmap");
}
if (!bitmap.isMutable()//可变位图
|| strategy.getSize(bitmap) > maxSize
|| !allowedConfigs.contains(bitmap.getConfig())) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(
TAG,
"Reject bitmap from pool"
+ ", bitmap: "
+ strategy.logBitmap(bitmap)
+ ", is mutable: "
+ bitmap.isMutable()
+ ", is allowed config: "
+ allowedConfigs.contains(bitmap.getConfig()));
}
bitmap.recycle();//不符合条件,回收bitmap
return;
}
final int size = strategy.getSize(bitmap);//根据系统版本获取bitmap策略
strategy.put(bitmap);//存储bitmap
tracker.add(bitmap);
puts++;
currentSize += size;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
}
dump();//记录信息,调试打印用的
evict();//调整大小
}
1.1.7 get操作
@Override
@NonNull
public Bitmap get(int width, int height, Bitmap.Config config) {
Bitmap result = getDirtyOrNull(width, height, config);
if (result != null) {
// Bitmaps in the pool contain random data that in some cases must be cleared for an image
// to be rendered correctly. we shouldn't force all consumers to independently erase the
// contents individually, so we do so here. See issue #131.
result.eraseColor(Color.TRANSPARENT);//如果有可用的脏数据进行图像擦除,防止跳动
} else {
result = createBitmap(width, height, config);//创建新的bitmap
}
return result;
}
@NonNull
@Override
public Bitmap getDirty(int width, int height, Bitmap.Config config) {
Bitmap result = getDirtyOrNull(width, height, config);
if (result == null) {
result = createBitmap(width, height, config);
}
return result;
}
@Nullable
private synchronized Bitmap getDirtyOrNull(
int width, int height, @Nullable Bitmap.Config config) {
assertNotHardwareConfig(config);
// Config will be null for non public config types, which can lead to transformations naively
// passing in null as the requested config here. See issue #194.
//就这行代码是关键,后面可以忽略
final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG);
//省略部分代码
return result;
}
二、DiskLruCache
三、MemoryCache
这里内存就比较简单了,说明复杂的点在后面。。。
3.1MemoryCache
3.2实现类:
四、缓存调用(重点来了)
需要从load方法入手,不过还是老样子,还得看相应的接口以及实现类,后面再把整个流程串起来。
4.1一些工具类的介绍
4.1.1 Engine
顾名思义就是图片处理引擎:
再来看一下重要的参数:
private static final String TAG = "Engine";
private static final int JOB_POOL_SIZE = 150;
private static final boolean VERBOSE_IS_LOGGABLE = Log.isLoggable(TAG, Log.VERBOSE);
private final Jobs jobs;//管理EngineJob的集合,是个大工程,后面细说
private final EngineKeyFactory keyFactory;//资源key工厂
private final MemoryCache cache;//内存缓存
private final EngineJobFactory engineJobFactory;//引擎工作工厂
private final ResourceRecycler resourceRecycler;//资源回收者
private final LazyDiskCacheProvider diskCacheProvider;//硬盘缓存策略提供者
private final DecodeJobFactory decodeJobFactory;//解码工厂,这个东西比较多,后面会单独说这个
private final ActiveResources activeResources;//活跃资源管理类,主要存放的是弱引用资源,其中会有一个专门的线程根据key释放跃资源
4.1.2 ActiveResources
用来管理弱引用的活跃资源的工具类
4.1.3MemoryCache
这次从MemoryCache获取之后同时让其处于活跃状态,MemoryCache存取上面做了具体说明,这里不再赘述。
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
4.1.4 LazyDiskCacheProvider
获取硬盘缓存策略,也就是真正实现硬盘缓存的提供者
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
private final DiskCache.Factory factory;
private volatile DiskCache diskCache;
LazyDiskCacheProvider(DiskCache.Factory factory) {
this.factory = factory;
}
@VisibleForTesting
synchronized void clearDiskCacheIfCreated() {
if (diskCache == null) {
return;
}
diskCache.clear();
}
@Override
public DiskCache getDiskCache() {
if (diskCache == null) {
synchronized (this) {
if (diskCache == null) {
diskCache = factory.build();
}
if (diskCache == null) {
diskCache = new DiskCacheAdapter();
}
}
}
return diskCache;
}
}
public static GlideExecutor.Builder newDiskCacheBuilder() {
return new GlideExecutor.Builder(/* preventNetworkOperations= */ true)
.setThreadCount(DEFAULT_DISK_CACHE_EXECUTOR_THREADS /*1*/ )
.setName(DEFAULT_DISK_CACHE_EXECUTOR_NAME /*disk-cache*/ );
}
4.1.5Jobs
处理任务的job集合
final class Jobs {
private final Map<Key, EngineJob<?>> jobs = new HashMap<>();
private final Map<Key, EngineJob<?>> onlyCacheJobs = new HashMap<>();
@VisibleForTesting
Map<Key, EngineJob<?>> getAll() {
return Collections.unmodifiableMap(jobs);
}
EngineJob<?> get(Key key, boolean onlyRetrieveFromCache) {
return getJobMap(onlyRetrieveFromCache).get(key);
}
void put(Key key, EngineJob<?> job) {
getJobMap(job.onlyRetrieveFromCache()).put(key, job);
}
void removeIfCurrent(Key key, EngineJob<?> expected) {
Map<Key, EngineJob<?>> jobMap = getJobMap(expected.onlyRetrieveFromCache());
if (expected.equals(jobMap.get(key))) {
jobMap.remove(key);
}
}
private Map<Key, EngineJob<?>> getJobMap(boolean onlyRetrieveFromCache) {
return onlyRetrieveFromCache ? onlyCacheJobs : jobs;
}
}
4.1.6 GlideExecutor
所有job的executor都是从这个build中fork出来的
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);
}
4.2具体调用
中间会省略大部分代码
4.2.1优先从活跃资源或硬盘缓存中取
EngineResource<?> memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
return waitForExistingOrStartNewJob(/*省略*/)
}
}
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
}
4.2.2都没有取到开始根据资源类型进行匹配引擎和解码job
waitForExistingOrStartNewJob方法:
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(/*省略*/);
DecodeJob<R> decodeJob =
decodeJobFactory.build(/*省略*/);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
4.3.3关于BitmapPool
public void run() {
//...
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (CallbackException e) {
//...
}
private void runWrapped() {
switch (runReason) {
//...
case DECODE_DATA:
decodeFromRetrievedData();
break;
//...
}
private void decodeFromRetrievedData() {
//...
Resource<R> resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
//...
}
}
private <Data> Resource<R> decodeFromData(
DataFetcher<?> fetcher, Data data, DataSource dataSource) throws GlideException {
try {
if (data == null) {
return null;
}
long startTime = LogTime.getLogTime();
Resource<R> result = decodeFromFetcher(data, dataSource);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded result " + result, startTime);
}
return result;
} finally {
fetcher.cleanup();
}
}
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
return runLoadPath(data, dataSource, path);
}
<Data> LoadPath<Data, ?, Transcode> getLoadPath(Class<Data> dataClass) {
return glideContext.getRegistry().getLoadPath(dataClass, resourceClass, transcodeClass);
}
public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
@NonNull Class<Data> dataClass,
@NonNull Class<TResource> resourceClass,
@NonNull Class<Transcode> transcodeClass) {
LoadPath<Data, TResource, Transcode> result =
loadPathCache.get(dataClass, resourceClass, transcodeClass);
if (loadPathCache.isEmptyLoadPath(result)) {
return null;
} else if (result == null) {
List<DecodePath<Data, TResource, Transcode>> decodePaths =
getDecodePaths(dataClass, resourceClass, transcodeClass);
// It's possible there is no way to decode or transcode to the desired types from a given
// data class.
if (decodePaths.isEmpty()) {
result = null;
} else {
result =
new LoadPath<>(
dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
}
loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
}
return result;
}
@NonNull
private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths(
@NonNull Class<Data> dataClass,
@NonNull Class<TResource> resourceClass,
@NonNull Class<Transcode> transcodeClass) {
List<DecodePath<Data, TResource, Transcode>> decodePaths = new ArrayList<>();
List<Class<TResource>> registeredResourceClasses =
decoderRegistry.getResourceClasses(dataClass, resourceClass);
for (Class<TResource> registeredResourceClass : registeredResourceClasses) {
List<Class<Transcode>> registeredTranscodeClasses =
transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);
for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {
List<ResourceDecoder<Data, TResource>> decoders =
decoderRegistry.getDecoders(dataClass, registeredResourceClass);
ResourceTranscoder<TResource, Transcode> transcoder =
transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
DecodePath<Data, TResource, Transcode> path =
new DecodePath<>(
dataClass,
registeredResourceClass,
registeredTranscodeClass,
decoders,
transcoder,
throwableListPool);
decodePaths.add(path);
}
}
return decodePaths;
}