ギークに憧れて

English: hotchemi.tumblr.com

Key-Valueデータをresourceで定義する

Androidアプリの初期データとして、Key-Value型で値を持ちたいというのはよくある事だと思います。

単純な文字列や配列であればContext#getStringResources#getStringArrayなどで取得すれば良い話ですが、Key-Value形式は中々良いやり方がなく以下の様にシングルトンなMapを作ってメモリに展開している実装をよく見ます。

public final class DatatMap {
    private DataMap() {
    }

    public static final Map<String, Integer> ICON_MAP = new HashMap<String, Integer>() {
        {
            put("100", R.drawable.icon_100);
            put("200", R.drawable.icon_200);
            put("300", R.drawable.icon_300);
            put("400", R.drawable.icon_400);
        }
    };
}

特に問題はなさそうに見えますが、コードとデータはなるべくなら分離したい所です。 調べた所、以下の様にすれば簡単に実装する事ができます。

  1. res/raw/に以下のフォーマットでデータを作成します。
<?xml version="1.0" encoding="utf-8"?>
<extras xmlns:android="http://schemas.android.com/apk/res/android">

    <extra
        android:name="mega"
        android:value="1000000" />

    <extra
        android:name="kilo"
        android:value="1000" />

</extras>
  1. 以下のクラスを実装しResourcesAdditions.getResourcesExtras(getResources(), R.raw.extras)といった形で使用します。
public final class ResourcesAdditions {
 
    public static final String TAG_EXTRAS = "extras";
 
    private ResourcesAdditions() {
        // No instance
    }
 
    public static Bundle getResourcesExtras(Resources res, int resId) throws Resources.NotFoundException {
        return getResourcesExtras(res, TAG_EXTRAS, resId);
    }
 
    public static Bundle getResourcesExtras(Resources res, String rootTag,
                                            int resId) throws Resources.NotFoundException {
        XmlResourceParser parser = res.getXml(resId);
 
        int type;
        try {
            while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
                // Empty loop
            }
            if (type != XmlPullParser.START_TAG) {
                throw new XmlPullParserException("No start tag found");
            }
            if (!parser.getName().equals(rootTag)) {
                throw new XmlPullParserException("Unknown start tag. Should be '" + rootTag + "'");
            }
 
            final Bundle extras = new Bundle();
            res.parseBundleExtras(parser, extras);
 
            return extras;
 
        } catch (Exception e) {
            final Resources.NotFoundException nfe = new Resources.NotFoundException();
            nfe.initCause(e);
            throw nfe;
        }
    }
 
}

使い方は以下の様になります。

Bundle bundle = ResourcesAdditions.getResourcesExtras(mResources, R.xml.extras);
assertEquals(4, bundle.size());
assertEquals(10, bundle.getInt("deca"));
assertEquals(100, bundle.getInt("hecto"));
assertEquals(1000, bundle.getInt("kilo"));
assertEquals(1000000, bundle.getInt("mega"));

この実装のメリットは読み込みの実装がAndroidに依存した形になっているので軽量な事、デメリットは大きなデータになるとGsonのdeserialization程には効率的に処理できないという点です。 軽量なデータセットであれば上記実装を検討しても良さそうな気がします。

参考

偉そうに書きましたが以下の記事の和訳となります。

Lightweight key-value pairs for Android bundled reso...