実装はシンプルな気がする。
あるオブジェクトをキャッシュに追加したい
putというメソッドを使います。
public synchronized final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
putCount++;
size += safeSizeOf(key, value);
V previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
trimToSize(maxSize);
return previous;
}
safeSizeOfとは?
sizeOfメソッドの返り値を取得してそのまま返してるだけです。
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
sizeOfはprotectedなメソッドなので、拡張クラスで任意の値を設定することができる。
LruCache.javaのsizeOfは1を返すようになっています。
protected int sizeOf(K key, V value) {
return 1;
}
古いキャッシュを更新
putを見てると以下のような処理があります。
V previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
これは古いキャッシュの大きさをsizeから引いてます。
同じキーでもファイルサイズが違うこともあるので、それを考慮して更新してくれています。
trimToSizeとは?
trimToSizeメソッドではmaxSizeとsizeを比較してsizeがmaxSize以下になるように調整してくれる。
trimToSizeはmapを見守り、mapがメモリを確保し過ぎることを防いでくれる。
private void trimToSize(int maxSize) {
while (size > maxSize && !map.isEmpty()) {
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
if (toEvict == null) {
break; // map is empty; if size is not 0 then throw an error below
}
K key = toEvict.getKey();
V value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
// TODO: release the lock while calling this potentially slow user code
entryEvicted(key, value);
}
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
}
キャッシュに追加する上で注意したいこと
先ほどsafeSizeOfで見たように、sizeの変動はsizeOfに依存してます。
trimToSizeを正しく使うためにはsafeSizeではキャッシュしたい対象のオブジェクトの大きさを正確に返すことが求められます。
正しいサイズが分からないと、これ以上キャッシュ確保できないのに大きなオブジェクトを詰め込もうとしてメモリリークを起こしてしまう。。なんてことがあります。僕は起こしました。
なので、拡張クラスのsizeOfでvalueの大きさを計算して返すようにします。
以下のように書くとvalueのサイズを計算できます。
protected int sizeOf(K key, V value) {
return value.getByteCount();
}
もし、sizeOfでvalueの計算をしないと困ったことが起きます。
sizeOfメソッドをオーバライドせずに使ってみる。
すると、常にsafeSizeの返り値が1となりますね。
仮に4MBのBitmapをキャッシュしたとしても、safeSizeの返り値は1になってしまいますw
sizeがたった1しか増えてないので、maxSizeがよほど小さく設定されてない限りtrimToSizeでメモリの調整はしてくれません。
このままだとトリムが全く発動しないまま大きいファイルをどんどんキャッシュしてしまいます。
その結果、メモリ領域を食いつぶしてしまいOutOfMemoryエラーが起きてしまうのです!