banner
ZetoHkr

ZetoHkr

摸🐟从未停止,努力从未开始
github

Android ContentProvider コンテンツプロバイダーを作成する

コンテンツプロバイダーとは#

コンテンツプロバイダー(Content Provider)は、Android システムが提供する、独自のストレージ内のコンテンツを管理したり、他のアプリケーションのストレージ内のコンテンツにアクセスしたりする機能であり、異なるアプリケーション間でコンテンツを共有する機能を提供します。

基本プロトコル形式#

データを単純に渡す場合、基本的な形式を知っていれば十分です。形式:content://org.z5r.ta.provider/test

ここで、org.z5r.ta.provider はあなたのプロバイダーのパスで、test はプロバイダーがコンテンツを保存するテーブルの名前です。ライブラリを指定したい場合は、content://org.z5r.ta.provider/test/ データベース名と書くことができます。

参考:https://developer.android.com/guide/topics/providers/content-provider-creating?hl=ja#content-uri-patterns

コンテンツプロバイダーを実装する方法#

以下のすべてのコードは Kotlin で書かれています

  1. アプリケーション内に 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")
		}
	}
  1. 使用する変数と内部補助クラスを初期化します
	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)
			}
		}
		
		// その他のコード ...
	}
  1. 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
		}
  1. 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
	}
  1. getType メソッドを実装します
		override fun getType(uri: Uri): String {
			// Uriをマッチング
			when (sUriMatcher!!.match(uri)) {
				// マッチ1の操作の場合はコンテンツタイプを返す
				TEST -> return CONTENT_TYPE
				else -> throw java.lang.IllegalArgumentException("不明なURI $uri")
			}
		}
  1. 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 に行を挿入できませんでした")
		}
  1. 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
		}
  1. 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

コンテンツプロバイダーの使用#

  1. 権限を申請します
	<!-- 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>
  1. プロバイダーを使用します
			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) {
				// ここで取得したデータを直接処理できます
			}
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。