Bitmap加载不当致使内存占用过大或者内存抖动,多是每一个Android工程师没法逃避的问题,如何合理的管理Bitmap内存,几乎成了各位必备的功课;好在Android官方早已经为咱们提供了解决思路;java
本章主要内容:android
Bitmap
内存管理和复用的简介LruBitmapPool
的基本分析AttributeStrategy
关键逻辑分析SizeConfigStrategy
关键逻辑分析GroupedLinkedMap
源码细致分析MemorySizeCalculator
对size的基本计算参考Managing Bitmap Memory文档:数组
Android对Bitmap的内存管理缓存
在Android2.2或更低的版本中,当发生垃圾回收时,线程都会暂停执行。这会致使延迟,下降程序性能。Android2.3增长了并发的垃圾回收机制,这意味着当图片对象再也不被引用时所占用的内存空间当即就会被回收。bash
在Android2.3.3或更低版本中,Bitmap的像素数据是存储在Native内存中,它和Bitmap对象自己是分开来的,Bitmap对象自己是存储在Java虚拟机堆内存中。存储在本地内存中的像素数据的释放是不可预知的,这样就有可能致使应用短暂的超过其内存限制而崩溃。Android3.0以后,Bitmap像素数据和它自己都存储在Java虚拟机的堆内存中。到了Android8.0及其之后,Bitmap有从新存储到Native堆中。并发
在Android2.3.3或更低版本中,调用recycle()
方法来尝试对Bitmap进行回收;less
在Android3.0或者更高的版本中,使用BitmapFactory.Options.inBitmap
来尝试对Bitmap进行复用,可是复用Bitmap是有条件限制的;ide
复用Bitmap的限制 参考官方文档函数
BitmapFactory.Options
设置;inSampleSize=1
的Bitmap才能够复用,inPreferredConfig须要设置成与目标Config一致;既然想复用Bitmap,就须要有集合来存储这些Bitmap,在Glide中,BitmapPool
就是干这事的。性能
BitmapPool
是Glide中对Bitmap复用进行统一管理的接口,原则上全部须要建立Bitmap的操做,都要通过它来进行获取,BitmapPool的类关系图以下: (缺个图)
BitmapPool
是一个接口,实现类有BitmapPoolAdapter
和LruBitmapPool
这两个;
BitmapPoolAdapter
是一个空壳子,根本没有作实际意义上的缓存操做;
LruBitmapPool
采用策略模式,它自身不处理具体逻辑,真正的逻辑在LruPoolStrategy
中;
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.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.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
相等才能命中;
上面说了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在缓存中是否存在;
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
相对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
的值在这三种状况:
至于为啥要取值0,Glide的解释是Android O上面Bitmap内存的申请在native,ART虚拟机对垃圾回收很是高效,并且咱们能够用设置BitmapConfig.HARDWARE
,因此对于Bitmap的缓存不是那么的重要。
随着Android各个版本对Bitmap的不断进化,Glide也在不断的适应新的特性,高版本对Bitmap的复用也在不断地放松,或许有一天,咱们再也不为Bitmap
内存问题所困扰,是否是就能够放弃这使人头大的Pool;