Wear OS Data Layer - java.lang.IllegalArgumentException: Buffer is closed 나의 경험담 2023. 12. 2. 23:21

안드로이드 와치 Wear OS 데이터 전송할 때 발생하는 java.lang.IllegalArgumentException: Buffer is closed 예외 해결 방법.

문제 상황

Wear OS에서 데이터 전송 및 동기화 가이드 대로 코드를 작성해도 예외가 발생한다.

2023-11-28 00:11:37.243 15775-15920/? D/DataLayerService: onDataChanged
2023-11-28 00:11:37.268 15775-15775/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.your_app, PID: 15775
    java.lang.IllegalArgumentException: Buffer is closed.
        at com.google.android.gms.common.data.DataHolder.zae(com.google.android.gms:play-services-base@@18.0.1:4)
        at com.google.android.gms.common.data.DataHolder.getString(com.google.android.gms:play-services-base@@18.0.1:1)
        at com.google.android.gms.common.data.DataBufferRef.getString(com.google.android.gms:play-services-base@@18.0.1:1)
        at com.google.android.gms.wearable.internal.zzdk.getUri(com.google.android.gms:play-services-wearable@@18.0.0:1)
        at com.google.android.gms.wearable.DataMapItem.<init>(com.google.android.gms:play-services-wearable@@18.0.0:1)
        at com.google.android.gms.wearable.DataMapItem.fromDataItem(com.google.android.gms:play-services-wearable@@18.0.0:2)
        at com.example.your_app.DataLayerListenerService$onDataChanged$1$1.invokeSuspend(DataLayerListenerService.kt:38)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at android.os.Handler.handleCallback(Handler.java:942)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7962)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:550)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952)
    	Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@474d8fd, Dispatchers.Main.immediate]

문제가 발생한 코드는 아래와 같다. Android DataLayer 샘플 코드 거의 그대로 사용 중인데도 문제가 발생한다.

class DataLayerListenerService : WearableListenerService() {
...
    override fun onDataChanged(dataEvents: DataEventBuffer) {
        Log.d(TAG, "onDataChanged")
        super.onDataChanged(dataEvents)
        // Do additional work for specific events
        dataEvents.forEach { dataEvent ->
            when (dataEvent.type) {
                DataEvent.TYPE_CHANGED -> {
                    when (dataEvent.dataItem.uri.path) {
                        IMAGE_PATH -> {
                            scope.launch {
                                val filename = DataMapItem.fromDataItem(dataEvent.dataItem)
                                    .dataMap
                                    .getString(FILENAME_KEY)
                                filename?.let { filename ->
                                    val bitmap = loadBitmap(
                                        DataMapItem.fromDataItem(dataEvent.dataItem)
                                            .dataMap
                                            .getAsset(IMAGE_KEY)
                                    )
                                    bitmap?.let {
                                        saveBitmapToJpeg(
                                            it,
                                            applicationContext.dataDir,
                                            "$FILE_DIR${filename}"
                                        )
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

해결 방법

딱 봐도 Buffer가 내가 쓰려는데 닫혀버리는 것 같은데, 복사를 해서 써야하나 해서 수정해봐도 잘 안되는데, 복사하는 방법을 가이드 하고 있다.

데이터 관리를 com.google.android.gms.wearable.DataMapItem.fromDataItem 여기서 하니까 google service lib 쪽으로 확인해서 해결 방안을 찾았다.

Google Play services - WearableListenerService 에 보면 DataEventFreezable.freeze()를 사용해서 코루틴 스코프에서 버퍼를 계속 사용할 수 있도록 해야 한다고 한다.

Freezable.freeze() 의 사용법은 다음과 같다.

문제의 코드를 살짝 고쳐서 해결했다. val frozenEvent = dataEvent.freeze() 해서 그 반환값을 사용하도록 수정.

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        Log.d(TAG, "onDataChanged")
        super.onDataChanged(dataEvents)
        // Do additional work for specific events
        dataEvents.forEach { dataEvent ->
            val frozenEvent = dataEvent.freeze()
            when (frozenEvent.type) {
                DataEvent.TYPE_CHANGED -> {
                    when (frozenEvent.dataItem.uri.path) {
                        IMAGE_PATH -> {
                            scope.launch {
                                val filename = DataMapItem.fromDataItem(frozenEvent.dataItem)
                                    .dataMap
                                    .getString(FILENAME_KEY)
                                filename?.let { filename ->
                                    val bitmap = loadBitmap(
                                        DataMapItem.fromDataItem(frozenEvent.dataItem)
                                            .dataMap
                                            .getAsset(IMAGE_KEY)
                                    )
                                    bitmap?.let {
                                        saveBitmapToJpeg(
                                            it,
                                            applicationContext.dataDir,
                                            "$FILE_DIR${filename}"
                                        )
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

별 문제 아니였지만 외부 코드(구글 플레이 서비스 패키지)에서 문제가 나왔는데, 샘플 가이드가 안되어 있어서 검색하는데 시간이 좀 걸렸다. 지피티도 모름. 문제가 되는 지점을 명확히하고 단계별로 범위를 좁혀서 디버깅 + 검색!

댓글