概要
Android には SyncAdapter というデータの同期のためのフレームワークがあります。このフレームワークにのっかることで、通信環境やサーバの状態に応じた適切なデータ同期タイミングの調整、リトライのバックオフをシステムがやってくれます。そのためアプリ側の実装では、どのデータをどのように同期するかというロジックに集中することができます。
必要なものとしてはAccountとContentProvider、ServiceそしてAbstractThreadedSyncAdapterの4つです。ContentProviderは空実装でも問題ありません。またAccountもダミーのもので構いません。いちばん大事なのはAbstractThreadedSyncAdapterを継承して作る「どのデータをどのように同期するか」という部分です。
onPerformSyncメソッドのSyncResult
Android システムは、いい感じのタイミングを見計らって、有効なAbstractThreadedSyncAdapterのonPerformSyncメソッドをバックグラウンドスレッドから呼び出してくれます。ここで同期したいデータを取り出し、適切な手順でサーバ(クラウド)とデータを同期します。
何もなければメソッドは正常に戻り、アプリがなすべき同期のフローは終わりです。ただし、通信の状況やサーバの状況によっては、うまくデータが同期できないことも考えられます。そのようなときのために、onPerformSyncメソッドにはSyncResultというオブジェクトが渡されます。
Android Developers のリファレンスによると
This class is used to communicate the results of a sync operation to the SyncManager. Based on the values here the SyncManager will determine the disposition of the sync and whether or not a new sync operation needs to be scheduled in the future.
とあり、Android システムがデータの同期のスケジューリングについてSyncResultの内容を見て判断してくれることが分かります。
SyncResult.stats
SyncResultのリファレンスには各々のフィールドについて、システムが無視するものと使うもの、参考程度にするものとがあると書いてあります。
このうち、システムが同期のスケジューリングのときに使う大事な情報をもつものはstatsフィールドです。このフィールドはSyncStatsという型で、何件のデータを同期したか、何件のデータが挿入・上書き・削除・されたか、また何件のIOException/ParseException/AuthExceptionがあったかを記録します。
名前からもわかるとおり、何かしらの理由で同期に失敗した場合には、IOException/ParseException/AuthExceptionの件数をインクリメントすることで、システムにスケジューリングを促すことになります。
ソフトエラーとハードエラー
同期の失敗理由にはいくつかの種類があるはずです。通信が貧弱でうまくデータがやりとりできなかった場合、サーバが障害等でレスポンスできなかった場合、あるいは、アプリ側の不良が理由で同期に失敗することもありえます。
通信環境による失敗はソフトエラーとしてシステムが記録し、近い未来に同期を再実行するようスケジューリングします。これが何度も続いた場合にはエクスポネンシャルバックオフと呼ばれる方法で、スケジューリングの感覚が徐々に長くなっていきます。
一方で、サーバの障害やアプリの不良、または不正なデータはすぐには改善する見込みが無いためハードエラーとしてシステムが記録します。このハードエラーが何度も続くと、システムは同期のスケジューリングを諦めます。
SyncResultをちゃんと記録しないとどうなるか
SyncResultに対して同期のフローで何が起きたかを記録しない場合、システムは同期が正常に終了したものとみなします。onPerformSyncメソッドにthrowsシグネチャはないので、自分で適切に例外をハンドリングしSyncResultにエラーを記録する必要があります。仮になにもしないと、たとえ何らかの失敗があってもシステムはスケジューリングをし続ける事になります。
君がッ 泣いても 殴るのをやめないッ!
記録のとりかたについては、GoogleSamples にちょうどよいプロジェクトが公開されています。
余談:ContentProviderClientは何だ
onPerformSyncメソッドにはSyncResult以外にもContentProviderClientも渡されます。
これはContentResolverのようにContentProviderにクエリを投げたりデータを挿入したりするために使います。これらの違いなどについては、このブログ記事に詳しく解説されていますが、AbstractThreadedSyncAdapterにおいてはContentProviderClient#release()はonPerformSyncから戻ってきたときに呼び出してくれるようです。