Wear에서 데이터 영역 이벤트 처리

Data Layer API를 호출하면 완료 시 호출의 상태를 수신할 수 있습니다. 또한 Wear OS by Google 네트워크의 어느 곳에서든 앱이 데이터를 변경하여 발생하는 데이터 이벤트를 수신 대기할 수 있습니다.

Data Layer API를 효과적으로 사용하는 예를 보려면 Android DataLayer 샘플 앱을 확인하세요.

데이터 영역 호출의 상태 대기

Data Layer API를 호출(예: DataClient 클래스의 putDataItem 메서드를 사용한 호출)하면 때로 Task<ResultType> 객체가 반환됩니다. Task 객체가 생성되자마자 작업은 백그라운드에서 대기합니다. 이후에 아무것도 하지 않으면 작업이 결국 자동으로 완료됩니다.

그러나 일반적으로 작업이 완료된 후 결과로 무언가를 하게 되므로 Task 객체를 사용하여 결과 상태를 비동기식 또는 동기식으로 기다릴 수 있습니다.

비동기 호출

코드가 기본 UI 스레드에서 실행되고 있다면 Data Layer API 차단 호출을 실행하지 마세요. 작업 완료 시 실행되는 Task 객체에 콜백 메서드를 추가하여 비동기식으로 호출을 실행합니다.

Kotlin

// Using Kotlin function references
task.addOnSuccessListener(::handleDataItem)
task.addOnFailureListener(::handleDataItemError)
task.addOnCompleteListener(::handleTaskComplete)
...
fun handleDataItem(dataItem: DataItem) { ... }
fun handleDataItemError(exception: Exception) { ... }
fun handleTaskComplete(task: Task<DataItem>) { ... }

Java

// Using Java 8 Lambdas.
task.addOnSuccessListener(dataItem -> handleDataItem(dataItem));
task.addOnFailureListener(exception -> handleDataItemError(exception));
task.addOnCompleteListener(task -> handleTaskComplete(task));

다양한 작업의 실행을 체이닝하는 등 다른 가능성을 알아보려면 Task API를 참고하세요.

동기 호출

백그라운드 서비스의 별도 핸들러 스레드에서 코드가 실행 중이라면(예: WearableListenerService에서) 차단 호출을 실행해도 됩니다. 이때 다음과 같이 Task 객체에서 Tasks.await()를 호출할 수 있습니다. 이 호출은 요청이 완료되어 Result 객체를 반환할 때까지 차단합니다. 예를 들면 다음과 같습니다.

참고: 기본 스레드에 있을 때는 이를 호출하지 마세요.

Kotlin

try {
    Tasks.await(dataItemTask).apply {
        Log.d(TAG, "Data item set: $uri")
    }
}
catch (e: ExecutionException) { ... }
catch (e: InterruptedException) { ... }

Java

try {
    DataItem item = Tasks.await(dataItemTask);
    Log.d(TAG, "Data item set: " + item.getUri());
} catch (ExecutionException | InterruptedException e) {
  ...
}

데이터 영역 이벤트 수신 대기

데이터 영역은 휴대기기와 웨어러블 기기 전체에서 데이터를 동기화하고 전송하므로 생성 중인 데이터 항목과 수신되는 메시지 등 중요한 이벤트를 일반적으로 수신 대기해야 합니다.

데이터 영역 이벤트를 수신 대기하기 위한 두 가지 옵션이 있습니다.

이 두 옵션을 사용하여 처리하고자 하는 이벤트에 관한 데이터 이벤트 콜백 메서드를 재정의합니다.

참고: 리스너 구현을 선택할 때는 앱의 배터리 사용량을 고려하세요. WearableListenerService는 앱의 매니페스트에 등록되며 앱이 아직 실행되고 있지 않으면 앱을 실행할 수 있습니다. 앱이 이미 실행되고 있을 때만 이벤트를 수신 대기해야 하면(이는 대화형 애플리케이션에서 일반적임) WearableListenerService는 사용하지 마세요. 대신 라이브 리스너를 등록하세요. 예를 들어 DataClient 클래스의 addListener 메서드를 사용합니다. 이렇게 하면 시스템의 부하를 줄이고 배터리 사용량도 줄일 수 있습니다.

WearableListenerService 사용

일반적으로 웨어러블 앱과 휴대기기 앱에서 모두 WearableListenerService 인스턴스를 만듭니다. 하지만 앱 중 하나의 데이터 이벤트에 관심이 없다면 해당 앱에서는 서비스를 구현하지 않아도 됩니다.

예를 들어 데이터 항목 객체를 설정하고 가져오는 휴대기기 앱과 UI 업데이트를 위해 이러한 업데이트를 수신 대기하는 웨어러블 앱이 있다고 가정하겠습니다. 웨어러블 앱은 데이터 항목을 업데이트하지 않으므로 휴대기기 앱은 웨어러블 앱의 데이터 이벤트를 수신 대기하지 않습니다.

WearableListenerService를 사용하여 수신 대기할 수 있는 일부 이벤트는 다음과 같습니다.

  • onDataChanged(): 데이터 항목 객체가 생성, 삭제 또는 변경될 때마다 시스템은 연결된 모든 노드에서 이 콜백을 트리거합니다.
  • onMessageReceived(): 노드에서 전송된 메시지가 타겟 노드에서 이 콜백을 트리거합니다.
  • onCapabilityChanged(): 앱의 인스턴스가 알리는 기능이 네트워크에서 사용 가능해지면 이벤트가 이 콜백을 트리거합니다. 근처 노드를 찾고 있다면 이 콜백에서 제공된 노드의 isNearby() 메서드를 쿼리할 수 있습니다.

ChannelClient.ChannelCallback에서 onChannelOpened()와 같은 이벤트를 수신 대기할 수도 있습니다.

위의 모든 이벤트는 기본 스레드가 아닌 백그라운드 스레드에서 실행됩니다.

WearableListenerService를 생성하려면 다음 단계를 따르세요.

  1. WearableListenerService를 확장하는 클래스를 만듭니다.
  2. 관심 있는 이벤트(예: onDataChanged())를 수신 대기합니다.
  3. WearableListenerService에 관해 시스템에 알리기 위해 Android 매니페스트에서 인텐트 필터를 선언합니다. 이 선언을 통해 시스템은 필요에 따라 서비스를 결합할 수 있습니다.

다음 예는 간단한 WearableListenerService를 구현하는 방법을 보여줍니다.

Kotlin

private const val TAG = "DataLayerSample"
private const val START_ACTIVITY_PATH = "/start-activity"
private const val DATA_ITEM_RECEIVED_PATH = "/data-item-received"

class DataLayerListenerService : WearableListenerService() {

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onDataChanged: $dataEvents")
        }

        // Loop through the events and send a message
        // to the node that created the data item.
        dataEvents.map { it.dataItem.uri }
                .forEach { uri ->
                    // Get the node ID from the host value of the URI.
                    val nodeId: String = uri.host
                    // Set the data of the message to be the bytes of the URI.
                    val payload: ByteArray = uri.toString().toByteArray()

                    // Send the RPC.
                    Wearable.getMessageClient(this)
                            .sendMessage(nodeId, DATA_ITEM_RECEIVED_PATH, payload)
                }
    }
}

Java

public class DataLayerListenerService extends WearableListenerService {
    private static final String TAG = "DataLayerSample";
    private static final String START_ACTIVITY_PATH = "/start-activity";
    private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received";

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onDataChanged: " + dataEvents);
        }

        // Loop through the events and send a message
        // to the node that created the data item.
        for (DataEvent event : dataEvents) {
            Uri uri = event.getDataItem().getUri();

            // Get the node ID from the host value of the URI.
            String nodeId = uri.getHost();
            // Set the data of the message to be the bytes of the URI.
            byte[] payload = uri.toString().getBytes();

            // Send the RPC.
            Wearable.getMessageClient(this).sendMessage(
                  nodeId,  DATA_ITEM_RECEIVED_PATH, payload);
        }
    }
}

다음 섹션에서는 인텐트 필터를 이 리스너와 함께 사용하는 방법을 설명합니다.

WearableListenerService와 함께 필터 사용

이전 섹션에 나와 있는 WearableListenerService 예의 인텐트 필터는 다음과 같을 수 있습니다.

<service android:name=".DataLayerListenerService" android:exported="true" tools:ignore="ExportedService" >
  <intent-filter>
      <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
      <data android:scheme="wear" android:host="*"
               android:path="/start-activity" />
  </intent-filter>
</service>

이 필터에서 DATA_CHANGED 작업은 특정 이벤트만 앱의 절전 모드를 해제하거나 앱을 시작하도록 이전에 권장되는 BIND_LISTENER 작업을 대체합니다. 이러한 변경으로 시스템 효율성이 개선되고 배터리 소모 및 앱과 관련된 기타 오버헤드가 줄어듭니다. 이 예에서 시계는 /start-activity 데이터 항목을 수신 대기하고 휴대전화는 /data-item-received 메시지 응답을 수신 대기합니다.

표준 Android 필터 매칭 규칙이 적용됩니다. 매니페스트당 여러 서비스, 서비스당 여러 인텐트 필터, 필터당 여러 작업, 필터당 여러 데이터 스탠자를 지정할 수 있습니다. 와일드 카드 호스트 또는 특정 호스트에서 필터를 매칭할 수 있습니다. 와일드 카드 호스트에서 매칭하려면 host="*"를 사용하고, 특정 호스트에서 매칭하려면 host=<node_id>를 지정하세요.

리터럴 경로 또는 경로 접두어를 매칭할 수도 있습니다. 이렇게 하려면 와일드 카드 또는 특정 호스트를 지정해야 합니다. 그러지 않으면 개발자가 지정하는 경로를 시스템에서 무시합니다.

Wear OS에서 지원하는 필터 유형에 관한 자세한 내용은 WearableListenerService의 API 참조 문서를 확인하세요.

데이터 필터 및 매칭 규칙에 관한 자세한 내용은 <data> 매니페스트 요소의 API 참조 문서를 확인하세요.

인텐트 필터를 매칭할 때 기억해야 할 중요한 두 가지 규칙은 다음과 같습니다.

  • 인텐트 필터에 스키마가 지정되어 있지 않으면 시스템은 다른 모든 URI 속성을 무시합니다.
  • 필터에 호스트가 지정되어 있지 않으면 시스템은 모든 경로 속성을 무시합니다.

라이브 리스너 사용

사용자가 앱과 상호작용할 때 앱이 데이터 영역 이벤트에만 관심을 두는 경우 모든 데이터 변경사항을 처리하기 위한 장기 실행 서비스가 필요하지 않을 수 있습니다. 그러한 경우 다음 인터페이스 중 하나 이상을 구현하여 활동의 이벤트를 수신 대기할 수 있습니다.

데이터 이벤트를 수신 대기하는 활동을 만들려면 다음 단계를 따르세요.

  1. 원하는 인터페이스를 구현합니다.
  2. onCreate() 또는 onResume() 메서드에서 Wearable.getDataClient(this).addListener(), MessageClient.addListener(), CapabilityClient.addListener() 또는 ChannelClient.registerChannelCallback()를 호출하여 활동이 데이터 영역 이벤트를 수신 대기하려는 것을 Google Play 서비스에 알립니다.
  3. onStop() 또는 onPause()에서 DataClient.removeListener(), MessageClient.removeListener(), CapabilityClient.removeListener() 또는 ChannelClient.unregisterChannelCallback()를 사용하여 리스너의 등록을 취소합니다.
  4. 활동이 특정 경로 접두어가 있는 이벤트에만 관심이 있다면 현재 애플리케이션 상태와 관련이 있는 데이터만 수신하도록 적절한 접두어 필터를 사용하여 리스너를 추가할 수 있습니다.
  5. 구현한 인터페이스에 따라 onDataChanged(), onMessageReceived(), onCapabilityChanged() 또는 ChannelClient.ChannelCallback의 메서드를 구현합니다. 이러한 메서드는 기본 스레드에서 호출됩니다. 또는 WearableOptions를 사용하여 맞춤 Looper를 지정할 수 있습니다.

다음은 DataClient.OnDataChangedListener를 구현하는 예입니다.

Kotlin

class MainActivity : Activity(), DataClient.OnDataChangedListener {

    public override fun onResume() {
        Wearable.getDataClient(this).addListener(this)
    }

    override fun onPause() {
        Wearable.getDataClient(this).removeListener(this)
    }

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        dataEvents.forEach { event ->
            if (event.type == DataEvent.TYPE_DELETED) {
                Log.d(TAG, "DataItem deleted: " + event.dataItem.uri)
            } else if (event.type == DataEvent.TYPE_CHANGED) {
                Log.d(TAG, "DataItem changed: " + event.dataItem.uri)
            }
        }
    }
}

Java

public class MainActivity extends Activity implements DataClient.OnDataChangedListener {

    @Override
    public void onResume() {
        Wearable.getDataClient(this).addListener(this);
    }

    @Override
    protected void onPause() {
        Wearable.getDataClient(this).removeListener(this);
    }

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        for (DataEvent event : dataEvents) {
            if (event.getType() == DataEvent.TYPE_DELETED) {
                Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
            } else if (event.getType() == DataEvent.TYPE_CHANGED) {
                Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri());
            }
        }
    }
}

라이브 리스너와 함께 필터 사용

앞서 언급했듯이 매니페스트 기반 WearableListenerService 객체의 인텐트 필터를 지정할 수 있는 것처럼, Wearable API를 통해 라이브 리스너를 등록할 때도 인텐트 필터를 사용할 수 있습니다. API 기반 라이브 리스너와 매니페스트 기반 리스너에 모두 동일한 규칙이 적용됩니다.

공통된 패턴은 활동의 onResume() 메서드에서 특정 경로 또는 경로 접두어로 리스너를 등록하고, 활동의 onPause() 메서드에서 리스너를 삭제하는 것입니다. 이러한 방식으로 리스너를 구현하면 앱이 이벤트를 좀 더 선택적으로 수신할 수 있으므로 앱의 디자인과 효율성이 개선됩니다.