Serializable, Parcelable 그리고 @Serializable, @Parcelize.

모두 “직렬화”에 관련된 키워드이지만 각각의 역할이 다르다. 각각 키워드의 세부 동작에 대해 알아보았다.

직렬화란?

네 가지 모두 직렬화에 관련된 키워드이다. 직렬화를 간단히 말하면, 객체를 파일 혹은 네트워크 등 외부 환경에 전송/수신 하기 위해 필요한 과정이다. 이를 통해 전송 가능한 바이트 스트림으로 만들수 있다.

interface Serializable

  • 자바(코틀린 X, 안드로이드 X)의 표준 인터페이스이다: java.io.Serializable
  • 구현해야할 메소드가 없다. 어떠한 메소드도 가지지 않는 “마커 인터페이스” 이다.
  • 이 인터페이스를 선언하기만 해도, JVM 에서 해당 객체를 저장하거나, 다른 서버로 저장할 수 있다. ObjectOutputStream 을 사용해서 객체를 직렬화 할 수 있습니다.
data class Member(
	val id: String
): Serializable

oos.writeObject(Member("ID")) // Serializable 이 없으면 에러
  • 클래스가 Serializable 을 구현(Implements) 했지만, Serializable 하지 않은 필드가 있는 경우는 직렬화 불가능 하다.
  • 상위 클래스가 직렬화 되지 않은 상태에서 서브 클래스가 Serializable 을 구현하더라도 상위 클래스의 필드가 직렬화 되지 않는다.
  • 위의 두 조건을 위배되지 않는 Serializable 을 구현한 클래스의 객체는 Java 의 리플렉션을 이용해 처리된다.
    • 런타임에 많은 오버헤드가 발생하고 성능 이슈가 있다.
  • Java 의 고유한 바이너리 형식으로 직렬화 된다.

interface Parcelable

  • 안드로이드 SDK 의 인터페이스. 안드로이드만을 위한 직렬화를 지원: android.os.Parcelable
  • implements 해야 하는 코드가 많다(보일러플레이트 코드)
import android.os.Parcel
import android.os.Parcelable

data class Member(
    val id: Long,
    val name: String,
    val friends: List<Member>
) : Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readLong(),
        parcel.readString() ?: "",
        parcel.createTypedArrayList(CREATOR) ?: emptyList()
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeLong(id)
        parcel.writeString(name)
        parcel.writeTypedList(friends)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Member> {
        override fun createFromParcel(parcel: Parcel): Member {
            return Member(parcel)
        }

        override fun newArray(size: Int): Array<Member?> {
            return arrayOfNulls(size)
        }
    }
}

  • Parcelable 을 구현하여, 안드로이드 프레임워크의 API 중 Parcelable 을 필요로 하는 곳에 이 클래스를 인자로 넘길수 있게 된다. 주로 Intent, Bundle 을 이용해서 서로 다른 컴포넌트(ex. Activity) 간의 데이터 전달에 사용된다.
  • Android에서 성능을 위해 최적화된 방식으로, Serializable 보다 더 나은 성능을 제공한다.

Annotation @Serializable

  • https://kotlinlang.org/docs/serialization.html
  • kotlin 이 제공하는 직렬화 라이브러리: kotlinx.serialization.Serializable
  • JSON 혹은 protocol buffer 같은 일반적인 데이터 직렬화 방식으로 만들어준다.
    • 주로 JSON 직렬화를 위해 사용된다.
  • Json.encodeToString, Json.decodeFromString<T> 메소드를 통해 직렬화/역직렬화가 가능하다.
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString

@Serializable
data class Data(val a: Int, val b: String)

fun main() {
   val json = Json.encodeToString(Data(42, "str"))
}
  • Gson, Moshi, Jackson 등 비슷한 역할을 하는 라이브러리가 있다.

Annotation @Parcelize

  • https://developer.android.com/kotlin/parcelize
  • kotlin-parcelize 플러그인이 @Parcelize 어노테이션이 붙은 클래스에 대해 위에서 설명한 Parcelable 의 메소드를 알아서 구현해준다.
plugins {
    id("kotlin-parcelize")
}
  • 컴파일 타임에 바이트 코드 변조를 통해 이루어지기 때문에 메소드를 추가해야 하거나 런타임 시 오버헤드 비용이 발생하지 않는다.
  • Parcelable interface 를 구현하는 클래스 정의에 @Parcelize 어노테이션을 추가한다
import kotlinx.parcelize.Parcelize

@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable
  • Parcelize를 사용하려면 모든 직렬화된 프로퍼티가 기본 생성자에 선언되어야 한다. 또한 기본 생성자 매개변수 중 일부가 프로퍼티가 아닌 경우 @Parcelize를 적용할 수 없다.
  • 직렬화하는 코드를 커스텀하고 싶다면, companion class 에 다음을 추가하면 된다.
@Parcelize
data class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
    private companion object : Parceler<User> {
        override fun User.write(parcel: Parcel, flags: Int) {
            // Custom write implementation
        }

        override fun create(parcel: Parcel): User {
            // Custom read implementation
        }
    }
}

Leave a comment