なるようになるかも

力は多くの場合、その人の思いを超えない。

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が面倒なのは、AuthenticatorContentProviderを実装しなければいけないという点で、AccountManagerなど前提知識が多くこの辺で詰まる人が多いと思いますけど、これらを実装する必要はありません

しかしContentProviderは実装しておくといいかもしれないです。DataBaseHelperを直接利用したほうが利便性は高いですし、開発者全体のレベル感に合わせて敢えて採用しないみたいなケースがありがちで、ちゃんとContentProviderを実装する経験というのは意外に得がたいです。

CursorAdapterCursorLoaderと連携させると非同期でデータを更新し、アップデートの通知を受け取って自動的に更新されるタイムラインが感動的なくらいに楽に作れます!(CursorAdapterRecyclerViewの相性が悪いことには目を背けつつ…)

スタブ実装で構わないということは、これらは不要なのでしょうか?

そうではなく、両者を実装することによってセキュアな同期処理を行えるのです。

SyncAdapterによって、システムがContentProviderを更新する」という説明からセキュリティ的にどうなの?と思うかもしれませんが、そのためのAuthenticatorです。AccountManagerによって認可された権限でContentProviderを操作することこそが、SyncAdapterの肝なのです。

たぶん。

SyncResultって何書けばいいの?

SyncAdapterを使えばいつ同期を行えばいいのか、システムが面倒を見てくれる」という触れ込みですが、これはSyncResultに結果を返したケースだと考えています。

SyncResultをちゃんと書くことで、システムに「いつ次の同期を行えばいいのか」の判断を与えることができます。

でも公式ドキュメントではこのことが完全にスルーされており、どう書けばいいのやら、他の実装を参考にしたり、実際の同期処理がいつ走っているのか確かめて手探りでやる必要があります…。

どうデバッグすればいいの?

SyncAdapterによる同期処理は別プロセスで起動するため、単にブレークポイントを貼ってもスルーされます。:syncのプロセスが起動したタイミングでDDMSからデバッガをアタッチする必要があります。

が、そんなことやってられません。

いつ同期されたのか、同期したときにエラーがなかったかなど、診断ログを出力する機能を作っておくのが一番いいのかなーと思ってます。