직렬화란?

  • 직렬화는 메모리 내에 존재하는 정보를 보다 쉽게 전송 및 전달하기 위해 byte 코드 형태로 나열하는 것이다. 여기서 메모리 내에 존재하는 정보는 즉 객체를 말한다.
  • JVM(Java Virtual Machine)의 메모리에 상주 되어있는 객체 데이터를 바이트 형태로 변환하는 기술

따라서 직렬화는 주로 객체들을 통째로 파일로 저장하거나 전송하고 싶을 때 사용한다. 나는 안드로이드 앱 개발을 할 때 액티비티간의 데이터를 전달할 때 인텐트를 사용하고 이 인텐트에 전달할 데이터를 추가한다.

복잡한 클래스의 객체를 이동하려는 경우에는 Serializable 또는 Parcelabe을 사용하여 직렬화하여 인텐트에 추가해서 데이터를 이동하면 된다.

역직렬화란?

  • byte로 변환된 Data를 원래대로 Object나 Data로 변환하는 기술을 역직렬화라고 한다.
  • 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태

Serializable

Java에서는 Value Object를 쉽게 직렬화 하기위해 Serializable이라는 interface가 있다. 이는 Marker Interface로서 단순히 implement하는 것만으로도 JVM에게 직렬화가 가능하다는 것을 알려주기 때문에 사용하는게 매우 쉬운 편이다.

Serializable은 Android SDK가 아닌 표준 Java의 인터페이스이다.

이 인터페이스를 구현한 클래스의 객체는 이제 한 액티비티에서 다른 액티비티로 이동할 준비가 된다. 다음 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.io.Serializable;

public class Person implements Serializable {

private String firstName;
private String lastName;
private int age;


public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}


public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

Serializable은 해당 클래스가 직렬화 대상이라고 알려주기만 할뿐 어떠한 메소드도 가지지 않는 단순한 마커 인터페이스이므로 사용자는 매우 쉽게 사용할 수 있다.

사용방법이 쉽다는 것은 곧 시스템적인 비용이 비싸다는 것을 의미한다.

Serializable은 내부에서 Reflection을 사용하여 직렬화를 처리한다. Reflection은 프로세스 동작 중에 사용되며 처리 과정 중에 많은 추가 객체를 생성한다. 이 많은 쓰레기들은 가비지 컬렉터의 타깃이 되고 가비지 컬렉터의 과도한 동작으로 인하여 성능 저하 및 배터리 소모가 발생한다.

Parcelable

Parcelable은 직렬화를 위한 또 다른 인터페이스이다. Serializable과는 달리 표준 Java가 아닌 Android SDK의 인터페이스이다.

Parcelabledms Reflection을 사용하지 않도록 설계되엇다. Serializable과는 달리 직렬화 처리 방법을 사용자가 명시적으로 작성하기 때문에 자동으로 처리하기 위한 Reflection이 필요없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import android.os.Parcel;
import android.os.Parcelable;

public class Person implements Parcelable {

private String firstName;
private String lastName;
private int age;


public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}


public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}


@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.firstName);
dest.writeString(this.lastName);
dest.writeInt(this.age);
}

protected Person(Parcel in) {
this.firstName = in.readString();
this.lastName = in.readString();
this.age = in.readInt();
}

public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
return new Person(source);
}

@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}

마커 인터페이스인 Serializable과는 달리 Parcelable은 구현해야 하는 필수 메소드를 포함하기 때문에 클래스에 보일러 플레이트 코드가 추가된다. 이는 클래스를 이해하기 어렵고 새로운 기능을 추가하기 힘들게 만든다. 또한 코드의 추가로 클래스가 복잡해질수록 유지 보수가 어려워지는 원인이 된다.

Parcelable의 사용법은 조금 어렵다. 필수적으로 정의해야 하는 메소드가 두 가지가 있다.

describeContent()

  • Parcel 객체에 내용을 기술한다. FileDescriptor 같은 특별한 객체가 들어가면 이 부분을 통해서 알려줘야 한다. 보통은 0을 리턴한다.

writeToParcel(Parcel out, int flag)

  • 직렬화시 container 역할을 하는 Parcel안에 데이터를 넣는 작업을 한다.

이 두가지 메소드를 정의해야함과 동시에 CREATOR라는 static field(정적 필드)를 반드시 가지고 있어야 한다. 이는 선언과 동시에 초기화 되어야 한다. CREATOR 객체는 나중에 역직렬화 시 사용된다.

CREATOR 구현에는 다음의 두 가지 메소드를 구현해야 한다.

createFromParcel(Parcel source)

  • parcel된 데이터를 다시 원래대로 객체로 변환시켜 주는 작업을 한다.

newArray(int size)

  • 전달하는 객체가 배열일 경우 즉, Parcel.createTypeArray()를 호출했을 때 불리며 배열을 다시 할당하기 위해 사용한다.

parcelable은 이러한 분해와 조립을 돕는다. 이 과정에서 순서가 있고 직렬화될 변수들을 직접 세팅하여 빠른 속도를 낸다.

Serializable이 시스템 비용을 발생한다면 Parcelable은 구현과 유지, 보수에 드는 사용자의 노력을 비용으로 지불해야 한다.

Parcelable VS Serializable

인터넷에서는 Parcelable과 Serializable을 비교하는 정보들을 많이 찾을 수 있다. 인터넷에서 쉽게 접할 수 있는 접근법과 다른 접근법이 존재하기에 여기에 적으려고 한다. 이 글의 참고는 아래에 적어놓았다.

  1. Parcelable이 Serializable보다 빠르다.

많이 볼 수 있는 위의 결과는 Parcelable이 Serializable보다 10배 이상 빠르다는 것을 보여준다. 일부분도 그렇게 생각한다고 한다.

  1. 다른 의견

두번째 의견은 기본 사용법에 의한 Serializable은 Parcelable보다 느리다는 것이다. 이 말의 뜻을 잘 살펴보면 다음과 같다.

Parcelable은 위에서 설명한 것처럼 하나의 클래스 객체만을 위한 특별한 사용자 정의 코드를 작성해야만 한다. 사용자가 정의한 코드의 도움으로 Parcelable은 Serializable과는 달리 쓰레기를 생성할 이유가 없고 그 결과 성능도 더 좋다.

그러나 기본 사용법에 의한 Serializable은 Java의 자동 직렬화 프로세스를 사용한다. 이 과정은 분명 Parcelable과 같은 수고로움은 없지만 처리 과정에서 많은 쓰레기를 만들어낸다. 따라서 더 나쁜 결과를 보여주게 된다.

지금부터 조금 다른 방법으로 Serializable을 사용해 볼 것이다. Serializable에서 자동으로 처리되는 직렬화 프로세스는 사용자가 구현한 writeObject(), readObject() 메소드로 대체될 수 있다. 사용자가 구현한 직렬화 메소드와 함께 Serializable 접근 방식을 사용하려면 다음의 메소드를 반드시 구현해야 한다.

1
2
3
4
5
6
7
8
private void writeObject(java.io.ObjectOutputStream out)
throws IOException;

private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;

private void readObjectNoData()
throws ObjectStreamException;

마치 Parcelable의 사용법처럼 Serializable의 writeObject()와 readObject() 메소드에는 해당 클래스에 맞는 처리 로직을 포함시킬 수 있다. 제대로 수행된다면 기본 사용법에 의한 Serializable 방식에서 발생하는 쓰레기가 더 이상 생성되지 않을 것이다.

이 글 원본 작성자가 테스트한 결과는 놀랍다고 한다. 특정 클래스 처리 로직을 구현한 Serializable은 Parcelable보다 쓰기 속도가 3배 이상, 읽기의 경우 1.6배 더 빠르다고 한다.

잠깐!

기본 사용법에 의한 Serializable보다 Parcelable이 빠르다고 했다. 그 이유를 살펴보면 Serializable은 내부적으로 리플렉션을 사용해서 이로 인해 많은 오브젝트 생성과 그에 따라 잦은 GC의 동작으로 인해 애플리케이션의 성능을 낮추는 배터리를 소모한다.

그렇다면 Parcelable은 왜 더 빠른 것일까??

Parcelable은 IPC(프로세스간 통신)을 이용하기 때문이다. 프로세스들 사이에서 서로 데이터를 주고 받는 것을 IPC라고 한다. 프로세스간의 메모리 영역은 서로 공유할 수 없기에 Parcelable 인터페이스는 커널 메모리를 통해 데이터를 다른 프로세스로 전달하는 통로를 만들어준다.

잠깐!

위에서 등장하는 보일러플레이트 코드는 무엇일까???

IT 업계에서 보일러 플레이트는 변경없이 재사용할 수 있는 저작품을 의미하며 보일러 플레이트 코드는 재사용이 가능한 코드를 의미한다.

이 보일러 플레이트라는 단어는 철강 업계에서 유래되었는데 증기 보일러 내에 사용되는 커다란 압연 강판을 보일러 플레이트라고 불었으며 “강철처럼 튼튼하다.” "반복적으로 재사용이 가능할 정도로 강력하다"라는 의미가 있다고 한다.

참고