しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

Androidのコンテンツプロバイダを試してみる

コンテンツプロバイダを使ってデータの保存を試してみました。
コンテンツプロバイダとは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'