- Android開発初心者向け - Content Providerの使い方

 AndroidでDB関係を実装するときによくわからなかったので勉強したことをまとめます。
自分はjavaは触ったことがあるがAndroidは初めてという人なので結構基本的なことから書いていると思います。
そんなこと知ってるわ！って人は最初の方を飛ばしてください。


## Content Providerとは何か
AndroidでDBにデータを取得/保存する時に利用するクラスです。
このクラスを使えば、あるアプリが持っているデータを他のアプリも使用できるようになったりします。もともと用意されているものとしては、着信ログや電話帳、ブラウザのブックマークなどがあります。
異なるアプリで同じDBを触ることができるというあたり、様々利用できそうで夢が膨らみます。
これらのデータを利用するためにはgetContentResolver().query()メソッドで、目的のデータを示すURIを指定してCursorインスタンスを取得する必要があるそうです。

## URI
URIとは目的のデータを示すために使われるものです。PCであればファイルをディレクトリで指定すると思いますが、AndoroidではURIで指定するといった感じだと思います(適当)。

Content ProviderのURIは3つの要素から構成されています。

(例) content://packagename.providername/sample

1. prefix："content://"
2. authority(パッケージ名+任意のprovider名)："packagename.providername"
3. table："sample"

authorityは世界で唯一の名前になるのが好ましいらしく、パッケージ名+provider名でつけるのが良いらしいです。

## SQLiteOpenHelper, BaseColumns

Content Providerを作成する前にDBが無いとデータを取得も保存もそもそもできないので、データ提供元となるDBを作成します。
これにはSQLiteOpenHelperクラスを継承したクラスを利用します。
DBを作成するときはDBの名前やテーブルの名前、カラムなどをBaseColumnsをimplementsしたクラスにまとめて書くのが良いと思います。
ここでは例としてユーザー情報を扱うDBを考えます。名前とメールアドレスだけのシンプルなDBです。

```java:Columns
public class UserColumns implements BaseColumns{
	// URIパス
	public static final String PATH = "user";
	// コンテントURI
	public static final Uri CONTENT_URI = Uri.parse("content://" + MyContentProvider.AUTHORITY + "/" + PATH);
	// テーブル指定コンテントタイプ
	public static final String CONTENT_TYPE = "vnd.android.cursor.item/vnd.example.users";
	// レコード個別指定コンテントタイプ
	public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.dir/vnd.example.users";

	// テーブル名
	public static final String TABLE = "users";
	// カラム 名前
	public static final String NAME = "name";
	// カラム メールアドレス
	public static final String EMAIL = "email";

	// コンストラクタ(呼ばれることは無い)
	private RecordColumns() {}
}
```

```java:SQLiteOpenHelper
public class UserDBHelper extends SQLiteOpenHelper{
	// DB名とバージョン
	private static final String DB_NAME = "database";
	private static final int DB_VERSION = 1;

	// コンストラクタ
	public UserDBHelper(Context context){
		super(context, DB_NAME, null, DB_VERSION);
	}

	@Override
	public void onCreate(SQLiteDatabase sqLiteDatabase) {
		// データベースのテーブルを作成する
		sqLiteDatabase.execSQL(/* TODO */);
	}

	@Override
	public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i2) {
		// DBに変更があったときに呼ばれる
	}
}
```


## Content Provider
DBの準備ができたので、いよいよContent Providerの説明に入ります。

Content Providerを継承したクラスは以下のメソッドを実装する必要があります。
- onCreate() .. プロバイダの初期化
- query() .. データを取得。戻り値はCursor
- insert() .. 行を追加。戻り値は新たに追加された行のURI
- update() .. 各行を更新。戻り値は更新した行の数
- delete() .. 行を削除。戻り値は削除した行の数
- getType() .. ContentURIに対応するMIMEタイプを返す

だいたいの形は以下のようになります。

基本的には受け取ったUriをUriMatcherを用いてマッチングし、マッチングに応じた処理をswitch文で記述することになります。UriMatcherクラスはコンテンツプロバイダのURIのマッチングをサポートするユーティリティクラスです。


```java:ContentProvider
public class UserContentProvider extends ContentProider{
	// Authority
	public static final String AUTHORITY = "com.example";

	// USERS テーブル URI ID
	private static final int USERS = 1;
	// USERS テーブル 個別 URI ID
	private static final int USER_ID = 2;
	
	// 利用者がメソッドを呼び出したURIに対応する処理を判定処理に使用します
    private static UriMatcher sUriMatcher;
    static {
		sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
		sUriMatcher.addURI(AUTHORITY, UserColumns.PATH, USERS);
		sUriMatcher.addURI(AUTHORITY, UserColumns.PATH + "/#", USER_ID);
    }

	// DBHelperのインスタンス
	private UserDBHelper mDBHelper;

	// コンテンツプロバイダの作成
	@Override
	public boolean onCreate() {
		mDBHelper = new UserDBHelper(getContext());
		return true;
	}

	// query実行
	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
		switch (sUriMatcher.match(uri)) {
			case USERS:
			case USER_ID:
				queryBuilder.setTables(UserColumns.TABLE);
				break;
			default:
				throw new IllegalArgumentException("Unknown URI " + uri);
		}

		SQLiteDatabase db = mDBHelper.getReadableDatabase();
		Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
		return cursor;
	}

	// insert実行
	@Override
	public Uri insert(Uri uri, ContentValues values) {
		String insertTable;
		Uri contentUri;
		switch (sUriMatcher.match(uri)) {
			case USERS:
				insertTable = UserColumns.TABLE;
				contentUri = UserColumns.CONTENT_URI;
				break;
			default:
				throw new IllegalArgumentException("Unknown URI " + uri);
		}

		SQLiteDatabase db = mDBHelper.getWritableDatabase();
		long rowId = db.insert(insertTable, null, values);
		if (rowId > 0) {
			Uri returnUri = ContentUris.withAppendedId(contentUri, rowId);
			getContext().getContentResolver().notifyChange(returnUri, null);
			return returnUri;
		} else {
			throw new IllegalArgumentException("Failed to insert row into " + uri);
		}
	}

	// update実行
	@Override
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		SQLiteDatabase db = mDBHelper.getWritableDatabase();
		int count;
		String id = uri.getPathSegments().get(1);
		count = db.update(RecordColumns.TABLE, values, selection, selectionArgs);
		getContext().getContentResolver().notifyChange(uri, null);
		return count;
	}

	// delete実行
	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		SQLiteDatabase db = mDBHelper.getWritableDatabase();
		int count;
		switch (sUriMatcher.match(uri)) {
			case USERS:
			case USER_ID:
				count = db.delete(RecordColumns.TABLE, selection, selectionArgs);
				break;
			default:
				throw new IllegalArgumentException("Unknown URI " + uri);
		}
		getContext().getContentResolver().notifyChange(uri, null);
		return count;
	}

	// コンテントタイプ取得
	@Override
	public String getType(Uri uri) {
		switch(sUriMatcher.match(uri)) {
			case USERS:
				return UserColumns.CONTENT_TYPE;
			case USER_ID:
				return UserColumns.CONTENT_ITEM_TYPE;
			default:
				throw new IllegalArgumentException("Unknown URI " + uri);
		}
	}
}	
```


また、Content Providerを利用するためにAndroidManifestに定義を追加する必要があります。

```AndroidManifest
<provider
	android:authorities="com.example" // URIの定義で定義したauthorities
	android:name="UserContentProvider" // ContentProviderを継承したクラスの名前
	android:exported="false"> // 他アプリへの公開/非公開設定。デフォルトはtrue
</provider>
```


## Content Resolverについて
これでContent Providerを利用する準備ができたのでデータを取得したり保存したりしたいと思います。
Content Providerを介してデータを取得するためにはContent Resolverインスタンスを利用します。

```java:MainActibvity
public final class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // テーブルにデータ投入.
        ContentValues values = new ContentValues();
        for (int i = 0; i < 3; i++) {
            values.clear();
            values.put(UserColumns.NAME, "name" + i);
            values.put(UserColumns.EMAIL, "email" + i);
            getContentResolver().insert(UserColumns.CONTENT_URI, values);
        }

        // テーブルのデータを全件検索. 表示.
        Cursor c = getContentResolver().query(UserColumns.CONTENT_URI, 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));
            }
        }
    }
}
```