はじめに
この記事はAndroidで手軽にアプリ間データ共有したい!と思ったときに試したことのメモです。
本来はアプリ間通信は、開発者が異なる場合がほとんどなのでAIDLなどで定義してやり取りしたほうがいいケースも多いかと思いますが、データの共有のみならContentProviderも簡単です。
ContentProviderはSQLiteでのデータ管理を前提としたようなAPIになっていますが、実装してみるとわかるように実はSQLiteで管理しなくていいような、SharedPreferenceのような単純なデータも共有することができます。
アプリ内のデータ共有
Androidアプリの開発では、自アプリ内でデータを永続化したいとき、SharedPreferencesを使うことがあると思います。
SharedPreferencesは同一プロセス内だと、シングルトンのオブジェクトにkey valueを保持してくれるので、File IOが最小限に抑えられます。(commitとか頻繁にしなければ。)
また、listenerを使えばイベントバスのようにpub/subで同期できるので、複数のServiceやActivityでデータをリアルタイムに共有できます。
例えばActivity同士やActivityとServiceなど、同一アプリで情報をやり取りするときに、Binder、MessengerやAIDLを実装するよりも手軽だったりします。
同様のことは、イベントバスだったりApplicationクラスを使ったりで実現できますが、APIが用意されているのでよく使ってしまいます。
アプリ間では?
SharedPreferenceはアプリ間で共有するようなオプションが存在します。MODE_WORLD_READABLEやMODE_WORLD_WRITEBLEです。また、同一アプリでも複数プロセスからアクセスするときのためにMODE_MULTI_PROCESSというのも存在します。
しかし、権限の指定などが管理できない上に、マルチプロセスでの対応があまりよくないという噂も聞きます。(記事が古いので今は改善されているかもしれません)
そのため、アプリ間共有などではなるべくきちんとプロセス間通信を使いたいです。
ContentProviderは標準アプリでも使われていますが、他のアプリにデータベースを共有するためのプロセス間通信の仕組みです。
どのように動いているのか詳しくは調べていませんが、基本的にはサービス間のBindと同様にOSのインターフェースを通じて通信しているようです。
ContentProviderの実装例を調べるとほとんどがSQLiteを実装したりの例だったのですが、共有すべきデータが、極端な話ひとつだけだったりしたときには過剰だったりします。(そこまでいくとContentProviderを使うのも過剰かもしれませんが、、)
そこでSharedPreferenceのような手軽さでContentProviderを使えないかと調べていたら、こちらの記事で「SharedPreferenceでデータ管理してContentProviderを使えばいいよ!」とアドバイスされていたので、試してみました。
public class TestProvider extends ContentProvider {
...
private SharedPreferences mPrefs;
@Override
public boolean onCreate() {
LogUtil.d(TAG, "onCreate " + Binder.getCallingPid() + ", "
+ Thread.currentThread().getName());
mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// URIの整合性を確認
checkUri(uri);
if (uri == null || uri.getPathSegments() == null) {
} else if (TABLE.equals(uri.getPathSegments().get(0))) {
// すごく例えが悪いですが、
// 例えばSharedPreferencesの中身をそのままkey,valueでいれてしまう。
MatrixCursor cursor = new MatrixCursor(new String[] {
"key", "value"
});
Map all = mPrefs.getAll();
Object value;
for (String key : selectionArgs) {
if (all.containsKey(key)) {
value = all.get(key);
cursor.addRow(new Object[] {
key, value
});
}
}
return cursor;
} else {
//テーブルによって出し分けるなど。
}
throw new UnsupportedOperationException("");
}
// もしもその他作業には対応していなければExceptionをthrowするなど。
@Override
public Uri insert(Uri uri, ContentValues values) {
checkUri(uri);
throw new UnsupportedOperationException();
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
checkUri(uri);
throw new UnsupportedOperationException();
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
checkUri(uri);
throw new UnsupportedOperationException();
}
}
また、ContentResolver.notifyChangeなどを使うと、listenerを用いた同期も行えるので便利です。
まとめ
ContentProviderではデータの格納方法については限定されてはいないので、データが大量である場合にはなんらかのDBを使うのが効率良いですが、小さい場合には逆にオーバヘッドが大きくなったりもするので、オプションや簡単な情報だけならSharedPreferenceのようなxml形式の永続化方法をとってもいいかもしれません。
ただ、MessengerやAIDLよりも手軽、と言いつつもテーブルの仕様などは共有しなければいけないので、逆にバグが見つかりづらくなってしまうかもしれません。
とはいえ、Broadcast IntentのやりとりやService同士のBindに比べて若干オーバヘッドが少ないように感じます。
もしデータだけのやりとりで、仕様クラスの共有などするのが面倒な時は試してもいいかもしれません。
というか、自分自身はこの件でContentProviderの便利さを見直しました。
メリット
- 実装が簡単(?)
- 共有する内容が少ない(Uri名、テーブル仕様など)
- ServiceのライフサイクルやBroadcast Intentのタイミングなどをきにしなくて良い
デメリット
- ビルドが通ってしまう
- データの形がテーブルに限定されてしまう