Android AccountManager+SyncAdapter

2013-06-04T00:00:00+00:00 Android Java

以前Android AccountManagerで使えるアカウントを開発する方法を書きましたが同期的な機能のSyncAdapterをすっぽかしてたのでやってみた

その前に現状だとSyncAdapterを使える仕様じゃないので、そこら辺をまず修正しなければならない。SyncAdapterを使う為にはContent Providerが必要な模様、なのでアクティビティ等のアプリに関わる部分(AccountAuthenticator以外)からサーバーに通信してデータを取る部分を全てContent Provider経由で取る。でバックグラウンドで同期が行われる際に、サーバー側から取得したデータをSQLiteデータベースに保管しておいて、それをContent Providerでやり取りをする

という感じな仕様に変えないといけない。んまぁそこら辺はちゃちゃっと修正して、SyncAdapterの本体辺りな所だけ書く

SyncAdapterを使う手順

  • AndroidManifest.xmlにandroid.content.SyncAdapterを持つを定義。さらにSyncAdapterから使うContent Providerの定義()も必要
  • SyncAdapterなXMLリソース定義を作成
  • SyncAdapterなandroid.app.Serviceを実装
  • SyncAdapterなandroid.content.AbstractThreadedSyncAdapterを実装

以上。これだけの追加が必要。でアプリ側の処理フロー的な流れ的には

  • SyncAdapterによるサーバーとのデータの同期を行う。その際そこからContent Providerを通じてSQLiteにデータを保管
  • Activity等からデータを利用するにはサーバーに通信せずにContent Providerを仲介する。でその際に必要になるのがCursorの扱いだが、そこはCursorLoader等を用いて利用する

っていう感じ。これは上でも書いてるので

又、SyncAdapterな機能を使う際には

  • android.permission.READ_SYNC_STATS
  • android.permission.READ_SYNC_SETTINGS
  • android.permission.WRITE_SYNC_SETTINGS

っていう権限が必要になる模様

AndroidManifest.xmlを修正する

SyncAdapterな所を追加する。上記でも書いてるようにパーミッションな所はすっ飛ばす

<service android:name=".SampleSyncService" android:exported="true">
    <intent-filter>
        <action android:name="android.content.SyncAdapter" />
    </intent-filter>
    <meta-data
        android:name="android.content.SyncAdapter"
        android:resource="@xml/sync" />
</service>

<provider
    android:name=".SampleContentProvider"
    android:authorities="@string/provider_name"
    android:label="@string/provider_name"
    android:exported="false"
    android:permission="sample.provider.permission" />

res/xml/sync.xmlを作成

<?xml version="1.0" ?>
<sync-adapter
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:contentAuthority="@string/provider_name" />

http://developer.android.com/reference/android/content/AbstractThreadedSyncAdapter.html に書いてるのでそれ参考に

SampleSyncService.java

package kinjouj.sample.authadapter;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class SampleSyncService extends Service {

    private static final String TAG = SampleSyncService.class.getName();
    private SampleSyncAdapter mSyncAdadter;

    @Override
    public void onCreate() {
        super.onCreate();
        mSyncAdadter = new SampleSyncAdapter(getApplicationContext(), true);
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.v(TAG, "onBind");

        return mSyncAdadter.getSyncAdapterBinder();
    }
}

AbstractThreadedSyncAdapter#getSyncAdapterBinderを返すだけ

SampleSyncAdapter.java

package kinjouj.sample.authadapter;

import java.io.IOException;
import java.util.Date;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import android.util.Log;

public class SampleSyncAdapter extends AbstractThreadedSyncAdapter {

    private static final String TAG = SampleSyncAdapter.class.getName();
    private AccountManager mAccountManager;

    public SampleSyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
        mAccountManager = AccountManager.get(context);
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority,
        ContentProviderClient provider, SyncResult result) {
        Log.v(TAG, "onPerformSync");
        Log.v("sync", "sync: " + new Date());

        String authToken = getAuthToken(account);

        // ここらへんでサーバーからのデータを取得してContent Provider介してSQLiteに対する保管を行う
    }

    public String getAuthToken(Account account) {
        String authTokenType = getContext().getString(R.string.account_type);
        String authToken = null;

        try {
            authToken = mAccountManager.blockingGetAuthToken(account, authTokenType, true);
        } catch (OperationCanceledException e) {
            e.printStackTrace();
        } catch (AuthenticatorException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return authToken;
    }
}

で大量のデータを登録するような場合だと、ContentProvider.applyBatch辺りを使えば良いんじゃないかなーっと

以上。アプリ入れてアカウント作ると

というように同期なチェックが出来るようになる。チェック入れると30秒毎にonPerformSyncが実行されてデータの同期が行われる。でこの間隔を設定にはどうしたらいいのかっていう所はまだ判明してない

んまぁAndroid公式のサンプルにあるSampleSyncAdapterのソース読んだ方が早いかも

Rails(OAuth)+Android SyncAdapter