Glide4.8源碼拆解(五)BitmapPool從入門到放棄

前言

Bitmap加載不當致使內存佔用過大或者內存抖動,多是每一個Android工程師沒法逃避的問題,如何合理的管理Bitmap內存,幾乎成了各位必備的功課;好在Android官方早已經爲咱們提供瞭解決思路;java

本章主要內容:android

  • Bitmap內存管理和複用的簡介
  • LruBitmapPool的基本分析
  • AttributeStrategy關鍵邏輯分析
  • SizeConfigStrategy關鍵邏輯分析
  • GroupedLinkedMap源碼細緻分析
  • MemorySizeCalculator對size的基本計算

Bitmap內存管理和複用

參考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的限制 參考官方文檔函數

  • 首先Bitmap必須是mutable,能夠經過BitmapFactory.Options設置;
  • 在Android4.4版本以前,僅支持jpg、png圖片,且size大小同樣,且 inSampleSize=1的Bitmap才能夠複用,inPreferredConfig須要設置成與目標Config一致;
  • 在Android4.4以後的版本,只要內存大小不小於需求的Bitmap均可以複用.
  • Bitmap.Config.HARDWARE不須要複用;

既然想複用Bitmap,就須要有集合來存儲這些Bitmap,在Glide中,BitmapPool就是幹這事的。性能

Glide中的BitmapPool

BitmapPool是Glide中對Bitmap複用進行統一管理的接口,原則上全部須要建立Bitmap的操做,都要通過它來進行獲取,BitmapPool的類關係圖以下: (缺個圖)

  • BitmapPool是一個接口,實現類有BitmapPoolAdapterLruBitmapPool這兩個;

  • 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也是一個抽象的策略接口,真正策略的實現類是SizeConfigStrategyAttributeStrategy;

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中的bucketequal()是真正的對比;

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同時知足widthheightconfig相等才能命中;

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;
    }
  }
  }
}
複製代碼

SizeConfigStrategyAttributeStrategy有不少類似之處,可是複雜的多,相同的是都是用GroupedLinkedMap做爲Lru存儲,不一樣之處是對於Key的獲取以及多出一個輔助集合NavigableMapKey的獲取已經不依賴WidthHeight了,而是size,它是Bitmap佔用的字節數,KeyhashCode()equals()依賴的是sizeconfig;

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對內存的利用率很低,沒有必要強制匹配緩存;

  • 若是sizesForPossibleConfigpossibleSize有一個不和目標相等,就能夠複用,不然說明二者的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()中建立,構造方法中須要傳maxSizemaxSize的計算規則是從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虛擬機對垃圾回收很是高效,並且咱們能夠用設置BitmapConfig.HARDWARE,因此對於Bitmap的緩存不是那麼的重要。

總結

隨着Android各個版本對Bitmap的不斷進化,Glide也在不斷的適應新的特性,高版本對Bitmap的複用也在不斷地放鬆,或許有一天,咱們再也不爲Bitmap內存問題所困擾,是否是就能夠放棄這使人頭大的Pool;

相關文章
相關標籤/搜索