コンテンツプロバイダを使ってデータの保存を試してみました。
コンテンツプロバイダとはAndroidの用意しているデータ保存の仕組みで、他アプリとのデータ共有を簡単にできるようになります。
今回はこれを使ってデータの保存をしてみようと思います。
まずはプロジェクトを作成します。
次は自前のContentProviderクラスを作成します。
onCreate、query、insert、update、delete、getTypeは必須のメソッドになります。
class MyProvider: ContentProvider() { override fun onCreate(): Boolean { return true } override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? { return null } override fun insert(uri: Uri, values: ContentValues?): Uri? { return null } override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int { return 0 } override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int { return 0 } override fun getType(uri: Uri): String? { return null } }
次はAndroidManifest.xmlのapplicationの中に下のような宣言をします。
こうする事でcom.exampleのURLの時にMyProviderが呼び出されます。
<provider android:name=".MyProvider" android:authorities="com.example" />
あとはMainActivityなどで下のようにcontentResolverを使ってinsertを呼ぶとMyProviderのinsertが呼ばれるようになります。
val uri = Uri.parse("content://com.example/") contentResolver.insert(uri, ContentValues().apply { put("name", "name1") })
次は実際にデータの取得・保存・編集・削除をできるようにします。
今回はSQLiteを使ったデータの保存を行います。
まずは下のようにSQLiteにアクセスするためのクラスを作成します。
onCreate内でTableの作成をしています。
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "my_database.db", null, 1) { override fun onCreate(db: SQLiteDatabase?) { db?.execSQL("create table my_database (_id integer primary key autoincrement, name text not null);") } override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { } }
次はContentProviderのonCreate、query、insertを実装します。
class MyProvider: ContentProvider() { lateinit var helper: MyDbHelper override fun onCreate(): Boolean { helper = MyDbHelper(context!!) return true } override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? { return helper.readableDatabase.query("my_database", arrayOf("name"), selection, selectionArgs, null, null, null) } override fun insert(uri: Uri, values: ContentValues?): Uri? { val id = helper.writableDatabase.insert("my_database", null, values) return ContentUris.withAppendedId(uri, id) } }
MainActivityなどで保存・取得処理を動かすと無事にデータの保存ができている事が分かります。
contentResolver.insert(uri, ContentValues().apply { put("name", "name1") }) contentResolver.query(uri, null, null, null, null)?.apply { moveToFirst() while (moveToNext()) { Log.e("Test", getString(0)) // → name1 } }
次は他アプリでこのデータが取得できるかを確認します。
まずはAndroidManifest.xmlのproviderにandroid:exportedを付けます。
こうすることで外部からのアクセスができるようになります。
<provider android:name=".MyProvider" android:authorities="com.example" android:exported="true" />
付けない場合は下のようなエラーが発生します。
Permission Denial: opening provider com.example.myapplication.MyProvider from ProcessRecord{com.example.myapplication2} (pid=xxx, uid=xxx) that is not exported from UID xxx
次に新規プロジェクトを作成します。
あとは下のように同一URLで取得するだけです。
AndroidManifest.xmlの変更やContentProviderのサブクラスを作る必要はありません。
val uri = Uri.parse("content://com.example/") contentResolver.query(uri, null, null, null, null)?.apply { moveToFirst() while (moveToNext()) { Log.e("Test", getString(0)) // → name1 } }
今回はやらないのですが、更新・削除もupdate・deleteメソッドの実装をする事で実現できそうです。
ちなみにデータは元のアプリケーション内にあるので、元アプリケーションを削除すると一緒に消えます。
元アプリケーションがない場合、contentResolverのqueryを呼び出してもnullが返ってくるようになります。
それと両方のアプリのAndroidManifest.xmlで同じauthoritiesのProviderを作ると下のようなエラーになるのでURLが被らないよう注意する必要があります。
Installation failed due to: 'INSTALL_FAILED_CONFLICTING_PROVIDER: Package couldn't be installed in /data/app/com.example.myapplication2-xxx==: Can't install because provider name com.example (in package com.example.myapplication2) is already used by com.example.myapplication'