Wear 上のネットワーク アクセスと同期

Wear OS by Google 搭載のスマートウォッチは、Android または iOS スマートフォンにアクセスせずにネットワークと直接通信できます。このネットワークへの直接アクセスにより、Data Layer API を(Wear 1.x で)使用せずにネットワークに接続することができます。

以下の関連リソースもご覧ください。

ネットワーク アクセス

Wear OS アプリではネットワーク リクエストを作成できます。スマートウォッチからスマートフォンへの Bluetooth 接続では、スマートウォッチのネットワーク トラフィックは通常、スマートフォンを介してプロキシ接続されます。ただし、スマートフォンを使用できない場合は、ハードウェアに応じて Wi-Fi ネットワークとモバイル ネットワークが使用されます。ネットワークの遷移は Wear プラットフォームによって処理されます。

プロトコルには HTTP、TCP、UDP などを使用できますが、android.webkit API(CookieManager クラスを含む)は使用できません。リクエストとレスポンスのヘッダーの読み取りと書き込みを行うことで、Cookie を使用できます。

また、以下を使用することをおすすめします。

  • 非同期ジョブ(一定間隔でのポーリングなど)用の JobScheduler API(下記を参照)。
  • マルチネットワーク API(特定の種類のネットワークに接続する必要がある場合。詳しくは、複数のネットワーク接続を参照)。

高帯域幅ネットワークへのアクセス

Wear OS プラットフォームは、全体的なユーザー エクスペリエンスを最大限に高めることを目標としてネットワーク接続を管理します。このプラットフォームはデフォルトのアクティブなネットワークを選択するうえで、以下の 2 つの要素のバランスを考慮します。

  • 電池の節約に関するニーズ
  • ネットワーク帯域幅に関するニーズ

電池の節約を優先すると、アクティブなネットワークにおける帯域幅の不足により、高帯域幅を必要とするネットワーク タスク(サイズの大きいファイルの転送、メディアのストリーミングなど)を実行できなくなることがあります。

このセクションでは、ConnectivityManager クラスを使用して、アプリで必要なネットワーク帯域幅を使用できるようにする方法について説明します。ネットワーク リソースをきめ細かく管理する方法については、ネットワーク使用状況の管理をご覧ください。

また、下記の手法についてのサンプルもご覧ください。

高帯域幅ネットワークを確保する

Wear OS では、高帯域ネットワークを常に利用できるとは限りません。高帯域幅ネットワークにアクセスする必要があるユースケース(サイズの大きいファイルの転送、メディアのストリーミングなど)では、次の手順を行うことをおすすめします。

  1. アクティブなネットワークが存在するかどうかを確認し、存在する場合はその帯域幅を確認します。
  2. アクティブなネットワークが存在しない場合、またはアクティブなネットワークの帯域幅が不十分な場合は、定額制の Wi-Fi ネットワークまたはモバイル ネットワークへのアクセスをリクエストします。

ConnectivityManager クラスを使用すると、アクティブなネットワークが存在し、その帯域幅が十分かどうかを確認できます。

Kotlin

    const val MIN_BANDWIDTH_KBPS = 320
    connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val bandwidth: Int = connectivityManager.activeNetwork?.let { activeNetwork ->
        connectivityManager.getNetworkCapabilities(activeNetwork).linkDownstreamBandwidthKbps
    } ?: -1

    when {
        bandwidth < 0 -> {
            // No active network
        }
        bandwidth in (0 until MIN_BANDWIDTH_KBPS) -> {
            // Request a high-bandwidth network
        }
        else -> {
            // You already are on a high-bandwidth network, so start your network request
        }
    }
    

Java

    int MIN_BANDWIDTH_KBPS = 320;
    connectivityManager =
      (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    Network activeNetwork = connectivityManager.getActiveNetwork();

    if (activeNetwork != null) {
      int bandwidth =
        connectivityManager.getNetworkCapabilities(activeNetwork).getLinkDownstreamBandwidthKbps();

      if (bandwidth < MIN_BANDWIDTH_KBPS) {
        // Request a high-bandwidth network
      } else {
        // You already are on a high-bandwidth network, so start your network request
      }
    } else {
      // No active network
    }
    

定額制の高帯域幅ネットワークをリクエストするには、ConnectivityManager を使用します。1 つのネットワーク リクエストで定額制の Wi-Fi ネットワークまたはモバイル ネットワークをリクエストできます。ネットワークの準備が整うと(たとえば、デバイスの Wi-Fi 無線が保存済みネットワークに接続されると)、NetworkCallback インスタンスの onAvailable() メソッドが呼び出されます。適切なネットワークが見つからない場合、onAvailable() メソッドは呼び出されません。そのため、リクエストを手動でタイムアウトする必要があります。詳しくは、ネットワークを使用できるようになるまで待機するをご覧ください。

Kotlin

    networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            if (bindProcessToNetwork(network)) {
                // socket connections will now use this network
            } else {
                // app doesn't have android.permission.INTERNET permission
            }
        }
    }

    val request: NetworkRequest = NetworkRequest.Builder().run {
        addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
        addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
        addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
        addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        build()
    }

    connectivityManager.requestNetwork(request, networkCallback)
    

Java

    networkCallback = new ConnectivityManager.NetworkCallback() {
      @Override
      public void onAvailable(Network network) {
        if (bindProcessToNetwork(network)) {
          // socket connections will now use this network
        } else {
          // app doesn't have android.permission.INTERNET permission
        }
      }
    };

    NetworkRequest request = new NetworkRequest.Builder()
      .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
      .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
      .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
      .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
      .build();

    connectivityManager.requestNetwork(request, networkCallback);
    

ネットワークを解放する

アプリで高帯域幅ネットワークが不要になったら、ConnectivityManager クラスを使用してネットワークを解放し、プラットフォームでネットワーク アクセスの管理を再開できるようにする必要があります。

Kotlin

    connectivityManager.bindProcessToNetwork(null)
    connectivityManager.unregisterNetworkCallback(networkCallback)
    

Java

    connectivityManager.bindProcessToNetwork(null);
    connectivityManager.unregisterNetworkCallback(networkCallback);
    

電池の消費量を最適化するには、ネットワーク接続の登録をアクティビティの期間中にのみ維持するようにします。そのため、アクティビティの onStop() メソッドでネットワークを解放することを検討する必要があります。

ネットワークを使用できるようになるまで待機する

スマートウォッチの Wi-Fi ネットワークまたはモバイル ネットワークは電池の節約のために切断されることがあるため、ネットワークをすぐに確保できない場合があります。また、スマートウォッチがネットワークに接続できない場合、NetworkCallback インスタンスの onAvailable() メソッドは呼び出されません。そのため、あらかじめ決められた時間が経過したらリクエストをタイムアウトして、関連リソースを解放する必要があります。

Kotlin

    const val MESSAGE_CONNECTIVITY_TIMEOUT = 1
    const val NETWORK_CONNECTIVITY_TIMEOUT_MS: Long = 10000
    ...
    handler = MyHandler()

    networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            handler.removeMessages(MESSAGE_CONNECTIVITY_TIMEOUT)
            ...
        }
    }

    connectivityManager.requestNetwork(request, networkCallback)

    handler.sendMessageDelayed(
            handler.obtainMessage(MESSAGE_CONNECTIVITY_TIMEOUT),
            NETWORK_CONNECTIVITY_TIMEOUT_MS
    )
    ...
    // Don't make this an inner class otherwise there is the potential to leak the parent class
    private class MyHandler : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MESSAGE_CONNECTIVITY_TIMEOUT -> {
                    // unregister the network
                }
            }
        }
    }
    

Java

    int MESSAGE_CONNECTIVITY_TIMEOUT = 1;
    long NETWORK_CONNECTIVITY_TIMEOUT_MS = 10000;

    handler = new MyHandler();

    networkCallback = new ConnectivityManager.NetworkCallback() {
      @Override
      public void onAvailable(Network network) {
        handler.removeMessages(MESSAGE_CONNECTIVITY_TIMEOUT);
        ...
      }
    };

    connectivityManager.requestNetwork(request, networkCallback);

    handler.sendMessageDelayed(
      handler.obtainMessage(MESSAGE_CONNECTIVITY_TIMEOUT),
      NETWORK_CONNECTIVITY_TIMEOUT_MS);

    ...
    // This needs to be static to avoid potentially leaking the parent class
    private static class MyHandler extends Handler {
       @Override
       public void handleMessage(Message msg) {
           switch (msg.what) {
               case MESSAGE_CONNECTIVITY_TIMEOUT:
                   // unregister the network
                   break;
           }
       }
    }
    

ネットワークの状態を監視する

NetworkCallback インターフェースには、バインドされているネットワークの状態の変化(帯域幅の変化、接続の切断など)を監視するためのメソッドが用意されています。

Kotlin

    networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
            val bandwidth: Int = networkCapabilities.linkDownstreamBandwidthKbps

            if (bandwidth < MIN_BANDWIDTH_KBPS) {
                // handle insufficient network bandwidth
            }
        }

        override fun onLost(network: Network) {
            // handle network loss
        }
    }
    

Java

    networkCallback = ConnectivityManager.NetworkCallback {
      @Override
      public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
        int bandwidth = networkCapabilities.getLinkDownstreamBandwidthKbps();

          if (bandwidth < MIN_BANDWIDTH_KBPS) {
            // handle insufficient network bandwidth
          }
      }

      @Override
      public void onLost(Network network) {
        // handle network loss
      }
    }
    

Wi-Fi 設定アクティビティを開始する

Wi-Fi ネットワークをリクエストすると、保存済みネットワークが設定されていて範囲内にある場合、システムは保存済みネットワークに接続しようとします。ただし、使用可能な保存済み Wi-Fi ネットワークが存在しない場合、NetworkCallback インスタンスの onAvailable() コールバック メソッドは呼び出されません。ネットワーク リクエストをタイムアウトするために Handler を使用している場合は、タイムアウトが発生したときに Wi-Fi ネットワークを追加するようユーザーに指示することができます。次のインテントを使用すると、ユーザーをアクティビティに直接誘導して Wi-Fi ネットワークを追加できます。

Kotlin

    context.startActivity(Intent("com.google.android.clockwork.settings.connectivity.wifi.ADD_NETWORK_SETTINGS"))
    

Java

    context.startActivity(new Intent("com.google.android.clockwork.settings.connectivity.wifi.ADD_NETWORK_SETTINGS"));
    

設定アクティビティを開始するには、アプリに次の権限を付与する必要があります。android.permission.CHANGE_WIFI_STATE

ユーザー インターフェースに関する考慮事項

アプリで高帯域幅の新しい Wi-Fi ネットワークへの接続が必要な場合、Wi-Fi の設定を開始する前に、接続する理由をユーザーに明確に示す必要があります。高帯域幅のネットワークが必要な場合にのみ、新しい Wi-Fi ネットワークを追加するようユーザーにリクエストします。高帯域幅のネットワークを必要としないアプリの機能にユーザーがアクセスするのを妨げないでください。

図 1 に音楽アプリの例を示します。このアプリでは、ユーザーが音楽をブラウジングできるようにする必要があり、音楽をダウンロードまたはストリーミングする場合にのみ、新しい Wi-Fi ネットワークを追加するようユーザーに要求します。

音楽のダウンロード

図 1. 音楽アプリのダウンロード フロー

アプリが動作するうえで事前に高帯域幅のネットワークが必要な場合、新しい Wi-Fi ネットワークを追加するようユーザーに要求する前に、明確な根拠をユーザーに示す必要があります。また、時間がかかるネットワーク操作(ユーザーのメディア プレイリストのダウンロードなど)の場合、進行状況インジケーターを表示し、実行されている操作の内容を示す必要があります。

図 2 に音楽アプリのストリーミング フローを示します。音楽をストリーミングするために高帯域幅のネットワークが必要な場合、ユーザーが Wi-Fi の設定に移る前に、新しい Wi-Fi ネットワークが必要な理由をアプリで明確に示す必要があります。

音楽のストリーミング

図 2. 音楽アプリのストリーミング フロー

クラウド メッセージング

通知の送信に関して、アプリは、Google Cloud Messaging(GCM)の後継機能である Firebase Cloud Messaging(FCM)を直接使用できます。FCM は Wear 2.0 でサポートされていますが、GCM は Wear 2.0 ではサポートされていません。

ネットワーク アクセス用や FCM 用の API は、Wear OS に固有のものではありません。既存のドキュメントで、ネットワークへの接続クラウド メッセージングについてご確認ください。

FCM は Doze モードでも正常に動作するため、スマートウォッチに通知を送信するのに適しています。

Wear アプリの実行中にデバイスの登録トークンを収集することで、FCM からのメッセージに備えます。さらに、サーバーから FCM REST エンドポイントにメッセージを送信する際に、このトークンを宛先の一部として含めます。FCM は、トークンで指定されたデバイスにメッセージを送信します。

FCM メッセージは JSON 形式のため、以下のペイロードのいずれかまたは両方を含めることができます。

  • 通知ペイロード: 通知ペイロードをスマートウォッチが受け取ると、通知ストリーム内でデータがユーザーに直接表示されます。ユーザーが通知をタップすると、アプリが起動します。
  • データ ペイロード: このペイロードにはカスタムの Key-Value ペアのセットが含まれており、Wear アプリにデータとして配信されます。

ペイロードの詳細と例については、FCM メッセージについてをご覧ください。

デフォルトでは、通知はスマートフォン アプリからスマートウォッチにブリッジ(共有)されます。スタンドアロンの Wear アプリと対応するスマートフォン アプリがある場合、通知が重複することがあります。たとえば、スマートフォンとスマートウォッチの両方が FCM から受け取った同じ通知が、両方のデバイスで別々に表示されることがあります。

バックグラウンド サービスを使用する

バックグラウンド タスクが適切に実行されるようにするには、各タスクでの Doze の使途を説明する必要があります。Android 6.0 では、Doze とアプリ スタンバイによって電池寿命が改善しました。

Doze は Android Nougat と Wear OS において機能拡張されています。画面がオフになるか、常に画面表示モードの状態が長く続くと、Doze の一部が開始され、バックグラウンド タスクが一定期間遅延されることがあります。その後、デバイスの操作が長時間行われないと、通常の Doze が開始されます。

ジョブのスケジュール設定には JobScheduler API を使用する必要があります。これにより、アプリが Doze 対応のコード実行に登録できるようになります。ジョブのスケジュール設定の際に、定期実行などの制約と、接続性やデバイスの充電に関するニーズを選択できます。ジョブの設定は、電池寿命に悪影響を及ぼさない方法で行ってください。ジョブでは JobInfo.Builder オブジェクトを使用して、制約とメタデータを指定する必要があります。たとえば、1 つのタスクに対して以下のメソッドの 1 つ以上を使用します。

  • ネットワークを必要とするタスクのスケジュールを設定するには、setRequiredNetworkType(int networkType) を使用し、NETWORK_TYPE_ANY または NETWORK_TYPE_UNMETERED を指定します。NETWORK_TYPE_UNMETERED は大規模なデータ転送に、NETWORK_TYPE_ANY は小規模なデータ転送に使用します。
  • 充電中にタスクのスケジュールを設定するには、setRequiresCharging(boolean requiresCharging) を使用します。
  • デバイスがタスクを実行していないことを指定するには、setRequiresDeviceIdle(boolean requiresDeviceIdle) を使用します。このメソッドは、バックグラウンド処理や同期の優先度が低い場合に、特に setRequiresCharging とともに使用すると便利です。

低帯域幅の一部のネットワーク(Bluetooth LE など)は従量制とみなされます。

制約を含むスケジュールを設定する

制約を必要とするタスクのスケジュールを設定できます。下の例の JobScheduler オブジェクトは、以下の制約を満たしている場合に MyJobService を有効化します。

  • 定額制ネットワーク
  • デバイスの充電中

ビルダー メソッド setExtras を使用すると、アプリ固有のメタデータのバンドルをジョブ リクエストにアタッチできます。ジョブの実行時に、このバンドルがジョブサービスに提供されます。JobInfo.Builder コンストラクタに渡される MY_JOB_ID の値にご注意ください。この MY_JOB_ID の値は、アプリから提供される識別子です。キャンセルのための後続の呼び出しと、同じ値を使って作成された後続のジョブにより、既存のジョブが更新されます。

Kotlin

    JobInfo.Builder(MY_JOB_ID,
            ComponentName(this, MyJobService::class.java))
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
            .setRequiresCharging(true)
            .setExtras(extras)
            .build()
            .also { jobInfo ->
                (getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler)
                        .schedule(jobInfo)
            }
    

Java

    JobInfo jobInfo = new JobInfo.Builder(MY_JOB_ID,
            new ComponentName(this, MyJobService.class))
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
            .setRequiresCharging(true)
            .setExtras(extras)
            .build();
    ((JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE))
            .schedule(jobInfo);
    

以下に、上のジョブを処理する JobService の実装を示します。ジョブが実行されると、JobParameters オブジェクトが onStartJob メソッドに渡されます。JobParameters オブジェクトを通じて、ジョブ ID の値と、ジョブのスケジュール設定の際に提供されるエクストラのバンドルを取得できます。onStartJob メソッドはメインのアプリスレッドで呼び出されるため、高コストのロジックは別のスレッドで実行する必要があります。例では、バックグラウンドでコードを実行するために AsyncTask が使用されています。タスクが完了したら、jobFinished メソッドを呼び出して、タスクが完了したことを JobScheduler に通知します。

Kotlin

    private class MyJobService : JobService() {

        override fun onStartJob(params: JobParameters): Boolean {
            JobAsyncTask().execute(params)
            return true
        }

        private class JobAsyncTask : AsyncTask<...>() { ... }
    }
    

Java

    public class MyJobService extends JobService {
        @Override public boolean onStartJob(JobParameters params) {
            new JobAsyncTask().execute(params);
            return true;
        }

        private class JobAsyncTask extends AsyncTask