ContentProviderClientを探る

ContentProviderClient
--------------------------------------
ContentResolverを介さずにContentProviderと直接通信することでパフォーマンス上のアドバンテージを得られるが, ContentProviderClientを管理するコストは無視できない. 
使いどころに困るが, 知っていればいいことがあるかもしれない, そんなクラス. 

参考: 

 - [Android Developers - ContentProviderClient](http://developer.android.com/reference/android/content/ContentProviderClient.html)
 - [Yukiの枝折 - ContentResolver？ContentProviderClient？](http://yuki312.blogspot.jp/2012/07/androidcontentresolvercontentprovidercl.html)

> The public interface object used to interact with a ContentProvider. This is obtained by calling acquireContentProviderClient(Uri). This object must be released using release() in order to indicate to the system that the ContentProvider is no longer needed and can be killed to free up resources.

> Note that you should generally create a new ContentProviderClient instance for each thread that will be performing operations. Unlike ContentResolver, the methods here such as query(Uri, String[], String, String[], String) and openFile(Uri, String) are not thread safe -- you must not call release() on the ContentProviderClient those calls are made from until you are finished with the data they have returned.

ContentProviderと相互通信するためのクラス. ContentProviderClientは`acquireContentProviderClient(Uri)`で取得できる. ContentProviderClientはContentProviderとのコネクションを確立し, クエリ処理後もその参照は続く. そのため使い終わったらrelease()で明示的にリソース解放する必要がある. 

また, ContentProviderClientのqueryやopenFileメソッドはContentResolverのものとは違ってスレッドセーフではない. ContentProviderClientは通常スレッド毎に保持し, ContentProvider操作中に誤ってreleaseしてしまわないよう配慮する必要がある. 


Performance
--------------------------------------
ContentResolverの一部でもContentProviderClientを使用してContentProviderと相互通信を行っている. ただし, ContentResolverではContentProviderClientを再利用することはせず1度使ったら即releaseする. 

ContentProviderClientの生成にはURIの解決からContentProviderとのコネクション確立とコストがかかるため, 自前でContentProviderClientを管理すると高速化するケースがある(管理コストに見合うかという問題は残る). 手元の環境では電話帳1件の取得で2回目以降 125ms ⇒ 23ms の改善が見られた.

後述するProcess has died.の内容も踏まえて, クエリを複数回, 固めて発行するような箇所でContentProviderClientを使用すると効果が見込める. 


Process has died.
--------------------------------------
ContentResolver経由でCursorオブジェクトを取得した場合, Cursor取得～Cursor.close()
までの間に接続中のContentProviderプロセスが死亡するとクライアント側のプロセスも道連れ
となる*1. 
例えば, アプリが電話帳データを取得している最中にacoreプロセスが死亡すると, アプリプロセスも強制終了される. これは, システムによるクリーンアップ制御である. 

クライアントがContentProviderClientを使用している場合は, ContentProviderClientの取得～release()までの間がこれに該当する. ContentProviderClientをonResume～onPauseの区間保持していると, 強制終了され得る範囲が非常に長くなることに注意. リソースの枯渇にも繋がるのでContentProviderClientを直接使用する際には, 限定的な使用に留めるのが得策. 

*1: JellyBean以降、関連するAPI acquireUnstableContentProviderClient が追加されました


Usage
--------------------------------------

```java
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化している.
        releaseContentProviderClient();
    } finally {
        c.close();
    }
}
```

acquireUnstableContentProviderClient
--------------------------------------
JellyBeanで下記のAPIが追加された。

 - [acquireUnstableContentProviderClient(String)](http://developer.android.com/reference/android/content/ContentResolver.html#acquireUnstableContentProviderClient(android.net.Uri))
 - [acquireUnstableContentProviderClient(Uri)](http://developer.android.com/reference/android/content/ContentResolver.html#acquireUnstableContentProviderClient(java.lang.String))

既存のacquireContentProviderClientメソッドとは次の点で異なる.

ContentProviderClientへの参照を保持している間, 対象のContentProviderが強制終了してもクライアントプロセスは強制終了されない. 
"Process has died."で述べた"道連れ強制終了"が上記APIで取得したContentProviderClientでは発生しない. 

ContentProviderが強制終了された後, このContentProviderにリンクするContentProviderClientを経由してクエリを発行するとDeadObject例外が投げられる. その場合, catch節の中で古いContentProviderClientを破棄(release())する. 
Usageにあるコードの acquireContentProviderClient を acquireUnstableContentProviderClient に置き換えれば動作する. 


> **License:**
> Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.