익숙한 Serializable과 Parcelable

Serializable과 Parcelable, 익숙한 녀석들이다. Bundle에 객체를 담아 Intent와 arguments를 통해 다른 곳으로 전달하려면 객체의 클래스가 둘 중 하나를 구현(상속)해야 한다.

나는 지금까지 Serializable이 비효율적이라고 해서 코틀린에서 제공하는 @Parcelize 을 사용하거나, 별 생각 없이 Serializable을 사용하곤 했다.

그러다 둘이 어떤 차이가 있고, 왜 Serializable이 비효율적인지 알아보기로 했다.

직렬화

Serializable: 직렬화 가능한, ‘직렬화’ 많이 들어보긴 했는데..

https://www.geeksforgeeks.org/serialization-in-java/

  • 직렬화(serialization): 객체를 바이트 단위의 연속적인 데이터(바이트 스트림)로 변경하는 작업
  • 역직렬화(deserialization): 바이트 스트림을 원래 객체로 변환하는 작업

Serializable과 Parcelable은 모두 직렬화와 관련이 있다. 객체를 주고 받으려면 객체를 직렬화 해야 한다.

왜 직렬화가 필요할까?

직렬화는 서로 다른 메모리 영역을 갖는 컴포넌트 간 객체를 주고 받을 때 사용한다. 객체는 대부분 다른 객체를 가리키는 참조 필드를 갖고 있는데, 이 주소 값을 다른 메모리에서는 사용할 수가 없다. 따라서, 이 참조 변수를 그것이 가리키는 실제 값으로 변환하는 작업이 필요하다.

Serializable

Serializable은 Java에서 제공하는 표준 인터페이스이다. Serializable을 구현한 클래스는 직렬화 대상이 된다.

장점

  • 사용하기 편하다. 따로 구현할 코드가 없다.

단점

  • Reflection을 사용하기 때문에 느리고 메모리를 많이 쓴다.

Reflection

  • Reflection: Java에서 제공하는 API로, 런타임에 객체의 정보를 분석하는 기법

객체의 프로퍼티는 런타임에 동적으로 변하기 때문에 컴파일 타임에 결정할 수 없다. 그래서 Reflection을 통해 정보를 분석한 후 직렬화 한다.

하지만 Reflection 과정에서 여러 중간 객체가 생성된다. 그리고 중간 객체를 생성하고 GC를 통해 제거하는 과정에서 메모리와 CPU를 사용하므로 그만큼 리소스가 소모되는 작업이라고 할 수 있다.

Parcelable

안드로이드 SDK에서 제공하는 인터페이스이다. 이것을 구현한 클래스의 객체는 직렬화 가능하다.

Parcelable은 Reflection이 런타임에 하는 작업을 개발자가 대신 한다. 개발자가 직접 직렬화/역직렬화 하는 로직을 작성해야 한다.

장점

Serializable에 비해 빠르고 리소스를 덜 소모한다.

Reflection 과정 없이 미리 작성된 로직을 바탕을 빠르게 직렬화/역직렬화 할 수 있다.

단점

직접 작성해야 하는 코드가 많다. class User(val id: Int, val name: String)에 대해 필요한 코드는 다음과 같다.

class User(val id: Int, val name: String) : Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readInt(),
        parcel.readString() ?: ""
    ) {
    }

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

    override fun describeContents(): Int {
        return 0
    }

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

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

(좀 길긴 하다;;)

Serializable vs Parcelable

그래서 어떤 걸 사용하는 게 좋을까? 개발 서적을 볼 때마다 항상 보는 문장이 있는데, 프로그래밍에 정답은 없다는 것이다.

개인적인 견해로는, 요새 하드웨어 성능이 좋으니까 속도가 중요한 상황이라면 Parcelable을, 그렇지 않다면 Serializable을 사용해도 괜찮지 않을까? 저렇게 긴 코드를 작성하는 것보다 핵심 로직에 집중하는 게 더 나을 것 같다.

하지만 Parcelize가 있다!

사실 현재 코틀린에서는 Parcelize를 제공하고 있다. Parcelize는 Serializable의 간편함과 Parcelable의 속도 측면의 장점을 모두 누릴 수 있는 기술로, Parcelable의 구현을 자동으로 생성해준다.

plugins {
    id("kotlin-parcelize")
}
import kotlinx.parcelize.Parcelize

@Parcelize
class User(val name: String, val email: String): Parcelable

위와 같이 Parcelable 인터페이스를 구현하고 @Parcelize 어노테이션을 달면 구현 끝!


참조

+ Recent posts