プログラマってこんなかんじ??

アプリ作ったり歌ったりしてます

LruCache 使ってみた(android.util.LruCache)

GoogleI/O 2012 のセッションで LruCache の話が出てたので使ってみた。


Doing More With Less: Being a Good Android Citizen
pdf(右側の 「Session presentation」 リンク) の p.8 くらいから。


LruCache(android.util.LruCache)

  1. Least Recently Used アルゴリズム(wiki) を用いたキャッシュ管理クラス
  2. 中身は LinkedHashMap
  3. インスタンス生成時に保持キャッシュサイズの最大値を指定
  4. 要素がputされたときに↑を超えていたら既存要素を自動的に破棄
  5. API Level 12
  6. support library(v4) にもあるから APILevel が ↑以下でも使える


使い方は基本的にこんなかんじ。


実装方法

  1. LruCache の拡張クラスを作成
  2. コンストラクタ保持キャッシュサイズ最大値 を定義(↓のsizeOfの合計値上限)
  3. LruCache#sizeOf を override して 各要素のサイズ計算方法 を定義
  4. LruCache#entryRemoved を override して 各要素の破棄処理 を定義


サンプルコード。

private void testLruCache() {
    // cache要素最大数 10個
    LruCache lruCache = new BitmapLruCache(10);

    // cache要素追加
    String key = ~;
    Bitmap val = ~;
    lruCache.put(key, val);

    // cache要素取得
    Bitmap cache = lruCache.get(key);

    // cache解放処理(全要素)
    lruCache.evictAll();
}

private static class BitmapLruCache extends LruCache<String, Bitmap> {

    public BitmapLruCache(int maxSize) {
        super(maxSize);
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        // default では 1 を返却
        return super.sizeOf(key, value);
    }

    @Override
    protected void entryRemoved(boolean evicted, String key, 
        Bitmap oldValue, Bitmap newValue) {

        // cache解放処理
        if (!oldValue.isRecycled()) {
            oldValue.recycle();
            oldValue = null;
        }
    }
}


この実装の場合、こんな感じになる。

  • 保持キャッシュ(Bitmap)最大数は 10個
  • 保持キャッシュ数が 10個 を超えると要素が解放されていく(Bitmap#recycle)

ビルド時にエラー発生(Error generating final archive)

また出た。
毎年恒例のこのエラー。
対応策は 「debug.keystore」 の削除。
去年の自分のエントリにも書いてた。


Error generating final archive(Debug Certificate expired on~) が発生する


このエラーが出たということはー
自分の場合は 「Androidアプリ開発始めてから丸2年経過」 ということか。


毎年このエラーが出るたびにイロイロ考えさせられそうです。
自分の1年を振り返るいいタイミング?

画面回転してもFragment再生成しないでレイアウト変更したい+取得したデータ使い回したい

お題

  • 画面回転させて、縦/横で別のレイアウトを表示する
    • 縦画面のときは ListView を表示
    • 横画面のときは GridView を表示(1行2列)
  • ただし、一度取得したデータは使いまわしたい


縦画面
f:id:dai4649:20120704014336p:plain

横画面
f:id:dai4649:20120704014417p:plain


実装のポイント

  • Activity
    • コンフィグ変更のたびに毎回 再生成
  • Fragment
    • Fragment#setRetainInstance(true) を設定し、Activity 再生成時に Fragment は再生成されないようにする(Fragment#onCreate/onDestroy を抑止)
    • Activity 再生成のたびに走る Fragment#onCreateView でレイアウト再読込させる
  • データ取得
    • 一度だけ(今回の例では一度だけ呼び出される Fragment#onCreate 内で)
  • レイアウト
    • res/layout, res/layout-land に 縦/横用レイアウトxml を格納しておく
    • レイアウトxml 内に 同一id で ListView/GridView を定義しておく


メイン部分のコード。(コード全体はこちら)

Activity再生成時のデータの保存・復元(Fragment#setRetainInstance)

  1. ネットワークからデータ取得(画像とか)
  2. データ取得完了したら画面にデータ反映


こういうパターンってよくありますよね。このときに画面回転等のコンフィグ変更(縦横切替とか)が行われると 「Activity のインスタンスが破棄->再生成」 されるので、何も考えてないと保存データも一緒に破棄されてしまいます。なので、内部に保存しているデータを 「保存/復元」 する必要があります。


自分、コンフィグ変更で Activity再生成 とかされると正直めんどくさいので、基本的に "android:configChanges" の属性指定で Activity再生成抑止 して データの保存/復元 とか考えない方向でいいんじゃね。とか思ってたんです(すみません)。


でもよくよく調べてたら Activity再生成 を回避できないケース がありまして。例えば、「フォント切替」。


こいつは configChanges のパラメータにないので、厳密にいうとやっぱり Activity再生成 は避けて通れない。悲しいです。というわけで、Activity再生成時の内部データの保存/復元方法 はどうやるのが正解なのだろう、と調べてみると 2.x、3.x〜系 でだいぶ変わっていました。自分への備忘録の意味も込めてまとめてみます。


Activity再生成時の保存/復元処理

2.x系(API Level 10まで)
  • サイズ小さめのデータの場合(serializeされる)
    • Activity#onSaveInstanceState で保存処理
    • Activity#onCreate、onRestoreInstanceState で復元処理
  • サイズ大きめのデータの場合(serializeされない)
    • Activity#onRetainNonConfigurationInstance で保存処理
    • 再生成後に任意のタイミングで Activity#getLastNonConfigurationInstance 呼び出して復元処理
3.x、4.x系(API Level 11〜)
  • サイズ小さめのデータの場合(serializeされる) ※Activity単位(2.x系と一緒)
    • Activity#onSaveInstanceState で保存処理
    • Activity#onCreate、onRestoreInstanceState で復元処理
  • サイズ小さめのデータの場合(serializeされる) ※Fragment単位
    • Fragment#onSaveInstanceState で保存処理
    • Fragment#onCreate で復元処理
  • サイズ大きめのデータの場合
    • Fragment#setRetainInstance(true) を指定して Fragment が破棄されないようにし、そこへデータ保存
    • 任意のタイミングで↑の Fragment からデータ取得して復元処理

サイズ小さめデータの 保存/復元 を行う際、Activity#onSaveInstanceState〜 については 2.x系 と特に変わらないのですが、Fragment単位でも同様のことができるようになってます。


サイズ大きめデータの 保存/復元 を行う際、3.x系〜は Activity#onRetainNonConfigurationInstance が非推奨になってます。代わりに Fragment#setRetainInstance(true) を使用する方法が推奨されています。


Fragment#setRetainInstance(true) を呼び出すことでどうなるかというと、

  • Activity再生成時に Fragment#onDestroy、onCreate が呼ばれなくなる。
  • Activity再生成時には Fragment#onDetach、onAttach が呼び出されるだけ。
  • Fragmentインスタンス破棄されないので Fragmentインスタンス内にデータをそのまま保持しておくことが可能。

ということみたいです。つまり、でかいデータについては、

Fragmentインスタンス自体を破棄されないようにしとく。
で、Fragmentインスタンス内部でそのまま保持っておく。

ってことですかね!


参考