同期アダプターを実行する

注: WorkManager をおすすめします。 バックグラウンド処理のほとんどのユースケースで推奨されるソリューションです。詳しくは、 バックグラウンド処理ガイドをご覧ください。

このクラスのこれまでのレッスンでは、同期アダプター コンポーネントを作成する方法を をカプセル化し、以下を可能にする追加のコンポーネントを 同期アダプターをシステムに接続します。これで、以下の手順に沿ってアプリをインストールする準備が整いました。 同期アダプターが含まれていますが、ご覧になったコードは同期アダプターを実際に実行していません。

同期アダプターの実行は、スケジュールに基づいて行うことも、いくつかの作業の間接的な結果として試すこともおすすめします。 イベントです。たとえば、同期アダプターを一定のスケジュールで実行したい場合、 特定の時間帯や特定の時間帯に 広告を表示できます必要に応じて 変更があるとデバイスに保存されているデータが変更されることはありません。実行中の Pod を 同期アダプターをユーザーの操作の直接の結果として認識します。この方法では、 同期アダプター フレームワークのスケジュール設定機能を利用できます。たとえば、 更新ボタンを提供する方法を紹介します

同期アダプターを実行するには、次のオプションがあります。

サーバーのデータが変更されたとき
サーバーベースであることを伝える、サーバーからのメッセージに応じて同期アダプターを実行する 確認します。このオプションを使用すると、サーバーからデバイスにデータを更新できます サーバーへのポーリングによってパフォーマンスの低下や バッテリー寿命の浪費をすることなく
デバイスデータが変更されたときに実行
デバイスのデータが変更されたときに同期アダプターを実行する。このオプションでは データをデバイスからサーバーにエクスポートできます。特に、セキュリティ リスクを サーバーには常に最新のデバイスデータがあることを 保証するものですこのオプションでは コンテンツ プロバイダにデータを保存する場合に実装します。スタブを使用している場合 データ変更の検出が難しくなる場合があります。
一定間隔
同期アダプターを、選択した間隔の経過後に実行するか、一定の時間が経過すると実行します。 しています。
オンデマンド
ユーザーの操作に応じて同期アダプターを実行します。ただし、適切なユーザー アカウントを 主に自動化されたオプションのいずれかを使用してください方法 バッテリとネットワーク リソースを節約できます。

このレッスンの残りの部分では、各オプションについて詳しく説明します。

サーバーデータの変更時に同期アダプターを実行する

アプリがサーバーからデータを転送し、サーバーのデータが頻繁に変更される場合は、 同期アダプターを使用して、データの変更に応じてダウンロードを実行します。同期アダプターを実行するには、 サーバーがアプリの BroadcastReceiver に特別なメッセージを送信します。 このメッセージに応じて、ContentResolver.requestSync() を呼び出して、同期アダプター フレームワークに 同期アダプターを使用します。

Google Cloud Messaging(GCM)は、 メッセージング システムを動作させるために必要なサーバー コンポーネントとデバイス コンポーネントを指定します。GCM を使用してトリガーする 転送は、サーバーにステータスをポーリングするよりも信頼性と効率に優れています。ポーリング中 常にアクティブな Service が必要です。GCM では、 メッセージが到着したときに有効化される BroadcastReceiver。ポーリング中 更新が利用できない場合でも、一定の間隔でバッテリーを消費し、GCM は 作成できます。

注: GCM を使用して、すべてのユーザーへのブロードキャストにより同期アダプターをトリガーします。 アプリをインストールしたデバイスへのメッセージは、 ほぼ同時に発生しますこの状況では、同期アダプターの複数のインスタンスが実行される可能性があります。 サーバーやネットワークの過負荷が 引き起こされますブロードキャストでこのような状況にならないように 同期アダプターの開始を一定期間遅らせる デバイスごとに固有の一意のキーがあります。

次のコード スニペットは、 次に対する requestSync(): GCM メッセージの受信:

Kotlin

...
// Constants
// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Account type
const val ACCOUNT_TYPE = "com.example.android.datasync"
// Account
const val ACCOUNT = "default_account"
// Incoming Intent key for extended data
const val KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST"
...
class GcmBroadcastReceiver : BroadcastReceiver() {
    ...
    override fun onReceive(context: Context, intent: Intent) {
        // Get a GCM object instance
        val gcm: GoogleCloudMessaging = GoogleCloudMessaging.getInstance(context)
        // Get the type of GCM message
        val messageType: String? = gcm.getMessageType(intent)
        /*
         * Test the message type and examine the message contents.
         * Since GCM is a general-purpose messaging system, you
         * may receive normal messages that don't require a sync
         * adapter run.
         * The following code tests for a a boolean flag indicating
         * that the message is requesting a transfer from the device.
         */
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE == messageType
            && intent.getBooleanExtra(KEY_SYNC_REQUEST, false)) {
            /*
             * Signal the framework to run your sync adapter. Assume that
             * app initialization has already created the account.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null)
            ...
        }
        ...
    }
    ...
}

Java

public class GcmBroadcastReceiver extends BroadcastReceiver {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Account type
    public static final String ACCOUNT_TYPE = "com.example.android.datasync";
    // Account
    public static final String ACCOUNT = "default_account";
    // Incoming Intent key for extended data
    public static final String KEY_SYNC_REQUEST =
            "com.example.android.datasync.KEY_SYNC_REQUEST";
    ...
    @Override
    public void onReceive(Context context, Intent intent) {
        // Get a GCM object instance
        GoogleCloudMessaging gcm =
                GoogleCloudMessaging.getInstance(context);
        // Get the type of GCM message
        String messageType = gcm.getMessageType(intent);
        /*
         * Test the message type and examine the message contents.
         * Since GCM is a general-purpose messaging system, you
         * may receive normal messages that don't require a sync
         * adapter run.
         * The following code tests for a a boolean flag indicating
         * that the message is requesting a transfer from the device.
         */
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)
            &&
            intent.getBooleanExtra(KEY_SYNC_REQUEST)) {
            /*
             * Signal the framework to run your sync adapter. Assume that
             * app initialization has already created the account.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null);
            ...
        }
        ...
    }
    ...
}

コンテンツ プロバイダのデータ変更時に同期アダプターを実行する

アプリがコンテンツ プロバイダ内のデータを収集していて、 プロバイダを更新したら、同期アダプターを自動的に実行するようにアプリを設定できます。タスク コンテンツ プロバイダのオブザーバーを登録します。コンテンツ プロバイダ内のデータが 変更すると、コンテンツ プロバイダ フレームワークはオブザーバーを呼び出します。オブザーバーで、 requestSync()(フレームワークに実行を指示する) 同期アダプターが必要です。

注: スタブ コンテンツ プロバイダを使用している場合、 コンテンツ プロバイダ、onChange() が 呼び出されることはありません。この場合、変更を検出する独自のメカニズムを用意する必要があります。 ダウンロードしますまた、このメカニズムでは呼び出しや requestSync(): データが変更されたとき。

コンテンツ プロバイダ用のオブザーバーを作成するには、クラスを拡張します。 ContentObserver と、その両方の形式の実装 onChange() メソッドを使用します。イン onChange()、呼び出し requestSync(): 同期アダプターを起動します。

オブザーバーを登録するには、次のように呼び出しに引数としてオブザーバーを渡します。 registerContentObserver()。イン この呼び出しでは、監視するデータのコンテンツ URI も渡す必要があります。内容 プロバイダ フレームワークは、このウォッチ URI を、引数として渡されたコンテンツ URI と比較し、 プロバイダを変更する ContentResolver メソッド ContentResolver.insert()。一致するものがあれば、 ContentObserver.onChange() の実装 が呼び出されます。

次のコード スニペットは、ContentObserver を定義する方法を示しています。 テーブルの作成時に requestSync() を呼び出す 変更点:

Kotlin

// Constants
// Content provider scheme
const val SCHEME = "content://"
// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Path for the content provider table
const val TABLE_PATH = "data_table"
...
class MainActivity : FragmentActivity() {
    ...
    // A content URI for the content provider's data table
    private lateinit var uri: Uri
    // A content resolver for accessing the provider
    private lateinit var mResolver: ContentResolver
    ...
    inner class TableObserver(...) : ContentObserver(...) {
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         * This method signature is provided for compatibility with
         * older platforms.
         */
        override fun onChange(selfChange: Boolean) {
            /*
             * Invoke the method signature available as of
             * Android platform version 4.1, with a null URI.
             */
            onChange(selfChange, null)
        }

        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         */
        override fun onChange(selfChange: Boolean, changeUri: Uri?) {
            /*
             * Ask the framework to run your sync adapter.
             * To maintain backward compatibility, assume that
             * changeUri is null.
             */
            ContentResolver.requestSync(account, AUTHORITY, null)
        }
        ...
    }
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Get the content resolver object for your app
        mResolver = contentResolver
        // Construct a URI that points to the content provider data table
        uri = Uri.Builder()
                .scheme(SCHEME)
                .authority(AUTHORITY)
                .path(TABLE_PATH)
                .build()
        /*
         * Create a content observer object.
         * Its code does not mutate the provider, so set
         * selfChange to "false"
         */
        val observer = TableObserver(false)
        /*
         * Register the observer for the data table. The table's path
         * and any of its subpaths trigger the observer.
         */
        mResolver.registerContentObserver(uri, true, observer)
        ...
    }
    ...
}

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider scheme
    public static final String SCHEME = "content://";
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Path for the content provider table
    public static final String TABLE_PATH = "data_table";
    // Account
    public static final String ACCOUNT = "default_account";
    // Global variables
    // A content URI for the content provider's data table
    Uri uri;
    // A content resolver for accessing the provider
    ContentResolver mResolver;
    ...
    public class TableObserver extends ContentObserver {
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         * This method signature is provided for compatibility with
         * older platforms.
         */
        @Override
        public void onChange(boolean selfChange) {
            /*
             * Invoke the method signature available as of
             * Android platform version 4.1, with a null URI.
             */
            onChange(selfChange, null);
        }
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         */
        @Override
        public void onChange(boolean selfChange, Uri changeUri) {
            /*
             * Ask the framework to run your sync adapter.
             * To maintain backward compatibility, assume that
             * changeUri is null.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null);
        }
        ...
    }
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Get the content resolver object for your app
        mResolver = getContentResolver();
        // Construct a URI that points to the content provider data table
        uri = new Uri.Builder()
                  .scheme(SCHEME)
                  .authority(AUTHORITY)
                  .path(TABLE_PATH)
                  .build();
        /*
         * Create a content observer object.
         * Its code does not mutate the provider, so set
         * selfChange to "false"
         */
        TableObserver observer = new TableObserver(false);
        /*
         * Register the observer for the data table. The table's path
         * and any of its subpaths trigger the observer.
         */
        mResolver.registerContentObserver(uri, true, observer);
        ...
    }
    ...
}

同期アダプターを定期的に実行する

実行から次の実行まで待機する時間を設定することで、同期アダプターを定期的に実行できます。 特定の時間帯に実行することも可能です同期アダプターを実行する サーバーの更新間隔をほぼ一致させることができます。

同様に、サーバーが比較的アイドル状態のときに、次のようにしてデバイスからデータをアップロードできます。 同期アダプターを夜間に実行するようスケジュール設定します。ほとんどのユーザーは電源を入れたまま電源につなぐ 通常はこの時間帯に空きがありますさらに、デバイスは次のタスクは実行されていません: 同期アダプターと同じ時間を使用します。ただし、この方法を使用する場合は、 各デバイスがわずかに異なるタイミングでデータ転送をトリガーします。すべてのデバイスで 同期アダプターを同時に使用すると、サーバーと携帯通信会社のデータが過負荷状態になる可能性があります。 接続します

一般的に、定期的な実行はユーザーが即時更新を必要としないが、 定期的に更新されます可用性のバランスを取る必要がある場合にも、定期的な実行は適切です。 同期アダプターを小規模で実行し、デバイスを過剰に使用せずに効率的に最新データを取得 説明します。

同期アダプターを定期的に実行するには、次のコマンドを使用します。 addPeriodicSync()。これにより、 同期アダプターを使用して、一定の時間が経過した後に実行します。同期アダプター フレームワークは、 他の同期アダプターの実行を考慮し、バッテリー効率を最大化しようとする場合、 数秒の誤差があります。また、同期されていない場合でも、同期アダプターが ネットワークを利用できません。

addPeriodicSync() はトリガーされません。 同期アダプターを実行する。同期アダプターの実行速度は、 トリガーとして繰り返しアラームを使用します。繰り返しアラームについては、 詳しくは、AlarmManager のリファレンス ドキュメントをご覧ください。「 メソッド setInexactRepeating() で設定する バリエーションがある場合は、開始時間をランダムに 同期アダプターが複数のデバイスから実行されるようにします。

メソッド addPeriodicSync()setSyncAutomatically() を無効にする、 そのため、比較的短時間で複数の同期が実行されることがあります。また、ごく少数の 同期アダプター制御フラグは、 addPeriodicSync()、宣言されている 許可または拒否されるかについては、 addPeriodicSync()

次のコード スニペットは、同期アダプターの定期的な実行をスケジュールする方法を示しています。

Kotlin

// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Account
const val ACCOUNT = "default_account"
// Sync interval constants
const val SECONDS_PER_MINUTE = 60L
const val SYNC_INTERVAL_IN_MINUTES = 60L
const val SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE
...
class MainActivity : FragmentActivity() {
    ...
    // A content resolver for accessing the provider
    private lateinit var mResolver: ContentResolver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Get the content resolver for your app
        mResolver = contentResolver
        /*
         * Turn on periodic syncing
         */
        ContentResolver.addPeriodicSync(
                mAccount,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL)
        ...
    }
    ...
}

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Account
    public static final String ACCOUNT = "default_account";
    // Sync interval constants
    public static final long SECONDS_PER_MINUTE = 60L;
    public static final long SYNC_INTERVAL_IN_MINUTES = 60L;
    public static final long SYNC_INTERVAL =
            SYNC_INTERVAL_IN_MINUTES *
            SECONDS_PER_MINUTE;
    // Global variables
    // A content resolver for accessing the provider
    ContentResolver mResolver;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Get the content resolver for your app
        mResolver = getContentResolver();
        /*
         * Turn on periodic syncing
         */
        ContentResolver.addPeriodicSync(
                mAccount,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL);
        ...
    }
    ...
}

オンデマンドで同期アダプターを実行する

ユーザー リクエストに応じて同期アダプターを実行することが最も推奨されない方法 同期アダプターを実行する場合に 使用しますバッテリーの消費を抑えるように設計されたフレームワーク スケジュールに従って同期アダプターを実行するときにトリガーされます。データに応じて同期を実行するオプション 新しいデータを提供するために電力が使用されるため、電力を効率的に消費します。

これに対し、ユーザーがオンデマンドで同期を実行できるようにすると、同期が単独で実行されるため、 ネットワークと電力リソースの 使用効率が良くありませんさらに オンデマンドで同期できるようにすることで データが変更された証拠がない場合でも同期をリクエストし、 データを更新しないは、バッテリー電力を効果的に使用しません。一般に、アプリには以下のいずれかが必要です。 他のシグナルを使用して同期をトリガーしたり、ユーザー入力なしで一定の間隔でスケジュールを設定したりする。

ただし、オンデマンドで同期アダプターを実行する場合は、同期アダプターのフラグを 手動で同期アダプターを実行してから、 ContentResolver.requestSync()

次のフラグを指定して、オンデマンド転送を実行します。

SYNC_EXTRAS_MANUAL
手動で同期を強制します。同期アダプター フレームワークでは、既存の設定が無視されるため、 (setSyncAutomatically() によって設定されたフラグなど)。
SYNC_EXTRAS_EXPEDITED
同期を強制的にすぐに開始します。設定しない場合、システムは 数秒前に行われます。これは、同期リクエストを実行する際の 短時間に多数のリクエストをスケジュールする場合。

次のコード スニペットは、 ボタンに対する応答の requestSync() 次をクリックします。

Kotlin

// Constants
// Content provider authority
val AUTHORITY = "com.example.android.datasync.provider"
// Account type
val ACCOUNT_TYPE = "com.example.android.datasync"
// Account
val ACCOUNT = "default_account"
...
class MainActivity : FragmentActivity() {
    ...
    // Instance fields
    private lateinit var mAccount: Account
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        /*
         * Create the placeholder account. The code for CreateSyncAccount
         * is listed in the lesson Creating a Sync Adapter
         */

        mAccount = createSyncAccount()
        ...
    }

    /**
     * Respond to a button click by calling requestSync(). This is an
     * asynchronous operation.
     *
     * This method is attached to the refresh button in the layout
     * XML file
     *
     * @param v The View associated with the method call,
     * in this case a Button
     */
    fun onRefreshButtonClick(v: View) {
        // Pass the settings flags by inserting them in a bundle
        val settingsBundle = Bundle().apply {
            putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
            putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true)
        }
        /*
         * Request the sync for the default account, authority, and
         * manual sync settings
         */
        ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle)
    }

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY =
            "com.example.android.datasync.provider";
    // Account type
    public static final String ACCOUNT_TYPE = "com.example.android.datasync";
    // Account
    public static final String ACCOUNT = "default_account";
    // Instance fields
    Account mAccount;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        /*
         * Create the placeholder account. The code for CreateSyncAccount
         * is listed in the lesson Creating a Sync Adapter
         */

        mAccount = CreateSyncAccount(this);
        ...
    }
    /**
     * Respond to a button click by calling requestSync(). This is an
     * asynchronous operation.
     *
     * This method is attached to the refresh button in the layout
     * XML file
     *
     * @param v The View associated with the method call,
     * in this case a Button
     */
    public void onRefreshButtonClick(View v) {
        // Pass the settings flags by inserting them in a bundle
        Bundle settingsBundle = new Bundle();
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_MANUAL, true);
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        /*
         * Request the sync for the default account, authority, and
         * manual sync settings
         */
        ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
    }