2012/07/25

Android:1つのDBに複数のContentProvider


データベースの構造が複雑化すると、これを扱うContentProviderも複雑になりがちです。
そんな時はContentProviderを分割するのも1つの方法です。

今回は複数のContentProviderで1つのデータベースを管理する方法について。

複雑な構造をもつデータベースを、たった1つのプロバイダが管理するのは大変です。
この場合、ContentProviderを分類毎や各機能毎に分割すれば複雑化を回避することができます。
また、ContentProviderの分割はセキュリティの向上にも役立ちます。

簡単な例を示します。

あなたのアプリが持つデータベースの状態が下記の場合...
  1. 1つのデータベースにデータタイプの異なるテーブルが複数存在する
  2. それぞれのテーブルは大きい、小さい、複雑、単純と様々
  3. いくつかのテーブルは外部アプリに提供する情報。いくつかのテーブルは非公開情報

複数のテーブルを管理するだけではなく、それぞれのデータの"公開"or"非公開"を意識する必要があります。
さらには、データの分類も多岐にわたるので、たった1つのContentProviderで管理するには無理がありそうです。


そこで、ContentProviderの分割を検討します。

まず、1つのContentProviderは1つのデータ分類を管理するようにします。
これでContentProviderの凝集度も上がります。

次に、公開or非公開のデータをどのように扱うかを考えます。
今回の場合は"データ分類B"が非公開情報です。
これは、データ分類BのContentProviderに対するアクセス権限を設定することで解決します。

対応後のイメージは下記になります。


それぞれのContentProviderを定義するマニフェストは次のようになります。
# あくまでサンプルです。実際には適切な権限と名前を割り当てます
...
<provider
    android:name="yuki.divcontentprovider.GlobalContentProvider"
    android:authorities="yuki.divcontentprovider.global" />
<provider
    android:name="yuki.divcontentprovider.LocalContentProvider"
    android:authorities="yuki.divcontentprovider.local"
    android:readPermission="yuki.divcontentprovider.dangerous" >
    <path-permission
        android:path="/ok"
        android:readPermission="yuki.divcontentprovider.normal" />
</provider>
...
GlobalContentProviderは公開情報(つまりデータ分類A)用のContentProviderです。
LocalContentProviderは非公開情報(つまりデータ分類B)用のContentProviderです。

LocalContentProviderはパスによって要求するパーミッションを変えています。
ContentProviderにはパスに対するパーミッション付与の機能が備わっているので制御も簡単です。

ソースコードに目を向けてみます。
ContentProviderを分割すると問題なのがデータベースの扱いです。
データベースの生成やアップグレードを担うSQLiteOpenHelperの設計には少し注意が必要です。

複数のContentProviderがいても、データベースは1つなのでSQLiteOpenHelperを継承するクラスはSingletonにします。
public class AppDataBase extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "hoge";
    private static final int DATABASE_VERSION = 1;

    private static AppDataBase sSingleton = null;

    public interface Table {
        public static final String TABLE1 = "TABLE_NAME";
    }

    public static synchronized AppDataBase getInstance(Context context) {
        if (sSingleton == null) {
            sSingleton = new AppDataBase(context);
        }
        return sSingleton;
    }

    public AppDataBase(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        ...
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        ...
    }
}
分割されたContentProvider達はAppDataBase.getInstance()メソッドを使用してインスタンスを取得します。

以上です。