以下、URIパターンを「content://(authority)/(table)」と「content://(authority)/(table)/(_id)」のみに絞った形での「1コンテンツプロバイダに対し複数テーブル」の実装例である。 もしもう少し込み入ったパターン(content://(authority)/(table)/(_id)/(group)/(group_pos)」のような)にも対応しようとするともう少し工夫が要る。
UriMatcherは、「指定されたauthorityとpathのパターンに合致したら指定したIDを返却する」というマッピング作業を行なってくれる。 ワイルドカードとして「#」は任意の整数値、「*」は任意の文字列パターンと合致させることができる。
/**
 * アプリ内部のSQLiteを扱う為のコンテンツプロバイダ.
 *
 * @author Hideyuki Kojima
 */
public final class SQLiteProvider extends ContentProvider {
    /** URIのauthority. */
    private static final String AUTHORITY = "com.kojion.test.provider";
    /** SQLiteデータベースのファイル名. */
    private static final String SQLITE_FILENAME = "test.sqlite";
    /**
     * コンテンツプロバイダ利用者との「契約」を定義する列挙型定数クラス.
     *
     * @author Hideyuki Kojima
     */
    public enum Contract {
        /** TABLE1テーブル. */
        TABLE1(BaseColumns._ID, "title", "note"),
        /** TABLE2テーブル. */
        TABLE2(BaseColumns._ID, "title2", "note2");
        /**
         * コンストラクタ. カラムを定義する.
         *
         * @param columns 対象テーブルで定義されているカラム
         */
        Contract(final String...columns) {
            this.columns = Collections.unmodifiableList(Arrays.asList(columns));
        }
        /** テーブル名. enum定数を小文字にしたものとする. */
        private final String tableName = name().toLowerCase();
        /** テーブル全体のデータに対して処理をしに行く時のコード. */
        private final int allCode = ordinal() * 10;
        /** 対象IDのデータに対して処理をしに行く時のコード. */
        private final int byIdCode = ordinal() * 10 + 1;
        /** そのテーブル固有のCONTENT_URI表現. コンテンツリゾルバからこれを使用してアクセスする. */
        public final Uri contentUri = Uri.parse("content://" + AUTHORITY + "/" + tableName);
        /** MIMEタイプ(単数). */
        public final String mimeTypeForOne = "vnd.android.cursor.item/vnd.kojion." + tableName;
        /** MIMEタイプ(複数). */
        public final String mimeTypeForMany = "vnd.android.cursor.dir/vnd.kojion." + tableName;
        /** カラムのリスト. */
        public final List<String> columns;
    }
    /** 既定のUriパターンで絞り込む為のMatcher. */
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        sUriMatcher.addURI(AUTHORITY, Contract.TABLE1.tableName, Contract.TABLE1.allCode);
        sUriMatcher.addURI(AUTHORITY, Contract.TABLE1.tableName + "/#", Contract.TABLE1.byIdCode);
        sUriMatcher.addURI(AUTHORITY, Contract.TABLE2.tableName, Contract.TABLE2.allCode);
        sUriMatcher.addURI(AUTHORITY, Contract.TABLE2.tableName + "/#", Contract.TABLE2.byIdCode);
    }
    /** SQLiteOpenHelperのインスタンス. */
    private SQLite mOpenHelper;
    /**
     * コンテンツプロバイダが生成された際に呼ばれる.
     * SQLiteデータベースのファイルが存在しなかった場合は作成し, テーブルを作成する.
     * SQLiteデータベースのファイルが既に存在した場合は, それを開いて返す.
     * データベースのバージョンは, 管理しやすいようにアプリのversionCodeをそのまま使用するものとする.
     *
     * @return SQLiteデータベースが開けたかどうか
     */
    @Override
    public boolean onCreate() {
        final int version;
        try {
            version = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), 0).versionCode;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
            return false;
        }
        mOpenHelper = new SQLite(getContext(), SQLITE_FILENAME, null, version);
        return true;
    }
    /**
     * 単数または複数検索して返す.
     *
     * @return クエリ結果が格納されたCursor
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        checkUri(uri);
        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        Cursor cursor = db.query(uri.getPathSegments().get(0), projection, appendSelection(uri, selection),
                appendSelectionArgs(uri, selectionArgs), null, null, sortOrder);
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }
    /**
     * 対象テーブルにデータを挿入する. Uriに_idを付与してリクエストしても_idは無視する.
     *
     * @return 作成されたデータのUri表現
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        checkUri(uri);
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final long rowId = db.insertOrThrow(uri.getPathSegments().get(0), null, values);
        Uri returnUri = ContentUris.withAppendedId(uri, rowId);
        getContext().getContentResolver().notifyChange(returnUri, null);
        return returnUri;
    }
    /**
     * 対象テーブルの対象データを更新する. _idやselectionArgsの指定が無い場合は全件更新する.
     *
     * @return 更新件数
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        checkUri(uri);
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final int count = db.update(uri.getPathSegments().get(0), values, appendSelection(uri, selection), appendSelectionArgs(uri, selectionArgs));
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }
    /**
     * 対象テーブルの対象データを削除する. _idやselectionArgsの指定が無い場合は全件削除する.
     *
     * @return 削除件数
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        checkUri(uri);
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final int count = db.delete(uri.getPathSegments().get(0), appendSelection(uri, selection), appendSelectionArgs(uri, selectionArgs));
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }
    /**
     * 対象UriのMIMEタイプを返却する.
     *
     * @return 対象UriのMIMEタイプ
     * @throws IllegalArgumentException このコンテンツプロバイダで扱えるUriパターンでなかった場合
     */
    @Override
    public String getType(Uri uri) {
        final int code = sUriMatcher.match(uri);
        for (final Contract contract : Contract.values()) {
            if (code == contract.allCode) {
                return contract.mimeTypeForMany;
            } else if (code == contract.byIdCode) {
                return contract.mimeTypeForOne;
            }
        }
        throw new IllegalArgumentException("unknown uri : " + uri);
    }
    /**
     * 対象Uriがこのコンテンツプロバイダで扱えるUriパターンかどうかを検証する.
     *
     * @throws IllegalArgumentException このコンテンツプロバイダで扱えるUriパターンでなかった場合
     */
    private void checkUri(Uri uri) {
        final int code = sUriMatcher.match(uri);
        for (final Contract contract : Contract.values()) {
            if (code == contract.allCode) {
                return;
            } else if (code == contract.byIdCode) {
                return;
            }
        }
        throw new IllegalArgumentException("unknown uri : " + uri);
    }
    /**
     * Uriで_idの指定があった場合, selectionにそれを連結して返す.
     *
     * @param uri Uri
     * @param selection 絞り込み条件
     * @return _idの条件が連結されたselection
     */
    private String appendSelection(Uri uri, String selection) {
        List<String> pathSegments = uri.getPathSegments();
        if (pathSegments.size() == 1) {
            return selection;
        }
        return BaseColumns._ID + " = ?" + (selection == null ? "" : " AND (" + selection + ")");
    }
    /**
     * Uriで_idの指定があった場合, selectionArgsにそれを連結して返す.
     *
     * @param uri Uri
     * @param selectionArgs 絞り込み条件の引数
     * @return _idの条件が連結されたselectionArgs
     */
    private String[] appendSelectionArgs(Uri uri, String[] selectionArgs) {
        List<String> pathSegments = uri.getPathSegments();
        if (pathSegments.size() == 1) {
            return selectionArgs;
        }
        if (selectionArgs == null || selectionArgs.length == 0) {
            return new String[] {pathSegments.get(1)};
        }
        String[] returnArgs = new String[selectionArgs.length + 1];
        returnArgs[0] = pathSegments.get(1);
        System.arraycopy(selectionArgs, 0, returnArgs, 1, selectionArgs.length);
        return returnArgs;
    }
    /**
     * SQLiteを扱うクラス. ContentProvider内で使用されるに留まる.
     *
     * @author Hideyuki Kojima
     */
    private static class SQLite extends SQLiteOpenHelper {
        /**
         * コンストラクタ.
         *
         * @param context コンテキスト
         * @param name SQLiteファイル名
         * @param factory CursorFactory
         * @param version DBバージョン
         */
        public SQLite(Context context, String name, CursorFactory factory, int version) {
            super(context, name, factory, version);
        }
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.beginTransaction();
            try {
                db.execSQL("CREATE TABLE Table1 (_id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, note TEXT)");
                db.execSQL("CREATE TABLE Table2 (_id INTEGER PRIMARY KEY AUTOINCREMENT, title2 TEXT, note2 TEXT)");
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
            }
        }
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // TODO 本来は移行用のコードを書く.
        }
    }
}
AndroidManifest.xmlに以下のようにプロバイダの定義を追加。android:exported="false"とすることで、他アプリからのアクセスはできなくなる。
<provider android:name=".SQLiteProvider"
     android:authorities="com.kojion.test.provider"
     android:exported="false"/>
これで以下のように実行できる。下記、CursorLoaderを使用していない。CursorLoaderを使用した例はまた次回とする。
public final class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // table1テーブルにデータ投入.
        ContentValues values = new ContentValues();
        for (int i = 0; i < 3; i++) {
            values.clear();
            values.put(Contract.TABLE1.columns.get(1), "title" + i);
            values.put(Contract.TABLE1.columns.get(2), "note" + i);
            getContentResolver().insert(Contract.TABLE1.contentUri, values);
        }
        // table2テーブルにデータ投入.
        for (int i = 0; i < 3; i++) {
            values.clear();
            values.put(Contract.TABLE2.columns.get(1), "title" + i);
            values.put(Contract.TABLE2.columns.get(2), "note" + i);
            getContentResolver().insert(Contract.TABLE2.contentUri, values);
        }
        // table1テーブルの_idが1のデータを削除.
        getContentResolver().delete(ContentUris.withAppendedId(Contract.TABLE1.contentUri, 1), null, null);
        // table1テーブルのデータを全件検索. 表示.
        Cursor c = getContentResolver().query(Contract.TABLE1.contentUri, null, null, null, null);
        startManagingCursor(c);
        while (c.moveToNext()) {
            for (int i = 0; i < c.getColumnCount(); i++) {
                Log.d(getClass().getSimpleName(), c.getColumnName(i) + " : " + c.getString(i));
            }
        }
    }
}
0 件のコメント:
コメントを投稿