Android Intent 객체 전달 방법 - Serializable vs Parcelable 비교
Android 개발에서 Activity 간 데이터 전달은 매우 빈번하게 발생하는 작업입니다. 문자열이나 정수 같은 기본 타입은 Intent의 putExtra() 메서드를 통해 쉽게 전달할 수 있지만, 복잡한 객체나 사용자 정의 클래스를 전달하려면 별도의 처리가 필요합니다.
이번 포스팅에서는 Android에서 Intent를 통해 객체를 전달하는 두 가지 주요 방법인 Serializable과 Parcelable을 상세히 비교하고, 각각의 구현 방법과 장단점을 알아보겠습니다. 실무에서 어떤 방법을 선택해야 하는지에 대한 명확한 기준도 제시합니다.
Activity 간 객체를 전달하기 전에 먼저 직렬화가 무엇이고 왜 필요한지 이해해야 합니다. 직렬화는 Android 컴포넌트 간 통신의 핵심 개념입니다.
직렬화(Serialization)는 객체의 상태를 바이트 스트림으로 변환하는 과정을 의미합니다. 이와 반대로 역직렬화(Deserialization)는 바이트 스트림을 다시 원래의 객체로 복원하는 과정입니다.
예를 들어, Person("홍길동", 26)이라는 객체는 직렬화를 거쳐 {"name":"홍길동", "age":"26"}와 같은 바이트 형식으로 변환되고, 이 데이터는 다시 역직렬화를 통해 원래의 Person 객체로 복원됩니다.
Person 객체 → 바이트 스트림으로 변환 → 전송
// 역직렬화 과정
수신된 바이트 스트림 → Person 객체로 복원
Android에서 각 Activity는 별도의 프로세스 메모리 공간에서 실행됩니다. 일반적인 객체 참조 방식으로는 한 Activity에서 다른 Activity로 객체를 전달할 수 없습니다.
만약 하나의 프로세스에서 다른 프로세스로 객체의 메모리 주소를 그대로 넘긴다면, 프로세스 간 메모리 공간이 다르기 때문에 해당 주소에 접근할 수 없거나 잘못된 데이터를 읽게 됩니다. 이는 심각한 보안 문제와 앱 충돌을 야기할 수 있습니다.
따라서 객체의 값 자체를 바이트 형태로 변환하여 전달해야 하며, 이것이 바로 직렬화가 필요한 이유입니다. Android는 이를 위해 IPC(Inter-Process Communication) 메커니즘과 Binder를 사용합니다.
Intent는 Android 컴포넌트 간 데이터 전달을 위한 메시징 객체입니다. Intent의 putExtra() 메서드는 기본 타입(String, int, boolean 등)은 자동으로 처리하지만, 사용자 정의 객체는 직렬화가 필요합니다.
| 데이터 타입 | 직렬화 필요 여부 | 설명 |
|---|---|---|
| 기본 타입 (String, int, boolean 등) | 불필요 | Intent가 자동으로 처리 |
| 사용자 정의 객체 | 필수 | Serializable 또는 Parcelable 구현 필요 |
| 컬렉션 (ArrayList, HashMap 등) | 조건부 | 요소가 직렬화 가능해야 함 |
Serializable은 Java의 표준 인터페이스로, 가장 간단하게 구현할 수 있는 직렬화 방법입니다. Android SDK가 아닌 Java 표준 라이브러리에 포함되어 있습니다.
Serializable은 마커 인터페이스(Marker Interface)로, 구현해야 할 메서드가 없습니다. 단순히 implements Serializable만 추가하면 해당 클래스의 객체를 직렬화할 수 있습니다.
public class Person implements Serializable {
private String name;
private int age;
private String address;
// 생성자
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
// Getter/Setter 메서드
public String getName() { return name; }
public int getAge() { return age; }
public String getAddress() { return address; }
}
Serializable 객체를 Intent에 담아 다른 Activity로 전송하는 방법은 매우 간단합니다. putExtra() 메서드를 사용하거나 Bundle을 통해 전달할 수 있습니다.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Person 객체 생성
Person person = new Person("홍길동", 26, "서울시 강남구");
// Intent 생성 및 객체 추가
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("person_data", person);
startActivity(intent);
// 또는 Bundle을 사용하는 방법
Bundle bundle = new Bundle();
bundle.putSerializable("person_data", person);
intent.putExtras(bundle);
startActivity(intent);
}
}
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
// Intent에서 객체 가져오기
Intent intent = getIntent();
Person person = (Person) intent.getSerializableExtra("person_data");
// 데이터 사용
if (person != null) {
Log.d("SecondActivity", "이름: " + person.getName());
Log.d("SecondActivity", "나이: " + person.getAge());
Log.d("SecondActivity", "주소: " + person.getAddress());
}
}
}
Serializable은 간단해 보이지만 내부적으로는 복잡한 과정을 거칩니다. Java의 Reflection API를 사용하여 런타임에 객체의 필드 정보를 읽고 직렬화합니다.
Reflection은 매우 편리하지만 다음과 같은 비용을 수반합니다.
그러나 현대 Android 기기의 하드웨어 성능이 크게 향상되면서 이러한 성능 차이는 예전만큼 크지 않습니다.
Parcelable은 Android SDK에서 제공하는 인터페이스로, Serializable보다 빠른 성능을 제공하지만 구현이 복잡합니다. Android의 IPC 메커니즘에 최적화되어 있습니다.
Parcelable은 개발자가 직접 직렬화/역직렬화 로직을 작성해야 합니다. Reflection을 사용하지 않기 때문에 더 빠르지만, 보일러플레이트 코드가 많습니다.
public class Person implements Parcelable {
private String name;
private int age;
private String address;
// 일반 생성자
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
// Parcel에서 읽어오는 생성자
protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
address = in.readString();
}
// Parcel에 쓰는 메서드 (직렬화)
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeString(address);
}
@Override
public int describeContents() {
return 0;
}
// CREATOR 필드 (역직렬화)
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
// Getter 메서드
public String getName() { return name; }
public int getAge() { return age; }
public String getAddress() { return address; }
}
Parcelable 구현에는 반드시 포함되어야 하는 세 가지 핵심 요소가 있습니다.
객체의 데이터를 Parcel에 쓰는 직렬화 메서드입니다. 필드를 작성한 순서대로 Parcel에 저장해야 하며, 이 순서는 역직렬화 시 읽는 순서와 정확히 일치해야 합니다.
Parcel에서 데이터를 읽어 객체를 복원하는 역직렬화 생성자입니다. writeToParcel()에서 쓴 순서와 동일한 순서로 데이터를 읽어야 합니다.
Parcelable 객체를 생성하기 위한 필수 필드입니다. 이 필드가 없으면 런타임에 Exception이 발생합니다. createFromParcel()과 newArray() 두 메서드를 구현해야 합니다.
Person person = new Person("홍길동", 26, "서울시 강남구");
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("person_data", person);
startActivity(intent);
// SecondActivity.java - Parcelable 객체 수신
Intent intent = getIntent();
Person person = intent.getParcelableExtra("person_data");
if (person != null) {
Log.d("SecondActivity", "이름: " + person.getName());
}
Kotlin을 사용한다면 @Parcelize 어노테이션을 활용하여 보일러플레이트 코드 없이 Parcelable을 구현할 수 있습니다.
plugins {
id 'kotlin-parcelize'
}
// Kotlin 데이터 클래스에 @Parcelize 적용
@Parcelize
data class Person(
val name: String,
val age: Int,
val address: String
) : Parcelable
이 방법을 사용하면 Kotlin 컴파일러가 자동으로 writeToParcel(), describeContents(), CREATOR를 생성합니다.
많은 개발 문서에서 Parcelable이 Serializable보다 훨씬 빠르다고 언급하지만, 실제 성능 차이와 선택 기준에 대해 자세히 살펴보겠습니다.
2012년 Philipe Breault의 실험 결과에 따르면 Parcelable이 Serializable보다 약 10배 빠른 것으로 나타났습니다. 그러나 이는 구형 기기를 대상으로 한 테스트이며, 최신 기기에서는 차이가 크지 않습니다.
| 비교 항목 | Serializable | Parcelable |
|---|---|---|
| 처리 속도 | 느림 (Reflection 사용) | 빠름 (직접 구현) |
| 메모리 사용 | 많음 (Garbage 객체 생성) | 적음 (최적화된 구현) |
| 구현 난이도 | 매우 쉬움 | 복잡함 (Kotlin @Parcelize 제외) |
| 코드 유지보수 | 쉬움 | 어려움 (필드 추가 시 수정 필요) |
| 플랫폼 의존성 | 없음 (Java 표준) | 있음 (Android SDK) |
| 스레드 안전성 | 스레드 안전하지 않음 | 스레드 안전함 |
최근 Android 기기의 하드웨어 성능이 크게 향상되면서 Serializable과 Parcelable의 성능 차이는 실질적으로 체감하기 어려운 수준입니다. 일반적인 앱 사용 시나리오에서는 두 방식 모두 충분히 빠릅니다.
또한 Serializable에서 writeObject()와 readObject() 메서드를 직접 구현하면 Parcelable과 비슷하거나 더 나은 성능을 낼 수 있다는 연구 결과도 있습니다.
성능보다는 프로젝트의 아키텍처와 유지보수성을 고려한 선택이 더 중요합니다.
Parcelable은 영구 저장(Persistent Storage)에는 적합하지 않습니다. Android 공식 문서에서도 Parcel 데이터를 파일이나 데이터베이스에 저장하지 말 것을 권고합니다. Parcel의 내부 구현이 변경될 수 있어 이전 버전의 데이터를 읽지 못할 수 있습니다.
영구 저장이 필요하다면 Serializable을 사용하거나, JSON과 같은 텍스트 기반 포맷을 사용하는 것이 안전합니다.
A: 아니요, Intent로 객체를 전달하기 위해 AndroidManifest.xml에 추가할 설정은 없습니다. 다만 전달받을 Activity가 AndroidManifest.xml에 등록되어 있어야 합니다. 등록되지 않은 Activity로 Intent를 보내면 ActivityNotFoundException이 발생합니다.
A: 기술적으로는 가능하지만 권장하지 않습니다. 두 인터페이스를 함께 구현하면 혼란을 야기하고 예상치 못한 동작이 발생할 수 있습니다. Intent에서는 Parcelable이 우선적으로 처리되므로, 둘 중 하나만 구현하는 것이 좋습니다.
A: 네, 가능합니다. 다만 모든 중첩 객체도 동일한 방식으로 직렬화 가능해야 합니다. Serializable의 경우 모든 필드가 Serializable이어야 하고, Parcelable의 경우 각 중첩 객체에 대해서도 writeToParcel()에서 적절히 처리해야 합니다.
public class Person implements Serializable {
private String name;
private Address address; // Address도 Serializable 구현 필요
}
public class Address implements Serializable {
private String city;
private String zipCode;
}
A: 네, Intent를 통한 데이터 전달은 약 1MB의 크기 제한이 있습니다. 이는 Android의 Binder 트랜잭션 버퍼 크기 제한 때문입니다. 대용량 데이터는 Intent가 아닌 다른 방법(싱글톤 패턴, Application 클래스, 데이터베이스, 파일 등)을 사용해야 합니다.
A: Serializable은 자동으로 null을 처리하지만, Parcelable은 명시적으로 null 처리 로직을 구현해야 합니다. writeToParcel()에서 null 여부를 플래그로 저장하고, 생성자에서 해당 플래그를 확인하여 복원해야 합니다.
A: 성능 문제가 실제로 발생하지 않는다면 굳이 전환할 필요는 없습니다. 현대 기기에서는 성능 차이가 미미하며, 전환 작업에 드는 시간과 노력 대비 얻는 이점이 크지 않습니다. 다만 Kotlin 프로젝트에서 @Parcelize를 사용할 수 있다면 전환을 고려해볼 만합니다.
A: 네, Intent는 다음과 같은 컬렉션 전달을 지원합니다.
ArrayList<Person> personList = new ArrayList<>();
intent.putParcelableArrayListExtra("person_list", personList);
// 수신
ArrayList<Person> list = intent.getParcelableArrayListExtra("person_list");
컬렉션의 요소가 Parcelable 또는 Serializable을 구현해야 합니다.
A: Enum은 기본적으로 Serializable을 구현하고 있어 별도 작업 없이 전달할 수 있습니다. Parcelable의 경우 writeString()과 readString()을 사용하여 Enum의 name()을 저장하고 복원합니다.
A: 네, Fragment의 arguments Bundle에 동일한 방식으로 데이터를 추가할 수 있습니다.
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putParcelable("person_data", person);
fragment.setArguments(args);
// Fragment에서 데이터 받기
Bundle args = getArguments();
if (args != null) {
Person person = args.getParcelable("person_data");
}
A: 권장하지 않습니다. Intent로 전달되는 데이터는 다른 앱이나 악의적인 코드에 의해 가로채질 수 있습니다. 비밀번호, 인증 토큰, 개인정보 등 민감한 데이터는 암호화하거나, 안전한 저장소(Keystore, Encrypted SharedPreferences)를 사용하고 참조만 전달하는 것이 좋습니다.
Android에서 Intent를 통한 객체 전달은 Serializable과 Parcelable 두 가지 방법으로 구현할 수 있습니다. Serializable은 구현이 간단하고 Java 표준이라는 장점이 있으며, Parcelable은 Android에 최적화된 성능을 제공합니다.
과거에는 Parcelable이 압도적으로 빠르다는 인식이 있었지만, 현대 기기에서는 두 방식의 성능 차이가 미미합니다. 따라서 성능보다는 프로젝트 구조와 유지보수성을 고려한 선택이 중요합니다.
도메인 레이어에서는 플랫폼 독립적인 Serializable을, UI 레이어에서는 Parcelable을 사용하는 것이 일반적인 패턴입니다. Kotlin 프로젝트라면 @Parcelize 어노테이션을 활용하여 Parcelable의 복잡성을 크게 줄일 수 있습니다.
결국 어떤 방법을 선택하든 일관성 있게 사용하고, 팀 내에서 명확한 가이드라인을 수립하는 것이 가장 중요합니다.
끝.
'Development > Android' 카테고리의 다른 글
| [Android] View의 터치영역 넓히기 (0) | 2016.03.25 |
|---|---|
| [Android] AsyncTask 중지하기 (0) | 2016.03.23 |
| [Android] ListView 계층 구조 (0) | 2015.08.11 |
| [Android] Webview에서 전화걸기 오작동 막기 (0) | 2015.02.26 |
| [Android] Google Play Store 계정 등록하기 (0) | 2015.02.23 |