内容简介:Bitmap加载不当导致内存占用过大或者内存抖动,可能是每个Android工程师无法逃避的问题,如何合理的管理Bitmap内存,几乎成了各位必备的功课;好在Android官方早已经为我们提供了解决思路;本章主要内容:参考
Bitmap加载不当导致内存占用过大或者内存抖动,可能是每个Android工程师无法逃避的问题,如何合理的管理Bitmap内存,几乎成了各位必备的功课;好在Android官方早已经为我们提供了解决思路;
本章主要内容:
Bitmap LruBitmapPool AttributeStrategy SizeConfigStrategy GroupedLinkedMap MemorySizeCalculator
Bitmap内存管理和复用
参考 Managing Bitmap Memory 文档:
Android对Bitmap的内存管理
在Android2.2或更低的版本中,当发生垃圾回收时,线程都会暂停执行。这会导致延迟,降低程序性能。Android2.3增加了并发的垃圾回收机制,这意味着当图片对象不再被引用时所占用的内存空间立即就会被回收。
在Android2.3.3或更低版本中,Bitmap的像素数据是存储在Native内存中,它和Bitmap对象本身是分开来的,Bitmap对象本身是存储在 Java 虚拟机堆内存中。存储在本地内存中的像素数据的释放是不可预知的,这样就有可能导致应用短暂的超过其内存限制而崩溃。Android3.0之后,Bitmap像素数据和它本身都存储在Java虚拟机的堆内存中。到了Android8.0及其以后,Bitmap有重新存储到Native堆中。
在Android2.3.3或更低版本中,调用 recycle()
方法来尝试对Bitmap进行回收;
在Android3.0或者更高的版本中,使用 BitmapFactory.Options.inBitmap
来尝试对Bitmap进行复用,但是复用Bitmap是有条件限制的;
复用Bitmap的限制 参考官方文档
BitmapFactory.Options inSampleSize=1
既然想复用Bitmap,就需要有集合来存储这些Bitmap,在Glide中, BitmapPool
就是干这事的。
Glide中的BitmapPool
BitmapPool
是Glide中对Bitmap复用进行统一管理的接口,原则上所有需要创建Bitmap的操作,都要经过它来进行获取,BitmapPool的类关系图如下:
(缺个图)
-
BitmapPool是一个接口,实现类有BitmapPoolAdapter和LruBitmapPool这两个; -
BitmapPoolAdapter是一个空壳子,根本没有做实际意义上的缓存操作; -
LruBitmapPool采用 策略模式 ,它自身不处理具体逻辑,真正的逻辑在LruPoolStrategy中;
LruBitmapPool
LruBitmapPool是策略的执行者,也是缓存大小的控制者;
LruBitmapPool.java
public class LruBitmapPool implements BitmapPool{
//构造方法
public LruBitmapPool(long maxSize) {
this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());
}
//构造方法
LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set<Bitmap.Config> allowedConfigs) {
this.initialMaxSize = maxSize;
this.maxSize = maxSize;
this.strategy = strategy;
this.allowedConfigs = allowedConfigs;
this.tracker = new NullBitmapTracker();
}
//put方法
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");
}
//如果bitmap不是Mutable或者bitmap尺寸大于最大尺寸,或者不允许缓存
if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize
|| !allowedConfigs.contains(bitmap.getConfig())) {
//直接回收bitmap
bitmap.recycle();
return;
}
//通过策略获取bitmap size
final int size = strategy.getSize(bitmap);
strategy.put(bitmap);//添加进lruCache
tracker.add(bitmap);
//计算put数量和currentSize
puts++;
currentSize += size;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
}
dump();
evict();//可能会触发淘汰
}
}
//获取能够缓存的config
private static Set<Bitmap.Config> getDefaultAllowedConfigs() {
//获取所有config
Set<Bitmap.Config> configs = new HashSet<>(Arrays.asList(Bitmap.Config.values()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
configs.add(null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//Bitmap.Config.HARDWARE是8.0之后才有的,不允许缓存
configs.remove(Bitmap.Config.HARDWARE);
}
return Collections.unmodifiableSet(configs);
}
//获取脏数据,可能返回空
private synchronized Bitmap getDirtyOrNull(
int width, int height, @Nullable Bitmap.Config config) {
assertNotHardwareConfig(config);
//从策略中获取
final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG);
if (result == null) {
//没有获取到,misses数量+1
misses++;
} else {
hits++;//命中+1
currentSize -= strategy.getSize(result);//currentSize减少
tracker.remove(result);
normalize(result);
}
dump();
return result;
}
//将缓存整理到size大小以内
private synchronized void trimToSize(long size) {
while (currentSize > size) {//判断条件
final Bitmap removed = strategy.removeLast();//调用策略的方法
if (removed == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Size mismatch, resetting");
dumpUnchecked();
}
currentSize = 0;
return;
}
tracker.remove(removed);
currentSize -= strategy.getSize(removed);
evictions++;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
}
dump();
removed.recycle();//被淘汰的bitmap执行回收
}
//获取擦除掉像素的Bitmap
public Bitmap get(int width, int height, Bitmap.Config config) {
Bitmap result = getDirtyOrNull(width, height, config);
if (result != null) {
result.eraseColor(Color.TRANSPARENT);
} else {
result = createBitmap(width, height, config);
}
return result;
}
//直接获取携带脏数据Bitmap
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;
}
}
复制代码
LruBitmapPool
主要方法:
构造方法
:需要传入 maxSize
,这个是控制缓存大小的必要参数;
put()
:将Bitmap进行缓存,如果不满足缓存条件,执行回收;
getDirtyOrNull()
:从缓存中获取Bitmap,可能返回空;
trimToSize()
:对内存重新整理,防止超出目标size;
get()
:获取一个全透明像素的bitmap,不为空;
getDirty()
:直接获取,如果取自缓存,可能包含脏数据,不为空;
其中操作缓存的核心方法在 strategy
中, LruPoolStrategy
也是一个抽象的策略接口,真正策略的实现类是 SizeConfigStrategy
和 AttributeStrategy
;
LruPoolStrategy
LruPoolStrategy.java
interface LruPoolStrategy {
//put操作
void put(Bitmap bitmap);
//get操作
@Nullable
Bitmap get(int width, int height, Bitmap.Config config);
@Nullable
Bitmap removeLast();
String logBitmap(Bitmap bitmap);
String logBitmap(int width, int height, Bitmap.Config config);
int getSize(Bitmap bitmap);
}
复制代码
通过调用 getDefaultStrategy()
方法获得LruPoolStrategy实例:
getDefaultStrategy()
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;
}
复制代码
SizeConfigStrategy
是针对Android4.4及其以上版本的策略, AttributeStrategy
则是低版本的策略;
首先来看低版本的策略:
AttributeStrategy
AttributeStrategy.java
class AttributeStrategy implements LruPoolStrategy {
private final KeyPool keyPool = new KeyPool();//KeyPool
private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();//真正的LRU CACHE
@Override
public void put(Bitmap bitmap) {
//获取Key
final Key key = keyPool.get(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
//保存
groupedMap.put(key, bitmap);
}
@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
//获取Key
final Key key = keyPool.get(width, height, config);
//取出
return groupedMap.get(key);
}
//移除末尾的元素
@Override
public Bitmap removeLast() {
return groupedMap.removeLast();
}
}
复制代码
AttributeStrategy
重写接口定义的方法,其中 GroupedLinkedMap
是真正实现Lru逻辑的集合,值得注意的是 Key
的获取在 KeyPool
中, Key
作为对入参 int width, int height, Bitmap.Config config
的封装,也是Lru缓存的键;
AttributeStrategy.Key重写 equals()
方法和 hashCode()
方法,其中 hashCode()
是用来识别Lru内部 LinkedHashMap
中的 bucket
, equal()
是真正的对比;
AttributeStrategy.Key
{
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key other = (Key) o;
//判断相等的条件是width、heigth一一对应相等且config相等
return width == other.width && height == other.height && config == other.config;
}
return false;
}
@Override
public int hashCode() {
//hashCode的生成也是根据width、height、config.hashCode()
int result = width;
result = 31 * result + height;
result = 31 * result + (config != null ? config.hashCode() : 0);
return result;
}
}
复制代码
AttributeStrategy
这个策略的核心目的,就是在缓存的 Key
上面做对比,只有缓存中的Bitmap同时满足 width
、 height
、 config
相等才能命中;
SizeConfigStrategy
上面说了 AttributeStrategy
是面向低于Android4.4版本的Bitmap缓存策略, SizeConfigStrategy
则是面向高版本的,从文章开头的部分我们知道,高版本的 inBitmap
限制没有这么严格,至少在尺寸这一块是放开了,只有内存大小不小于需求就行;下面看看代码怎么实现的:
SizeConfigStrategy.java
public class SizeConfigStrategy implements LruPoolStrategy {
private static final int MAX_SIZE_MULTIPLE = 8;
private final KeyPool keyPool = new KeyPool();
private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
private final Map<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();
@Override
public void put(Bitmap bitmap) {
//获取bitmap的像素占用字节数
int size = Util.getBitmapByteSize(bitmap);
//获取key
Key key = keyPool.get(size, bitmap.getConfig());
//保存到LRU
groupedMap.put(key, bitmap);
//获取sizeConfig Map
NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig());
//保存键值对,键是字节数大小,值是总共有多少个
Integer current = sizes.get(key.size);
sizes.put(key.size, current == null ? 1 : current + 1);
}
public Bitmap get(int width, int height, Bitmap.Config config) {
//获取字节数
int size = Util.getBitmapByteSize(width, height, config);
//获取最优的key
Key bestKey = findBestKey(size, config);
//从LRU中获取
Bitmap result = groupedMap.get(bestKey);
if (result != null) {
//操作sizeConfig集合,做减1操作或者移除
decrementBitmapOfSize(bestKey.size, result);
//重新计算Bitmap宽高和config
result.reconfigure(width, height,
result.getConfig() != null ? result.getConfig() : Bitmap.Config.ARGB_8888);
}
return result;
}
//获取SizesForConfig Map
private NavigableMap<Integer, Integer> getSizesForConfig(Bitmap.Config config) {
NavigableMap<Integer, Integer> sizes = sortedSizes.get(config);
if (sizes == null) {
//treeMap
sizes = new TreeMap<>();
sortedSizes.put(config, sizes);
}
return sizes;
}
//Key的组成
static final class Key{
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key other = (Key) o;
return size == other.size
&& Util.bothNullOrEqual(config, other.config);
}
return false;
}
@Override
public int hashCode() {
int result = size;
result = 31 * result + (config != null ? config.hashCode() : 0);
return result;
}
}
}
}
复制代码
SizeConfigStrategy
和 AttributeStrategy
有很多相似之处,但是复杂的多,相同的是都是用 GroupedLinkedMap
作为Lru存储,不同之处是对于 Key
的获取以及多出一个辅助集合 NavigableMap
; Key
的获取已经不依赖 Width
和 Height
了,而是 size
,它是Bitmap占用的字节数, Key
的 hashCode()
和 equals()
依赖的是 size
和 config
;
SizeConfigStrategy
最关键的方法是 getBestKey()
,它的作用是获取最合适的Key;
SizeConfigStrategy.findBestKey()
//获取最适合的Key
private Key findBestKey(int size, Bitmap.Config config) {
//从pool里取出,肯定不为空
Key result = keyPool.get(size, config);
//获取匹配的Config,一般只有一个匹配
for (Bitmap.Config possibleConfig : getInConfigs(config)) {
//获取sizesForConfig
NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
//获取不比size小的可能缓存的size,ceiling方法相当于是数学上的进一法
Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
//命中的size不能大于目标size的8倍,可能是担心浪费内存;
if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
//`size`不相等或者`config`不相等,此处的判断等于是判断了`!Key.equals()`逻辑,这时候才降低维度获取相近的key
if (possibleSize != size
|| (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
//接受相近的缓存key,第一步创建的key放入队列
keyPool.offer(result);
//命中的key,他的size和目标相近但是肯定不完全一样
result = keyPool.get(possibleSize, possibleConfig);
}
break;
}
}
return result;
}
//获取能匹配上的config
private static Bitmap.Config[] getInConfigs(Bitmap.Config requested) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Bitmap.Config.RGBA_F16.equals(requested)) {
return RGBA_F16_IN_CONFIGS;
}
}
switch (requested) {
case ARGB_8888:
return ARGB_8888_IN_CONFIGS;
case RGB_565:
return RGB_565_IN_CONFIGS;
case ARGB_4444:
return ARGB_4444_IN_CONFIGS;
case ALPHA_8:
return ALPHA_8_IN_CONFIGS;
default:
return new Bitmap.Config[] { requested };
}
}
//8888能匹配8888,大于等于Android O 能匹配RGBA_F16
private static final Bitmap.Config[] ARGB_8888_IN_CONFIGS;
static {
Bitmap.Config[] result =
new Bitmap.Config[] {
Bitmap.Config.ARGB_8888,
null,
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
result = Arrays.copyOf(result, result.length + 1);
result[result.length - 1] = Config.RGBA_F16;
}
ARGB_8888_IN_CONFIGS = result;
}
//RGBA_F16_IN_CONFIGS和ARGB_8888_IN_CONFIGS一样
private static final Bitmap.Config[] RGBA_F16_IN_CONFIGS = ARGB_8888_IN_CONFIGS;
//565匹配565
private static final Bitmap.Config[] RGB_565_IN_CONFIGS =
new Bitmap.Config[] { Bitmap.Config.RGB_565 };
//4444匹配4444
private static final Bitmap.Config[] ARGB_4444_IN_CONFIGS =
new Bitmap.Config[] { Bitmap.Config.ARGB_4444 };
//ALPHA_8匹配ALPHA_8
private static final Bitmap.Config[] ALPHA_8_IN_CONFIGS =
new Bitmap.Config[] { Bitmap.Config.ALPHA_8 };
复制代码
getBestKey()
主要是通过 getInConfigs()
拿到能匹配到的 sizesForPossibleConfig
,通过辅助集合 NavigableMap
拿到size相近的 possibleSize
;
-
能匹配的第一个条件是possibleSize要小于等于
size * MAX_SIZE_MULTIPLE,MAX_SIZE_MULTIPLE默认是8;如果大于8对内存的利用率很低,没有必要强制匹配缓存; -
如果
sizesForPossibleConfig和possibleSize有一个不和目标相等,就可以复用,否则说明两者的key肯定相等(参考Key.equals()方法),两者相等没有必须再进行经纬度的匹配,直接返回就行;
辅助Map
在回头看看这个 NavigableMap
,通过调用 getSizesForConfig()
得到一个 TreeMap
,这个Map保存了每个缓存的Bitmap的size和相同size的count,在 getBestKey()
方法中调用 ceilingKey(size)
方法, TreeMap
默认会对key进行自然排序, ceilingKey(size)
函数的意义是返回一个和size最接近的不小于size的key,正好符合内存复用的价值;而
疑问:为啥要用Map来保存size和该size对应的count,count有何用?
SizeConfigStrategy中有这么一个方法: decrementBitmapOfSize()
;
private void decrementBitmapOfSize(Integer size, Bitmap removed) {
Bitmap.Config config = removed.getConfig();
NavigableMap<Integer, Integer> sizes = getSizesForConfig(config);
Integer current = sizes.get(size);
if (current == null) {
}
if (current == 1) {
//移除掉该条数据
sizes.remove(size);
} else {
//减1
sizes.put(size, current - 1);
}
}
复制代码
该方法调用时机是当 Bitmap
从是缓存池中取出或者移除时,执行内容:操作该map,被移除的Bitmap对应的size减1或者把当前key移除,只有移除掉,在 getBestKey()
调用 ceilingKey(size)
时才知道该size在缓存中是否存在;
GroupedLinkedMap
BitmapPool真正实现LruCache功能的是 GroupedLinkedMap
,这个类的功能跟 LinkedHashMap
很相似但又不同,相同的是都是利用链表来记住数据访问顺序,不同的是该类把相同key的value保存到一个数组中;
class GroupedLinkedMap<K extends Poolable, V> {
//LinkedEntry是存入Map的节点,同时是一个双向链表,同时还是持有一个数组
private static class LinkedEntry<K, V> {
@Synthetic final K key;//key
private List<V> values;//value数组
LinkedEntry<K, V> next;//链表下一个节点
LinkedEntry<K, V> prev;//链表上一个节点
//构造
LinkedEntry() {
this(null);
}
//构造
LinkedEntry(K key) {
next = prev = this;
this.key = key;
}
//移除数组的最后一个元素
@Nullable
public V removeLast() {
final int valueSize = size();
return valueSize > 0 ? values.remove(valueSize - 1) : null;
}
//数组的长度
public int size() {
return values != null ? values.size() : 0;
}
//添加到数组
public void add(V value) {
if (values == null) {
values = new ArrayList<>();
}
values.add(value);
}
}
//头节点
private final LinkedEntry<K, V> head = new LinkedEntry<>();
//存储key和entry的HashMap
private final Map<K, LinkedEntry<K, V>> keyToEntry = new HashMap<>();
//放入
public void put(K key, V value) {
LinkedEntry<K, V> entry = keyToEntry.get(key);
if (entry == null) {
//创建结点
entry = new LinkedEntry<>(key);
//放到链表尾部
makeTail(entry);
//放到hashMap中
keyToEntry.put(key, entry);
} else {
key.offer();//keyPool的操作
}
//放入entry数组中
entry.add(value);
}
//获取操作
public V get(K key) {
//从HashMap中查找
LinkedEntry<K, V> entry = keyToEntry.get(key);
if (entry == null) {
//如果不存在,创建结点,放到hashMap中
entry = new LinkedEntry<>(key);
keyToEntry.put(key, entry);
} else {
key.offer();//keyPool的操作
}
//放到链表头部
makeHead(entry);
return entry.removeLast();//返回数组的最后一个
}
//设成链表头(其实就是head的下一个)
private void makeHead(LinkedEntry<K, V> entry) {
removeEntry(entry);
entry.prev = head;
entry.next = head.next;
updateEntry(entry);
}
//设成链表尾(其实就是head的上一个)
private void makeTail(LinkedEntry<K, V> entry) {
//把自己从链表中移除
removeEntry(entry);
//绑定自身的关系
entry.prev = head.prev;
entry.next = head;
//绑定自身前后节点与自己的关系
updateEntry(entry);
}
//更新节点,把当前节点的上一个的next指向自己,下一个的perv指向自己,完成双向链表
private static <K, V> void updateEntry(LinkedEntry<K, V> entry) {
entry.next.prev = entry;
entry.prev.next = entry;
}
//删除当前节点,把自己上一个的next指向下一个,把自己下一个的prev指向上一个
private static <K, V> void removeEntry(LinkedEntry<K, V> entry) {
entry.prev.next = entry.next;
entry.next.prev = entry.prev;
}
//移除队尾的元素
public V removeLast() {
//获取队尾节点
LinkedEntry<K, V> last = head.prev;
//这一块的whild循环有意思
while (!last.equals(head)) {
//移除改节点数组的最后一个
V removed = last.removeLast();
if (removed != null) {//如果不为空直接返回
return removed;
} else {
//如果走到这里,说明last节点底下的数组为空,所以根本没有移除掉数据,第一件事就是干掉这个节点
removeEntry(last);
keyToEntry.remove(last.key);
last.key.offer();
}
//走到这一步还是因为last节点底下的数组为空,继续探寻它的上一个节点,直到能return出去为止
last = last.prev;
}
return null;
}
}
复制代码
GroupedLinkedMap
的代码量并不大,我在代码里做了比较详细的注释,如果有解释不当之处,还请留言交流;
BitmapPool缓存大小的计算
首先, BitmapPool
相对Glide对象是单例,在 GlideBuilder.build()
中创建,构造方法中需要传 maxSize
, maxSize
的计算规则是从 MemorySizeCalculator.getBitmapPoolSize()
获得;
GlideBuilder
//BitmapPool的创建
if (bitmapPool == null) {
//通过memorySizeCalculator获取bitmapPoolSize
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
复制代码
通过memorySizeCalculator获取 size
,
如果 size
等于0时,创建 BitmapPoolAdapter
,否则创建 LruBitmapPool
,什么时候情况下 size
等于0?我们还是看一些 memorySizeCalculator
的定义;
MemorySizeCalculator
//构造方法,Builder模式
MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
this.context = builder.context;
//得到arrayPoolSize
arrayPoolSize =
isLowMemoryDevice(builder.activityManager)
? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
: builder.arrayPoolSizeBytes;
//最大总共内存缓存size
int maxSize =
getMaxSize(
builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);
//屏幕宽度
int widthPixels = builder.screenDimensions.getWidthPixels();
//屏幕高度
int heightPixels = builder.screenDimensions.getHeightPixels();
//屏幕像素数,一个像素按照4字节算
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;
//目标bitmap池缓存Size
int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);
//目标内存缓存size
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
//可用内存size
int availableSize = maxSize - arrayPoolSize;
//如果算出来的size相加小于可用内存,直接赋值
if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
memoryCacheSize = targetMemoryCacheSize;
bitmapPoolSize = targetBitmapPoolSize;
} else {
//按比例重新分配memoryCacheSize和bitmapPoolSize
float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
}
}
复制代码
首先, MemorySizeCalculator
是Builder模式,主要的参数是在 MemorySizeCalculator.Builder
中生成,在 MemorySizeCalculator
构造方法中对 Glide
所有内存缓存的计算,这包括 arrayPool
缓存的大小, bitmapPool
缓存的大小, memoryCache
缓存的大小。
我们主要讨论BitmapPool的size计算,在构造方法中, targetBitmapPoolSize
的计算规则是屏幕尺寸的像素大小 * builder.bitmapPoolScreens
;
其次 MemorySizeCalculator
还会根据 builder
的配置得到最大的缓存容量 maxSize
;
最后,会重新计算 targetBitmapPoolSize
,使其不超出最大容量;
接下来看一下 MemorySizeCalculator.Builder
中对 bitmapPoolScreens
的计算:
MemorySizeCalculator.Builder
public static final class Builder {
static final int BITMAP_POOL_TARGET_SCREENS =
Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 4 : 1;
float bitmapPoolScreens = BITMAP_POOL_TARGET_SCREENS;
public Builder(Context context) {
this.context = context;
activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
screenDimensions =
new DisplayMetricsScreenDimensions(context.getResources().getDisplayMetrics());
// On Android O+ Bitmaps are allocated natively, ART is much more efficient at managing
// garbage and we rely heavily on HARDWARE Bitmaps, making Bitmap re-use much less important.
// We prefer to preserve RAM on these devices and take the small performance hit of not
// re-using Bitmaps and textures when loading very small images or generating thumbnails.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isLowMemoryDevice(activityManager)) {
//在Android O上面,低内存的手机bitmapPoolScreens设置为0
bitmapPoolScreens = 0;
}
}
}
复制代码
bitmapPoolScreens
的值在这三种情况:
- 如果设备小于Android O,取值 4 ;
- 如果设备大于等于Android O,低内存手机,取值 0 ;
- 如果设备大于等于Android O,非低内存手机,取值 1 ;
至于为啥要取值 0
,Glide的解释是Android O上面Bitmap内存的申请在native,ART虚拟机对垃圾回收非常高效,而且我们可以用设置Bitmap Config.HARDWARE
,所以对于Bitmap的缓存不是那么的重要。
总结
随着Android各个版本对Bitmap的不断进化,Glide也在不断的适应新的特性,高版本对Bitmap的复用也在不断地放松,或许有一天,我们不再为 Bitmap
内存问题所困扰,是不是就可以放弃这令人头大的Pool;
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Glide4.8源码拆解(二)核心加载流程
- 一套完整的 Tomcat 和 Jetty 的源码拆解
- Glide4.8源码拆解(三)Registry和数据转换流程
- Glide4.8源码拆解(四)Bitmap解析之"下采样"浅析
- Dubbo 源码解析之 SPI(一):拆解扩展类的加载过程
- 深入拆解 Java 虚拟机
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Don't Make Me Think
Steve Krug / New Riders Press / 18 August, 2005 / $35.00
Five years and more than 100,000 copies after it was first published, it's hard to imagine anyone working in Web design who hasn't read Steve Krug's "instant classic" on Web usability, but people are ......一起来看看 《Don't Make Me Think》 这本书的介绍吧!