안드로이드 앱을 만들다보면 @Serializable
을 사용하는 경우가 굉장히 많다.
하지만 직렬화 인터페이스는 Serializable
만 있는 것이 아니다.
Parcelable
도 존재한다.
비교에 들어가기 전 두 인터페이스의 차이를 간단하게 설명하자면
Serializable
은 리플렉션을 사용해 직렬화를 수행하고
Parcelable
은 그렇지 않아 속도가 빠르다는 이점이 있다.
그렇다면 무조건 Parcelable
을 사용하는 것이 좋지 않을까 싶지만 그렇지 않다.
이것은 두 인터페이스의 용도가 다르기 때문이다.
일반적으로 Serializable
를 dto나 Entity에서 흔히 사용하는데 Serializable
은 표준 자바 인터페이스로 서버와 안드로이드 클라이언트 간 데이터 모델을 공유하기 쉽다.
또한, Retrofit
같은 네트워크 통신 라이브러리들이 JSON 변환을 기본적으로 지원하여 서버와의 통신에 사용하기 편리하다.
즉, 두 인터페이스는 용도가 다르다.
Serializable
: 서버와의 통신, Java 표준 인터페이스라서 서버 개발자와 안드로이드 개발자가 동일한 모델을 공유 가능Parcelable
: 안드로이드 SDK 특화, 안드로이드 컴포넌트 간에 데이터를 효율적으로 전달할 때 사용, Intent에 데이터를 추가할 때, Bundle을 사용할 때 사용하지만 안드로이드 컴포넌트 간에 데이터 전달에도 편의상 Serializable
을 사용하는 경우가 많다.
본인도 그냥 Serializable
하나만 써도 되지 않을까 싶은 의문이 들어 두 인터페이스의 성능을 비교해보고자 한다.
이미 이것을 비교한 좋은 글이 있다.
하지만 이것은 2018년에 작성되었고 코틀린, Compose환경도 아닌만큼 Parcelize
가 추가된 현재는 어떤 차이가 있는지 알아보자.
@Serializable
data class Person(
val name: String,
val age: Int
)
val person = Person(SAMPLE_NAME, SAMPLE_AGE)
val timeNs = measureNanoTime {
val serialized = json.encodeToString(person)
val restored = json.decodeFromString<Person>(serialized)
}
val timeMs = timeNs / 1_000_000.0
Log.d("Test", "1. JSON Serializable: $timeNs ns (${String.format("%.3f", timeMs)} ms)")
위와 같이 간단한 data class를 직렬화 역직렬화를 해보자.
테스트 결과 다음과 같은 시간이 걸렸다.
@Parcelize
data class Person(
val name: String,
val age: Int
) : Parcelable
동일한 data class를 @Parcelize
어노테이션으로만 바꿔준다.
val person = Person(SAMPLE_NAME, SAMPLE_AGE)
val timeNs = measureNanoTime {
val parcel = Parcel.obtain()
parcel.writeParcelable(person, 0)
parcel.setDataPosition(0)
val restored = parcel.readParcelable<Person>(Person::class.java.classLoader)!!
parcel.recycle()
}
val timeMs = timeNs / 1_000_000.0
Log.d("Test", "2. Parcelize Parcelable: $timeNs ns (${String.format("%.3f", timeMs)} ms)")
테스트 결과 아래와 같은 시간이 걸렸다.
유의미할 정도로 큰 속도 차이가 존재한다!
이전의 Parcelable
인터페이스는 직접 직렬화/역직렬화 코드를 작성해야했지만 Parcelize
는 그것도 필요없다.
@Serializable
data class Person(
val name: String,
val age: Int
) {
fun writeObject(out: ObjectOutputStream) {
out.writeUTF(name)
out.writeInt(age)
}
fun readObject(input: ObjectInputStream): Person {
val name = input.readUTF()
val age = input.readInt()
return Person(name, age)
}
}
이번에는 직접 직렬화/역직렬화를 한 Serializable
이다.
val person = Person(SAMPLE_NAME, SAMPLE_AGE)
val timeNs = measureNanoTime {
val byteOut = ByteArrayOutputStream()
val objectOut = ObjectOutputStream(byteOut)
person.writeObject(objectOut)
objectOut.flush()
val bytes = byteOut.toByteArray()
val byteIn = ByteArrayInputStream(bytes)
val objectIn = ObjectInputStream(byteIn)
val restored = person.readObject(objectIn)
}
val timeMs = timeNs / 1_000_000.0
Log.d("Test", "3. Custom JSON Serializable: $timeNs ns (${String.format("%.3f", timeMs)} ms)")
동일하게 직렬화/역직렬화를 수행해준다.
기존의
@Serializable
을 사용한 것보다 훨씬 빠르다.
하지만 이 또한 @Parcelize
에 비하면 느리다.
현재는 Parcelable
인터페이스도 직접 로직을 구현할 필요가 없어진만큼 안드로이드 컴포넌트간의 데이터 전달에는 @Parcelize
를 사용하는 것이 성능상으로 굉장한 이점을 갖기에 되도록이면 @Parcelize
를 사용해보자.