2023/02/06追記: 以下の方法は別のアプリ間でデータをやりとりできますが、それはapkの署名が同じである場合に限るようです。
異なる署名間のアプリでは、この方法だけではContentProviderのデータを読み取ることができませんでした。
セキュリティ的にはそりゃそうだよね〜という感じです。
データを提供する側のアプリ: ContentProviderを実装
データを読み取る側のアプリ: ContentResolverのAPIを呼び出す
データを提供する側
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<!-- 自分のContentProviderにアクセスさせるための権限を宣言する -->
<permission android:name="com.example.contentproviertest.READ_DATA" />
<application>
<!-- 自分のContentProviderの宣言を追加 -->
<provider
android:name=".MyContentProvider"
android:authorities="com.example.contentprovidertest.myprovider"
android:enabled="true"
android:exported="true"
android:readPermission="com.example.contentproviertest.READ_DATA"
/>
</application>
</manifest>
ポイント
- ContentProvider実装クラスをAndroidManifest.xmlに宣言する
-
<provider>
- name: クラス名(
.
で始めた場合はパッケージ名からの相対名) - authorities: オーソリティ名。他のアプリと被ってはいけない。パッケージ名のように、ドメイン名の逆順とかで付ける
- readPermission:
<permission>
で宣言した名前と揃える
- name: クラス名(
ContentProviderの実装は以下のような感じになります。
class MyContentProvider : ContentProvider() {
override fun onCreate(): Boolean {
return true
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
return 0 // TODO 未実装
}
override fun getType(uri: Uri): String? {
return null // TODO 未実装(nullのままでもデータの取得には問題ない)
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
return null // TODO 未実装(外からデータを読み取るだけならこのままでも問題ない)
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
): Int {
return 0 // TODO 未実装(外からデータを読み取るだけならこのままでも問題ない)
}
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor {
// TODO なんらかの方法でデータを取得し、Cursor型として返却する。
// データがSQLiteテーブルにあるなら引数Cursorをそのまま返す。
// データがSQLiteテーブルではないなら、MatrixCursorを使って擬似的なテーブルのような構造でデータを返す。
// ここでは固定の値を返すサンプル
return MatrixCursor(
arrayOf("name", "address"),
).apply {
addRow(arrayOf("Taro", "Tokyo"))
addRow(arrayOf("Yuka", "Okinawa"))
}
}
}
このContentProviderにデータをクエリすると、以下のようなテーブル(のような構造)が返却されるはずです。
name | address |
---|---|
Taro | Tokyo |
Yuka | Okinawa |
データを読み取る側
ContentProviderのデータを利用する側のAndroidManifestに、「このContentProviderを使いますよ」という宣言を入れる。
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- ContentProvider側の <permission> と同じ名前を <uses-permission> で宣言 -->
<uses-permission android:name="com.example.contentproviertest.READ_DATA" />
<queries>
<!-- ContentProvider側の <provider> の authorities と同じものを指定 -->
<provider android:authorities="com.example.contentprovidertest.myprovider" />
</queries>
<application>
<!-- 以下、省略 -->
</application>
</manifest>
ポイント
- ContentProvider側で指定している
<permission>
を<uses-permission>
に書く - ContentProvider側で指定している
<provider>
のandroid:authorities
を<queries>
内の<provider android:authorities>
に書く
AndroidManifestの定義がきちんとできていれば、別のアプリからContentProviderのデータをクエリできます。その際、content://${authorities}
という形式のURIを使用してContentResolver.query()
メソッドを呼びます。
val authority = "com.example.contentprovidertest.myprovider"
val uri = "content://$authority"
context.contentResolver.query(Uri.parse(uri), null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex("name")
val addressIndex = cursor.getColumnIndex("address")
while (cursor.moveToNext()) {
val name = cursor.getString(nameIndex)
val address = cursor.getString(addressIndex)
}
}
以上、ContentProviderを使ったアプリ間のデータ共有方法のすごくシンプルな実装を紹介しました。
最低限の説明のために非常にシンプルな呼び出し方を使用しましたが、実際にはデータを識別するためにURIの末尾にパスを指定したり、query()
メソッドの残りの引数に欲しいカラム名や条件を絞り込むためのクエリ文字列を入れたりすることが可能です。