わかってしまえば簡単なことなんですが、StackOverflowでも、sizeOfをoverrideしなかったらどうなるの?!って言っている人が居た ので、ああ僕以外にも初歩的なことで悩んでる人いるんだと思って一応この記事を書きます。
LruCache(android.util.LruCache)とは
Androidでメモリキャッシュを行う際に使用するクラスです。例えばBitmapをメモリキャッシュする際、こんな風にサイズを指定して使用します。
mMemCache = new LruCache<String, Bitmap>(10);
この状態だと、キャッシュに入れるBitmapは10個までとなります。sizeOfメソッドがデフォルトの実装になっており、返される値が必ず1だからです。
sizeOfの値
LruCacheのsizeOfメソッドは、このキャッシュにデータが追加される際に、追加されようとしているデータのサイズを逐一計測し、総サイズを積んでいくメソッドです。デフォルトでは固定で1を返すようになっています。
上記の例でいうと、データが追加される度に1を積んでいき、コンストラクタで指定してあるmaxサイズの"10"まで積まれると、「もう入れませーん」という悲鳴をあげます。
コンストラクタで指定する値
コンストラクタで指定するのは、あくまでmaxのサイズです。単位は、個数かもしれないし、byteかもしれないし、わかりませんが、とにかく最大のサイズです。中身にどれだけ保持できるかは、sizeOfとの兼ね合いによって決まってきます。ここで指定したサイズのメモリが最初にどかっと確保されてしまうわけではないのです。sizeOfで積んでいった値が指定されたサイズを超えた際に悲鳴をあげるための設定値となります。
具体的に、どう実装するか
1.キャッシュに10個しかオブジェクト保持させなくてイイ、っていう場合
コンストラクタに10を渡し、sizeOfはデフォルトのまま1を返す、とすればよいことになります。sizeOfについては何もしなくて良いです。
追加される度に、サイズを図った結果として1が返され、その値が積まれていきます。積まれた値が10になったとき、つまり、キャッシュにアイテムが10個追加された時、もうこれ以上は無理、と意思表示されます。
2.追加されるオブジェクトのメモリ量を都度測りつつ、指定したmaxメモリ量まではキャッシュできるようにしたい、っていう場合
int maxMemory = Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mMemCache = new LruCache<String, Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount()
}
}
例えば、アプリごとに割り当てられたメモリサイズを取得し、余裕を持ってその1/8のメモリまではキャッシュに使用できるようにする、っていうLruCacheの実装例でよくあるパターンです。こういった場合は、sizeOfをoverrideして、
return bitmap.getByteCount()
と書けばOKです。
キャッシュにビットマップが追加される度に、サイズを図った結果として適当な値が返され(700byte程度の画像をキャッシュしたら、3KBという計測結果が帰ってきました)、その値が積まれていきます。積まれた値が指定サイズに達したとき、もうこれ以上は無理、と意思表示されます。この時、キャッシュされている個数は、中身のビットマップのサイズによって動的に変化します。
まとめ
ネットを見ているとRuntimeから取得した値を/1024してKBにしたり、/1024/1024してMBにしてからコンストラクタに渡しているサンプルをよく見かけます。「コンストラクタに指定する値=メモリ確保量」だと勝手に勘違いをして混乱してしまいましたが、コンストラクタに指定するのは初期確保量ではありませんでした。 最大値(コンストラクタに渡す値)と計測値(sizeOfで返ってくる値)の単位さえ合っていれば、MBで計算しようがKBで計算しようが、byteで計算しようが個数で計算しようが、適切なキャッシュサイズを設計することができます。