영구 데이터 동기화

이 문서에서는 Wear OS 기기와 휴대기기 간에 데이터를 동기화하는 방법을 설명합니다.

네트워크에서 직접 데이터 전송 및 동기화

네트워크와 직접 통신하는 Wear OS 앱을 빌드합니다. 모바일 개발에 사용하는 것과 동일한 API를 사용하지만 몇 가지 Wear OS만의 차이점을 염두에 두어야 합니다.

Wear OS Data Layer API를 사용하여 데이터 동기화

DataClient는 구성요소가 DataItem 또는 Asset를 읽거나 여기에 쓸 수 있는 API를 노출합니다.

기기에 연결되어 있지 않은 상태에서 데이터 항목 및 애셋을 설정할 수 있습니다. 기기가 네트워크 연결을 설정할 때 동기화됩니다. 이 데이터는 내 앱에만 공개되며 다른 기기에 있는 내 앱에서만 액세스할 수 있습니다.

  • DataItem는 Wear OS 네트워크의 모든 기기에서 동기화됩니다. 일반적으로 크기가 작습니다.

  • Asset를 사용하여 이미지와 같은 더 큰 객체를 전송합니다. 시스템은 이미 전송된 애셋을 추적하고 자동으로 중복 삭제를 실행합니다.

서비스에서 이벤트 수신 대기

WearableListenerService 클래스를 확장합니다. 시스템은 기본 WearableListenerService의 수명 주기를 관리하며, 데이터 항목이나 메시지를 전송해야 하면 서비스에 바인딩하고 필요한 작업이 없으면 서비스 바인딩을 해제합니다.

활동의 이벤트 수신 대기

OnDataChangedListener 인터페이스를 구현합니다. 사용자가 앱을 적극적으로 사용하는 경우에만 변경사항을 수신 대기하려면 WearableListenerService 대신 이 인터페이스를 사용하세요.

데이터 전송

블루투스 전송을 통해 다른 기기의 음성 녹음 파일과 같은 바이너리 대형 객체를 전송하려면 Asset를 데이터 항목에 연결한 후 데이터 항목을 복제된 데이터 스토어에 넣으면 됩니다.

재전송을 방지하고 블루투스 대역폭을 보존하기 위해 애셋은 데이터 캐싱을 자동으로 처리합니다. 일반적인 패턴은 휴대기기 앱이 이미지를 다운로드하고, 웨어러블에 표시하기 위한 적절한 크기로 축소한 다음, 웨어러블 앱에 애셋으로 전송하는 것입니다. 다음 예는 이 패턴을 보여줍니다.

참고: 데이터 항목의 크기는 이론적으로 100KB로 제한되지만 실제로는 더 큰 데이터 항목을 사용할 수 있습니다. 큰 데이터 항목의 경우 고유한 경로로 데이터를 구분하고 모든 데이터에 단일 경로를 사용하지 않아야 합니다. 대용량 애셋을 전송하면 대부분의 경우 사용자 환경에 영향을 미치므로 앱을 테스트하여 대용량 애셋을 전송할 때 성능이 저하되지 않는지 확인할 수 있습니다.

애셋 전송

Asset 클래스의 create...() 메서드 중 하나를 사용하여 애셋을 만듭니다. 비트맵을 바이트 스트림으로 변환한 후 다음 샘플과 같이 createFromBytes()를 호출하여 애셋을 만듭니다.

Kotlin

private fun createAssetFromBitmap(bitmap: Bitmap): Asset =
    ByteArrayOutputStream().let { byteStream ->
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream)
        Asset.createFromBytes(byteStream.toByteArray())
    }

Java

private static Asset createAssetFromBitmap(Bitmap bitmap) {
    final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
    return Asset.createFromBytes(byteStream.toByteArray());
}

이제 DataMap 또는 PutDataRequest에서 putAsset() 메서드를 사용하여 데이터 항목에 애셋을 연결합니다. 그런 다음 다음 샘플과 같이 putDataItem() 메서드를 사용하여 데이터 항목을 Datastore에 넣습니다.

다음 샘플에서는 PutDataRequest를 사용합니다.

Kotlin

val asset: Asset = BitmapFactory.decodeResource(resources, R.drawable.image).let { bitmap ->
    createAssetFromBitmap(bitmap)
}
val request: PutDataRequest = PutDataRequest.create("/image").apply {
    putAsset("profileImage", asset)
}
val putTask: Task<DataItem> = Wearable.getDataClient(context).putDataItem(request)

Java

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
Asset asset = createAssetFromBitmap(bitmap);
PutDataRequest request = PutDataRequest.create("/image");
request.putAsset("profileImage", asset);
Task<DataItem> putTask = Wearable.getDataClient(context).putDataItem(request);

다음 샘플에서는 PutDataMapRequest를 사용합니다.

Kotlin

val asset: Asset = BitmapFactory.decodeResource(resources, R.drawable.image).let { bitmap ->
    createAssetFromBitmap(bitmap)
}
val request: PutDataRequest = PutDataMapRequest.create("/image").run {
    dataMap.putAsset("profileImage", asset)
    asPutDataRequest()
}
val putTask: Task<DataItem> = Wearable.getDataClient(context).putDataItem(request)

Java

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
Asset asset = createAssetFromBitmap(bitmap);
PutDataMapRequest dataMap = PutDataMapRequest.create("/image");
dataMap.getDataMap().putAsset("profileImage", asset);
PutDataRequest request = dataMap.asPutDataRequest();
Task<DataItem> putTask = Wearable.getDataClient(context).putDataItem(request);

애셋 수신

애셋이 생성되면 연결의 다른 쪽에서 애셋을 읽고 추출해야 할 수 있습니다. 다음은 애셋 변경을 감지하고 애셋을 추출하기 위해 콜백을 구현하는 방법의 예입니다.

Kotlin

override fun onDataChanged(dataEvents: DataEventBuffer) {
    dataEvents
            .filter { it.type == DataEvent.TYPE_CHANGED && it.dataItem.uri.path == "/image" }
            .forEach { event ->
                val bitmap: Bitmap? = DataMapItem.fromDataItem(event.dataItem)
                        .dataMap.getAsset("profileImage")
                        .let { asset -> loadBitmapFromAsset(asset) }
                // Do something with the bitmap
            }
}

fun loadBitmapFromAsset(asset: Asset): Bitmap? {
    // Convert asset into a file descriptor and block until it's ready
    val assetInputStream: InputStream? =
            Tasks.await(Wearable.getDataClient(context).getFdForAsset(asset))
            ?.inputStream

    return assetInputStream?.let { inputStream ->
        // Decode the stream into a bitmap
        BitmapFactory.decodeStream(inputStream)
    } ?: run {
        Log.w(TAG, "Requested an unknown Asset.")
        null
    }
}

Java

@Override
public void onDataChanged(DataEventBuffer dataEvents) {
  for (DataEvent event : dataEvents) {
    if (event.getType() == DataEvent.TYPE_CHANGED &&
        event.getDataItem().getUri().getPath().equals("/image")) {
      DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
      Asset profileAsset = dataMapItem.getDataMap().getAsset("profileImage");
      Bitmap bitmap = loadBitmapFromAsset(profileAsset);
      // Do something with the bitmap
    }
  }
}

public Bitmap loadBitmapFromAsset(Asset asset) {
    if (asset == null) {
        throw new IllegalArgumentException("Asset must be non-null");
    }
    // Convert asset into a file descriptor and block until it's ready
    InputStream assetInputStream =
        Tasks.await(Wearable.getDataClient(context).getFdForAsset(asset))
            .getInputStream();
    if (assetInputStream == null) {
        Log.w(TAG, "Requested an unknown Asset.");
        return null;
    }
    // Decode the stream into a bitmap
    return BitmapFactory.decodeStream(assetInputStream);
}

자세한 내용은 GitHub의 DataLayer 샘플 프로젝트를 참고하세요.