Wear에서 네트워크 액세스 및 동기화

Wear OS by Google을 사용하면 Android 또는 iOS 스마트폰에 액세스하지 않고도 시계가 네트워크와 직접 통신할 수 있습니다. 이러한 직접 네트워크 액세스 기능 덕분에 네트워크에 연결하기 위해 Wear 1.x에서 Data Layer API를 사용할 필요가 없습니다.

다음의 관련 리소스를 참조하세요.

네트워크 액세스

Wear OS 앱은 네트워크 요청을 할 수 있습니다. 시계가 스마트폰과 블루투스로 연결되어 있으면 시계의 네트워크 트래픽은 일반적으로 스마트폰을 통해 프록시 처리됩니다. 그러나 전화를 사용할 수 없는 경우 하드웨어에 따라 Wi-Fi 및 셀룰러 네트워크가 사용됩니다. Wear 플랫폼은 네트워크 간의 전환을 처리합니다.

HTTP, TCP, UDP와 같은 프로토콜을 사용할 수 있지만, android.webkit API(CookieManager 클래스 포함)는 사용할 수 없습니다. 요청 및 응답에서 헤더를 읽고 써서 쿠키를 사용할 수 있습니다.

또한 다음을 사용하는 것이 좋습니다.

  • 일정한 간격의 폴링을 포함한 비동기 작업용 JobScheduler API(아래에서 설명함)
  • 특정 네트워크 유형에 연결해야 하는 경우 Multi-networking API(다중 네트워크 연결 참조)

고대역폭 네트워크 액세스

Wear OS 플랫폼은 최상의 전반적인 사용자 환경을 제공하기 위해 네트워크 연결을 관리합니다. 이 플랫폼은 두 가지 요소의 균형을 유지함으로써 기본적인 활성 네트워크를 선택합니다.

  • 긴 배터리 수명이 필요
  • 네트워크 대역폭이 필요

배터리 보존이 더 중요한 경우, 활성 네트워크에서는 대용량 파일 전송이나 미디어 스트리밍과 같이 고대역폭이 필요한 네트워크 작업을 수행하기에 대역폭이 부족할 수 있습니다.

이 섹션에서는 앱에서 필수 네트워크 대역폭을 사용할 수 있도록 하기 위해 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
        }
    }
    

자바

    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를 사용하여 무제한의 고대역폭 네트워크를 요청할 수 있습니다. 단일 네트워크 요청을 사용해 무제한의 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)
    

자바

    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)
    

자바

    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
                }
            }
        }
    }
    

자바

    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
        }
    }
    

자바

    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"))
    

자바

    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 클라우드 메시징(GCM)을 대신하는 Firebase 클라우드 메시징(FCM)을 직접 사용할 수 있습니다. FCM은 Wear 2.0에서 지원되지만, GCM은 Wear 2.0에서 지원되지 않습니다.

네트워크 액세스 또는 FCM용 API 중에서 Wear OS에만 해당되는 것은 없습니다. 네트워크에 연결클라우드 메시징에 관한 기존 문서를 참조하세요.

FCM은 잠자기 기능과 잘 작동하며, 시계에 알림을 보낼 때 사용하면 좋습니다.

Wear 앱이 실행될 때 기기의 등록 토큰을 수집하여 FCM의 메시지를 제공하세요. 그런 다음 서버가 FCM REST 엔드포인트로 메시지를 보낼 때 목적지의 일부로 토큰을 포함하세요. FCM은 토큰에 의해 식별된 기기로 메시지를 전송합니다.

FCM 메시지는 JSON 형식이며 다음 페이로드 중 하나 또는 둘 다를 포함할 수 있습니다.

  • 알림 페이로드. 시계가 알림 페이로드를 수신하면 알림 스트림에서 사용자에게 직접 데이터가 표시됩니다. 사용자가 알림을 탭하면 앱이 시작됩니다.
  • 데이터 페이로드. 페이로드에는 맞춤형 키/값 쌍의 집합이 있습니다. 페이로드는 Wear 앱에 데이터로 전달됩니다.

페이로드에 관한 자세한 내용과 예는 FCM 메시지 정보를 참조하세요.

기본적으로 알림은 스마트폰 앱에서 시계로 브리징(공유)됩니다. 독립 실행형 Wear 앱과 그에 상응하는 스마트폰 앱이 있는 경우 중복된 알림이 발생할 수 있습니다. 예를 들어, 스마트폰과 시계에서 모두 수신된 동일한 FCM 알림이 두 기기에 별도로 표시될 수 있습니다.

백그라운드 서비스 사용

백그라운드 작업이 올바르게 실행되도록 하려면 잠자기 기능을 고려해야 합니다. Android 6.0에서 잠자기 및 앱 대기 기능으로 배터리 수명이 증대되었습니다.

잠자기 기능은 Android Nougat 및 Wear OS에서 향상되었습니다. 화면이 꺼지거나 오랜 시간 동안 대기 모드에 머물게 되면 잠자기의 하위 집합이 발생할 수 있고 특정 기간에 백그라운드 작업이 지연될 수 있습니다 나중에 오랜 시간 동안 기기에 변화가 없으면 일반 잠자기가 발생합니다.

JobScheduler API로 작업을 예약해야 하는데, 이를 통해 앱에서 잠자기 없는 코드 실행을 등록할 수 있습니다. 작업을 예약할 때 정기적인 실행과 연결 또는 기기 충전 요구 같은 제약 조건을 선택할 수 있습니다. 배터리 수명에 부정적인 영향을 주지 않는 방식으로 작업을 구성하세요. 제약 조건과 메타데이터를 제공하려면 작업에 관해 다음 중 하나 이상의 메서드와 함께 JobInfo.Builder 객체를 사용해야 합니다.

  • 네트워킹이 필요한 작업을 예약하려면 setRequiredNetworkType(int networkType)을 사용하고 NETWORK_TYPE_ANY 또는 NETWORK_TYPE_UNMETERED를 지정합니다. NETWORK_TYPE_UNMETERED는 대규모 데이터 전송에 사용하고 NETWORK_TYPE_ANY는 소규모 전송에 사용합니다.
  • 충전 중에 작업을 예약하려면 setRequiresCharging(boolean requiresCharging)을 사용합니다.
  • 기기가 작업에 관해 유휴 상태임을 지정하려면 setRequiresDeviceIdle(boolean requiresDeviceIdle)을 사용합니다. 특히 setRequiresCharging과 함께 사용할 때, 이 메서드는 우선순위가 낮은 백그라운드 작업 또는 동기화에 유용합니다.

블루투스 LE와 같은 일부 저대역폭 네트워크는 제한이 있는 것으로 간주됩니다.

제약 조건을 사용하여 예약

제약 조건이 필요한 작업을 예약할 수 있습니다. 아래의 예제에서 JobScheduler 객체는 다음 제약 조건이 충족될 때 MyJobService를 활성화합니다.

  • 무제한 네트워킹
  • 기기 충전

앱 관련 메타데이터의 번들을 작업 요청에 첨부하기 위해 빌더 메서드 setExtras를 사용할 수 있습니다. 작업이 실행되면 이 번들이 작업 서비스에 제공됩니다. MY_JOB_ID 값이 JobInfo.Builder 생성자에 전달됩니다. 이 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)
            }
    

자바

    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<...>() { ... }
    }
    

자바

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

        private class JobAsyncTask extends AsyncTask