면접 질문 목록으로 가기
면접 질문실전 질문꼬리 질문

안드로이드 IPC에서의 Parcel과 Parcelable

skydovesJaewoong Eum (skydoves)||10분 소요

안드로이드 IPC에서의 Parcel과 Parcelable

안드로이드의 프로세스 간 통신(IPC, Inter-Process Communication)은 Parcel 클래스를 저수준 전송 컨테이너로 사용하며, Parcelable 인터페이스를 통해 객체가 자기 자신을 Parcel로 직렬화하는 계약(contract)을 정의합니다. Parcel이 메모리를 어떻게 관리하는지, 데이터를 어떻게 마샬링(marshaling)하고 언마샬링(unmarshaling)하는지, 그리고 왜 영속적(persistent) 저장이 아닌 일시적 IPC 전용으로 제한되는지를 이해하는 것은 프로세스 경계를 넘나드는 안드로이드 컴포넌트를 다루는 데 필수적입니다. 면접에서도 ParcelParcelable의 내부 동작 원리를 묻는 질문이 자주 출제되므로, 이번 기회에 확실히 정리해 두시면 좋겠습니다.

이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.

  • Parcel이 무엇이며, IPC 데이터 컨테이너로서 어떻게 동작하는지 설명할 수 있습니다.
  • Parcelable 객체의 마샬링과 언마샬링 과정을 이해하고 설명할 수 있습니다.
  • Parcel을 영속적 저장에 사용하면 안 되는 이유를 파악할 수 있습니다.
  • 타입별 읽기/쓰기 메서드와 범용 객체 처리 방식의 차이를 구분하실 수 있습니다.
  • 프로덕션 코드에서 Parcelable을 구현할 때의 모범 사례를 적용하실 수 있습니다.

Parcel 컨테이너

Parcel은 안드로이드의 Binder IPC 메커니즘을 통해 전송할 수 있는 가변(mutable) 데이터 컨테이너입니다. 내부적으로는 C++ Parcel 객체를 래핑하며, 이 객체가 연속된 메모리 블록을 관리합니다. 데이터는 순차적으로 기록되고 동일한 순서로 읽힙니다. Parcel은 읽기/쓰기 연산이 발생할 때마다 전진하는 위치 커서(position cursor)를 유지하고 있습니다.

네이티브 백업 메모리는 Java 바이트 배열이 아닙니다. Binder 드라이버 커널 모듈이 직접 할당하고 관리하는 메모리이며, 가능한 경우 프로세스 간 제로 카피(zero-copy) 전송을 지원합니다. Parcel.writeInt(42)를 호출하면, 값이 현재 위치의 네이티브 버퍼에 직접 기록되고 위치가 4바이트만큼 전진합니다. 박싱(boxing)도, 중간 버퍼도, 별도의 직렬화 프레임워크도 개입하지 않습니다.

ParcelIBinder 참조 기록도 지원합니다. 이를 통해 한 프로세스가 다른 프로세스의 라이브 객체에 대한 핸들을 보유할 수 있게 됩니다. 안드로이드 서비스가 프로세스 경계를 넘어 API를 노출하는 원리가 바로 이것입니다. Binder 드라이버는 이러한 참조를 IPC 전송에도 유효한 커널 레벨 핸들로 변환해 줍니다.

Parcel 클래스는 기록된 데이터의 현재 바이트 크기를 확인하는 dataSize()와 할당된 버퍼 용량을 확인하는 dataCapacity() 메서드도 제공합니다. 기록된 데이터가 현재 용량을 초과하면 Parcel이 자동으로 내부 버퍼를 확장하는데, 이 재할당은 네이티브 코드에서 처리되므로 일반적으로 빠릅니다. 다만 타이트 루프(tight loop) 안에서 반복적으로 확장이 발생하면 메모리 단편화가 생길 수 있습니다. 기록할 데이터의 대략적인 크기를 알고 있다면 setDataCapacity()로 미리 용량을 할당하여 반복적인 리사이징을 방지하는 것이 좋습니다.

Parcel에 데이터 기록하기

Parcel API는 모든 원시 타입(primitive type)과 여러 복합 타입에 대한 타입별 메서드를 제공합니다.

val parcel = Parcel.obtain()
parcel.writeInt(42)
parcel.writeString("hello")
parcel.writeLong(System.currentTimeMillis())
parcel.writeFloat(3.14f)
parcel.writeTypedList(listOf(user1, user2))

각 메서드는 대응하는 읽기 메서드가 기대하는 형식으로 데이터를 기록합니다. 기록자(writer)와 독자(reader) 사이에서 순서와 타입이 정확히 일치해야 합니다. Int를 기록한 위치에서 String을 읽으면 잘못된 데이터가 반환되거나 크래시가 발생하는데, 이는 Parcel이 값 옆에 타입 정보를 별도로 저장하지 않기 때문입니다. 타입 메타데이터를 생략하여 페이로드 크기를 줄이고 전송 속도를 높이는 의도적인 트레이드오프(trade-off)입니다.

Parcel은 배열과 리스트를 기록하는 메서드도 제공합니다. writeStringList()는 길이 접두사(length prefix) 뒤에 각 문자열 요소를 기록하며, writeTypedList()Parcelable 객체 리스트의 각 요소에 대해 writeToParcel()을 순차적으로 호출하여 기록합니다. 읽기 측에서는 createStringArrayList()createTypedArrayList()가 새 리스트를 할당하고 Parcel 데이터로 채웁니다. 이러한 리스트 메서드는 null 리스트를 처리할 때 길이 값으로 -1이라는 센티널 값(sentinel value)을 기록하며, 읽기 메서드는 이를 감지하여 null을 반환합니다.

이 면접 질문은 구독자 전용입니다

Dove Letter를 구독하시면 안드로이드, 코틀린 개발 관련 독점 면접 질문의 전체 내용을 볼 수 있습니다.

구독하기
면접 질문 목록으로 가기