ContentProviderClientというクラスがあります。
これはContentResolver同様、ContentProviderとやり取りするためのクライアントです。
アプリからこれを使用するにはContentResolverのacquireContentProviderClient(Uri)
またはacquireContentProviderClient(String)を呼び出してインスタンスを取得すします。
ContentProviderClientのAPIを見るとContentResolverと似ていることがわかります。
⇒ContentProviderClient:Android Developers
●ContentResolverとContentProviderClientの違いは何か?
ContentResolverはDBへ問合せる時、毎回コンテンツURIのAUTHORITYをキーにContentProviderを検索します。
そして、問合せ結果のcursorがcloseされるとContentProviderへの参照を破棄します。
ContentProviderClientは検索して取得したContentProviderへの参照を保持し続けます。
これは、問合せ結果のcursorがcloseされても続きます。
ContentProviderClientは、ContentResolverがクエリ発行の度にContentProviderを検索
する処理を省略する分高速に動作します。
また、ContentResolverと異なり、ContentProviderClientはqueryとopenFileメソッドは
スレッドセーフではありません。
●ContentProviderClientを扱う場合の注意点
開発者はContentProviderClientが不要となった場合にこれを明示的に破棄(release() )する必要があります。
これを怠ると、システムがContentProviderの必要・不要を判断できないためリークに繋
がります。
●ContentProviderプロセスの死亡とクライアントプロセスの死亡
ContentResolver経由でCursorオブジェクトを取得した場合、Cursor取得~Cursor.close()までの間に対象のContentProviderプロセスが死亡すると、呼び出し側のプロセスも道連れ
となります。
例えば、あなたのアプリが電話帳データを取得している最中にacoreプロセスが死亡すると、
あなたのアプリプロセスも強制終了されます。
これは、ContentProviderプロセスが死亡した時、これに依存するプロセスも強制終了させ
るようシステムがクリーンアップする仕組みです。
ContentProviderClientの場合は、ContentProviderClientの取得~破棄(release())まで
となります。
ContentProviderClientはその性質上、キャッシュされる期間がonResume~onPauseに相当
する程長くなりがちです。
ContentResolverの強制終了され得る範囲がCursor取得~Cursor.close()であることを
考えると、ContentProviderClientが強制終了され得る範囲は非常に長くなります。
※これについてはJellyBean以降、関連するAPIが追加されました
●それぞれのメリット・デメリット
ContentProviderClientはContentResolverと比べて高速に動作するメリットがあります。軽量な電話帳データ1件をqueryで取得した場合のパフォーマンスは下記です。
平均:125ms (ContentResolver)
平均:23ms (ContentProviderClient)
ContentProviderClientの方が5倍以上高速です。
# 両者の差はContentProviderの検索時間がほとんどなので、1度に大量のデータを取得す
# るような場合、この恩恵はあまり感じられないかもしれません。
ただし、ContentProviderClientは明示的に破棄(release())する必要があるため、
別途管理しないといけないデメリットがあります。
また、ContentProviderの強制終了による"道連れ強制終了"を少なからず考慮する必要が
あるでしょう。
※これについてはJellyBean以降、関連するAPIが追加されました
もし、あなたのアプリがContentResolver経由で軽量なクエリを複数回呼び出しているよ
うであれば、ContentProviderClientに置き換えることで大きなパフォーマンス向上が見込
めます。
ただし、ContentProviderClient(release())を管理することを忘れてはいけません。
●サンプルコード
ContentProviderClientを取得するacquireContentProviderClientメソッドの引数には、対象のContentProviderを取得できるURIを指定します。
@Override
protected void onResume() {
super.onResume();
initContentProviderClient();
}
@Override
protected void onPause() {
super.onPause();
releaseContentProviderClient();
}
private void initContentProviderClient() {
releaseContentProviderClient();
mContentProviderClient = getContentResolver()
.acquireContentProviderClient(
ContactsContract.Contacts.CONTENT_URI);
}
private void releaseContentProviderClient() {
if (mContentProviderClient != null) {
mContentProviderClient.release();
}
}
private void onUpdate() {
Cursor c = null;
try {
c = mContentProviderClient.query(
ContactsContract.Contacts.CONTENT_URI,
null, null, null, null);
} catch (RemoteException e) {
// ContentProviderがDeadObject化している。
// 必要に応じて、ContentProviderClientの再取得とリクエリを実施
initContentProviderClient();
} finally {
if (c != null) {
try {
c.close();
} catch (Exception e) {
// N.O.P
}
}
}
}
●JellyBeanで拡張されたAPI acquireUnstableContentProviderClient
JellyBeanで下記のAPIが追加されました。public final ContentProviderClient acquireUnstableContentProviderClient(String)
public final ContentProviderClient acquireUnstableContentProviderClient(Uri)
既存のacquireContentProviderClientメソッドとほぼ同じですが、下記の点で異なります。
ContentProviderClientへの参照を保持している間に、対象のContentProviderが強制終了
してもクライアントプロセスは強制終了されません。
"ContentProviderプロセスの死亡とクライアントプロセスの死亡"
で述べた"道連れ強制終了"が上記のAPIを呼び出して取得したContentProviderClientでは
発生しないということです。
このメソッドは、対象のContentProviderが安全でない、あるいは信頼できない場合に
使用します。
ContentProviderが強制終了された後、このContentProviderにリンクするContentProviderClient
を経由してクエリを発行するとDeadObject例外が投げられます。
その場合、catch節の中で古いContentProviderClientを破棄(release())して、再生成する
必要があります。
サンプルコードは、先ほどのコードにある
acquireContentProviderClient
を
acquireUnstableContentProviderClient
に置き換えるだけで動作するでしょう。
以上です。