SyncAdapterのメモ。
年末暇なAndroid開発者は是非SyncAdapter
を実装しましょう。
バックグラウンドフェッチはこれでやるべきです。
startForeground()
とかStickyBroadcast
を悪用した死ににくいサービスとかで延々通信させる闇アプリは滅びてください。ていうか他人が実装した通信処理を信用できないので、バックグラウンドで何か通信するアプリは極力入れない主義です。
しかしSyncAdapter
は本当に資料がないです。以下、実装するときにいくつか疑問だったことのメモです。
android:process=":sync"
SyncAdapter
のトリガとなるサービスには:sync
というプロセス名を付けなければいけません。
<service> | Android Developers
If the name assigned to this attribute begins with a colon (':'), a new process, private to the application, is created when it's needed and the service runs in that process.
:
から始まるプロセス名はそのアプリのプライベートプロセスとして起動されることを意味します。そうでない場合、グローバルプロセスになります。
実動作を見てみると、hogehoge.yourpackage:sync
というプロセス名で同期処理が行われていることを確認できると思います。ここまでは知ってる人も多いかと思います。
Androidが闇なのは、ここで「グローバルプロセスとプライベートプロセスって何が違うの?」という当然の疑問を抱いても、それを知ってる人がまずいないことです…。
ContentProviderClient
これがずっと疑問で、Stackoverflow日本語版のおかげで解決したんですけど、
SyncAdapter
のサンプルだとContentResolver
をプライベート変数にしたり、onPerformSync()
のタイミングで取得したりしてますけど、単一のContentProvider
を使う限りにおいては、引数で取得できるContentProviderClient
を利用した方が高速です。
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
// フェッチを実行する、結果が取得できなければ更新しない
List<FeedItem> result = FeedConnection.execute();
if (result == null) {
syncResult.stats.numIoExceptions++;
return;
}
try {
ContentValues[] values = FeedDAO.getInstance().convertFromFeedList(result);
provider.bulkInsert(Constants.CONTENT_URI, values);
} catch (RemoteException e) {
syncResult.databaseError = true;
}
}
また、バルクインサートを使うとより効率的です。
テーブルを正規化してたりすると、複数のContentProvider
を扱うことになると思いますので、この手は使えないのですけど。
DAO は自作しない方がいいです
Data Access Object
とかgetter/setter
とか人間が書くものじゃない…。
素直にLombok
とORMで楽しましょう。
…と考えてるのですけど、どのORMを使うかが難しい。
ActiveAndroid
は見えてる地雷っぽいので回避したいです。greenDao
がよさげなのですけど、資料の漁りやすさに難がありそう。
AuthenticatorもContentProviderもスタブ実装でいい
SyncAdapter
が面倒なのは、Authenticator
とContentProvider
を実装しなければいけないという点で、AccountManager
など前提知識が多くこの辺で詰まる人が多いと思いますけど、これらを実装する必要はありません。
しかしContentProvider
は実装しておくといいかもしれないです。DataBaseHelper
を直接利用したほうが利便性は高いですし、開発者全体のレベル感に合わせて敢えて採用しないみたいなケースがありがちで、ちゃんとContentProvider
を実装する経験というのは意外に得がたいです。
CursorAdapter
、CursorLoader
と連携させると非同期でデータを更新し、アップデートの通知を受け取って自動的に更新されるタイムラインが感動的なくらいに楽に作れます!(CursorAdapter
とRecyclerView
の相性が悪いことには目を背けつつ…)
スタブ実装で構わないということは、これらは不要なのでしょうか?
そうではなく、両者を実装することによってセキュアな同期処理を行えるのです。
「SyncAdapter
によって、システムがContentProvider
を更新する」という説明からセキュリティ的にどうなの?と思うかもしれませんが、そのためのAuthenticator
です。AccountManager
によって認可された権限でContentProvider
を操作することこそが、SyncAdapter
の肝なのです。
たぶん。
SyncResultって何書けばいいの?
「SyncAdapter
を使えばいつ同期を行えばいいのか、システムが面倒を見てくれる」という触れ込みですが、これはSyncResult
に結果を返したケースだと考えています。
SyncResult
をちゃんと書くことで、システムに「いつ次の同期を行えばいいのか」の判断を与えることができます。
でも公式ドキュメントではこのことが完全にスルーされており、どう書けばいいのやら、他の実装を参考にしたり、実際の同期処理がいつ走っているのか確かめて手探りでやる必要があります…。
どうデバッグすればいいの?
SyncAdapter
による同期処理は別プロセスで起動するため、単にブレークポイントを貼ってもスルーされます。:sync
のプロセスが起動したタイミングでDDMSからデバッガをアタッチする必要があります。
が、そんなことやってられません。
いつ同期されたのか、同期したときにエラーがなかったかなど、診断ログを出力する機能を作っておくのが一番いいのかなーと思ってます。