コンテンツプロバイダーとは#
コンテンツプロバイダー(Content Provider)は、Android システムが提供する、独自のストレージ内のコンテンツを管理したり、他のアプリケーションのストレージ内のコンテンツにアクセスしたりする機能であり、異なるアプリケーション間でコンテンツを共有する機能を提供します。
基本プロトコル形式#
データを単純に渡す場合、基本的な形式を知っていれば十分です。形式:content://org.z5r.ta.provider/test
ここで、org.z5r.ta.provider はあなたのプロバイダーのパスで、test はプロバイダーがコンテンツを保存するテーブルの名前です。ライブラリを指定したい場合は、content://org.z5r.ta.provider/test/ データベース名と書くことができます。
コンテンツプロバイダーを実装する方法#
以下のすべてのコードは Kotlin で書かれています
- アプリケーション内に TestContentProvider という名前のクラスを新たに作成し、Android が提供する既存の ContentProvider を継承します。その後、必要なメソッドを実装するように求められます。コードは以下の通りです:
package org.z5r.ta.provider
import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
class TestContentProvider: ContentProvider() {
override fun onCreate(): Boolean {
TODO("Not yet implemented")
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
TODO("Not yet implemented")
}
override fun getType(uri: Uri): String? {
TODO("Not yet implemented")
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
TODO("Not yet implemented")
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
TODO("Not yet implemented")
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
TODO("Not yet implemented")
}
}
- 使用する変数と内部補助クラスを初期化します
class TestContentProvider: ContentProvider() {
companion object {
// ログを出力するためのTAG
const val TAG: String = "TestContentProvider"
// データベース名
const val DATABASE_NAME: String = "test.db"
// データベースバージョン(ここではあまり意味がない)
const val DATABASE_VERSION: Int = 1
// テーブル名
const val TEST_TABLE_NAME: String = "test"
// コンテンツプロバイダーのパス
private const val AUTHORITY: String = "org.z5r.ta.provider"
// パスのマッチング、一時的に空で、後でコンテンツパスが一致するかどうかを判断するために使用
var sUriMatcher: UriMatcher? = null
// Uriマッチング結果のコードマッピング(操作を区別するために使用)
const val TEST: Int = 1
// 同上
const val TEST_ID: Int = 2
// クエリのフィールドプロジェクション、呼び出し元が渡したクエリの列名をデータベースのフィールド名にマッピング
var testProjectionMap: HashMap<String, String>? = null
// データベース操作の補助
lateinit var dbHelper: DatabaseHelper
// プロバイダーUriを構築
val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/test")
// コンテンツタイプ、何を書いても良いが、標準に従って書く:vnd.android.cursor.dir/ここにカスタム
const val CONTENT_TYPE = "vnd.android.cursor.dir/vnd.z5r.test"
// 最初のフィールド、ID(必要に応じて書く、ここではキーと値のペアのテーブルを作成)
const val ID = "_id"
// 2番目のフィールド、result
const val RESULT = "result"
}
/**
* 内部クラス、データベースバージョン管理の補助クラス
*/
class DatabaseHelper internal constructor(context: Context?) :
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
// テーブルを作成
db.execSQL(((((("CREATE TABLE $TEST_TABLE_NAME").toString() + " (" + ID)
+ " LONG PRIMARY KEY," + RESULT) + " VARCHAR(255));")))
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
Log.w(TAG, "データベースをバージョン $oldVersion から $newVersion にアップグレードしています。これによりすべての古いデータが破壊されます")
// テーブルを削除
db.execSQL("DROP TABLE IF EXISTS $TEST_TABLE_NAME")
onCreate(db)
}
}
// その他のコード ...
}
- onCreate メソッドを実装します
override fun onCreate(): Boolean {
// データベース補助オブジェクトを初期化
dbHelper = DatabaseHelper(context)
// uriマッチング結果を不一致に初期化
sUriMatcher = UriMatcher(UriMatcher.NO_MATCH)
// マッチするUriを追加
// AUTHORITY:プロバイダーのパス
// TEST_TABLE_NAME:テーブル名
// TEST:Uriマッチング結果のコードマッピング
sUriMatcher?.addURI(AUTHORITY, TEST_TABLE_NAME, TEST)
sUriMatcher?.addURI(AUTHORITY, "$TEST_TABLE_NAME/#", TEST_ID)
// データベースフィールドプロジェクションを初期化
testProjectionMap = HashMap<String, String>()
testProjectionMap!![ID] = ID
testProjectionMap!![RESULT] = RESULT
// Trueを返す。例外処理が必要な場合は自分で書いてください
return true
}
- query メソッドを実装します
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
// 条件フィールドをコピー
var newSelection = selection
// データベース操作オブジェクトを構築
val qb = SQLiteQueryBuilder()
// テーブル名を指定
qb.tables = TEST_TABLE_NAME
// フィールドプロジェクションを指定
qb.projectionMap = testProjectionMap
// Uriマッチング結果のコードマッピングに基づいて対応する操作を選択
when (sUriMatcher?.match(uri)) {
TEST -> {}
TEST_ID -> {
// IDでデータをフィルタリング
newSelection = selection + "_id = " + uri.lastPathSegment
}
// サポートされていない操作
else -> throw IllegalArgumentException("不明なURI $uri")
}
// データベース補助クラスの読み取りオブジェクトをコピー
val db = dbHelper.readableDatabase
// クエリを開始
val c = qb.query(db, projection, newSelection, selectionArgs, null, null, sortOrder)
// コンテンツの変更リスナーを登録
c.setNotificationUri(context?.contentResolver, uri)
// 結果を返す
return c
}
- getType メソッドを実装します
override fun getType(uri: Uri): String {
// Uriをマッチング
when (sUriMatcher!!.match(uri)) {
// マッチ1の操作の場合はコンテンツタイプを返す
TEST -> return CONTENT_TYPE
else -> throw java.lang.IllegalArgumentException("不明なURI $uri")
}
}
- insert メソッドを実装します
override fun insert(uri: Uri, values: ContentValues?): Uri {
// Uriがマッチ1の操作かどうかを判断
if (sUriMatcher!!.match(uri) != TEST) {
throw java.lang.IllegalArgumentException("不明なURI $uri")
}
// 挿入する値を取得、空の場合は空のMapを返す
val newVal: ContentValues = if (values != null) {
ContentValues(values)
} else {
ContentValues()
}
// データベース補助操作の書き込みオブジェクトを取得
val db = dbHelper.writableDatabase
// データの挿入を開始
// テーブル名、フィールド、値
val rowId = db.insert(TEST_TABLE_NAME, RESULT, newVal)
// IDが0より大きいかどうかを判断(0より大きい場合は成功)
if (rowId > 0) {
// 新しいデータ行のuriを取得
val testUri = ContentUris.withAppendedId(CONTENT_URI, rowId)
// コンテンツプロバイダーに特定の行の内容が変更されたことを通知
context!!.contentResolver.notifyChange(testUri, null)
// uriを返す
return testUri
}
// 挿入に失敗
throw SQLException("URI $uri に行を挿入できませんでした")
}
- delete メソッドを実装します
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
// データベース補助操作の書き込みオブジェクトを取得
val db = dbHelper.writableDatabase
// 条件フィールドをコピー
var newSelection = selection
// Uri操作をマッチング
when (sUriMatcher?.match(uri)) {
TEST -> {}
TEST_ID -> {
// IDでデータをフィルタリング
newSelection = selection + "_id = " + uri.lastPathSegment
}
else -> throw IllegalArgumentException("不明なURI $uri")
}
// 削除を実行
val count = db.delete(TEST_TABLE_NAME, newSelection, selectionArgs)
// コンテンツプロバイダーに通知
context?.contentResolver?.notifyChange(uri, null)
// 削除した行数を返す
return count
}
- update メソッドを実装します
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
// データベース補助操作の書き込みオブジェクトを取得
val db = dbHelper.writableDatabase
// 更新された数
val count: Int
// Uri操作をマッチング
when (sUriMatcher!!.match(uri)) {
// 条件に基づいてデータを更新
TEST -> count = db.update(TEST_TABLE_NAME, values, selection, selectionArgs)
else -> throw java.lang.IllegalArgumentException("不明なURI $uri")
}
// コンテンツの更新を通知
context!!.contentResolver.notifyChange(uri, null)
// 影響を受けた行数を返す
return count
}
権限の登録と定義#
コンテンツプロバイダーは書くだけでは使用できず、登録と権限の設定が必要です。
<!-- AndroidManifest.xml -->
<application android:name="アプリケーションの内容はそのままに">
<provider
android:name=".provider.TestContentProvider(ここはプロバイダーのパッケージパス)"
android:authorities="org.z5r.ta.provider(ここはプロバイダーのパスで、上記Kotlinコード部分で定義したAUTHORITYと一致させる)"
android:permission="android.permission.READ_USER_DICTIONARY(権限、これだけ書けば大丈夫)"
android:exported="true(公開するかどうか、他のアプリがアクセスできないようにする場合はfalseに変更する必要があります)" >
<grant-uri-permission android:path="test(これは許可されたパスの「/」以降の部分)" />
</provider>
</application>
さらに多くの権限については、こちらを参照してください:https://developer.android.com/privacy-and-security/security-tips?hl=ja#ContentProviders
コンテンツプロバイダーの使用#
- 権限を申請します
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<!-- データを取得するために必要、書かないとアクセスが拒否される -->
<uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
<!-- データを取得するために必要、書かないと何もアクセスできない(プロバイダーが見つからない Failed to find provider info for xxxx) -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
<!-- その他のコード -->
</manifest>
- プロバイダーを使用します
val uri = Uri.parse("content://org.z5r.ta.provider/test")
val resObj = contentResolver.query(uri, null, null, null)
// ここは必ず書く必要がある、書かないと例外が発生する:android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 1
// 初期ポインタ位置を指定する必要がある
resObj?.moveToFirst()
val colIdx = resObj?.getColumnIndex("result")
val res = colIdx?.let { resObj.getStringOrNull(it) }
if (res != null) {
// ここで取得したデータを直接処理できます
}