[Android] Android Serializable & Parcelable

devseunggwan·2020년 5월 11일
0

Android Parcel Error

Intent를 통해 액티비티 전환 시, Bundle 에 (Key, Value) 값으로 데이터를 포장한다. 이후 전환하려는 엑티비티에 Bundle을 넘겨준다. Bundle에는 Int, String 등 객체나 클래스등을 담아서 넘겨줄 수 있다.

public class User {
    String user_id;
    String user_name;
    String user_age;
    String user_address;

    // Constructor
    public User(int user_id, String user_name, int user_age, String user_address) {
        this.user_id = user_id;
        this.user_name = user_name;
        this.user_age = user_age;
        this.user_address = user_address;
    }

    // Getter & Setters
    public int getUser_id() {
        return user_id;
    }

    public void setUser_id(int user_id) {
        this.user_id = user_id;
    }

    public String getUser_name() {
        return user_name;
    }

    public void setUser_name(String user_name) {
        this.user_name = user_name;
    }

    public int getUser_age() {
        return user_age;
    }

    public void setUser_age(int user_age) {
        this.user_age = user_age;
    }

    public String getUser_address() {
        return user_address;
    }

    public void setUser_address(String user_address) {
        this.user_address = user_address;
    }
}

User 객체를 선언한다.

// MainActivity.java
User user = new User("dev", "Song", "25", "changwon");
bundle = new Bundle();
bundle.putSerializable("user", serialableUser);

intent = new Intent(this, ChangeActivity.class);
intent.putExtras(bundle);
startActivity(intent);

User 객체를 ChangeActivity에 전달하기 위해 Bundle를 선언하고 User 객체를 Bundle에 포장했다.

// ChangeActivity.java
Intent intent = getIntent();
User user = (User) intent.getSerializable("user")

Toast.makeText(this, "Hello!, " + user.user_name, Toast.LENGTH_LONG).show();

Bundle에 포장된 클래스를 불러온다.

어플리케이션 실행 후 엑티비티를 넘어가면 다음과 같은 에러가 발생한다.

java.lang.RuntimeException: Parcel: unable to marshal value...

이 에러가 발생하는 이유는 Bundle에 포장한 클래스를 직렬화 시켜주지 않아 발생한다.

직렬화(Serialize)

데이터 구조나 객체 상태를 동일하거나 다른 컴퓨터 환경에 저장하고 나중에 재구성할 수 있는 포멧으로 변환하는 과정을 말한다. 엑티비티나 다른 프로그램과 통신 시 객체를 직렬화하여 포멧을 직렬화 후 전송한다. 이후에 직렬화한 객체를 전환된 엑티비티나 다른 프로그램에서 역직렬화를 통해 원래 객체로 변환하여 사용한다.

안드로이드 어플리케이션에서 직렬화를 시켜주기 위해선 ParcelableSerializable 를 사용하여야 한다.

Serializable

import java.io.Serializable 로 사용가능한 자바 인터페이스이다. 사용 방법은 직렬화하려는 클래스에 implementsSerializable 를 선언만 해주면 된다.

import java.io.Serializable

public class User implements Serializable{
    int user_id;
    String user_name;
    int user_age;
    String user_address;

    // Constructor & Getter & Setter 생략
}

선언 후 엑티비티에 있는 코드는 수정없이 그대로 사용하면 된다. 하지만 간단한만큼 시스템 비용면에서 값을 지불한다.

Serializable 실행 시 리플렉션이 발생함에 따라 많은 객체가 생성된다. 그래서 Serializable 실행 후 사용하지 않는 객체들을 처리하기 위해 Garbage Collection이 실행된다. 결국, GC가 실행되면서 어플리케이션의 성능이 저하되는 단점이 발생한다.

Parcelable

Android SDK 인터페이스이다. 간단하게 implementsSerializable 만 선언해줘서 사용하는 것과는 차이점이 있다. 클래스에 implementsParcelable 을 선언하고 내부 인터페이스를 구성해야 한다.

import android.os.Parcel;
import android.os.Parcelable;

public class User implements Parcelable {

    public String user_id;
    public String user_name;
    public String user_age;
    public String user_address;
		
    // Activity에서 호출하는 Constructor
    public user(){}
    
    // Parcelable 인터페이스에서 사용하는 Constructor
    protected User(Parcel in) {
        user_id = in.readString();
        user_name = in.readString();
        user_age = in.readString();
        user_address = in.readString();
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
    
    // Parcel하려는 객체의 종류를 정의
    @Override
    public int describeContents() {
        return 0;
    }

    // 실제 객체들을 Serialization
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(user_id);
        dest.writeString(user_name);
        dest.writeString(user_age);
        dest.writeString(user_address);
    }
}

클래스에 Parcelable 을 적용했다면 사용하려는 엑티비티의 코드를 수정해야 한다. 클래스를 호출하고 클래스 내 각 객체마다 값을 추가해야한다. 이후 인텐트를 선언하고 putExtra 를 사용하여 (Key, Value) 값으로 클래스를 인텐트에 추가한다.

// MainActivity.java
User user = new User();
User.user_id = "dev";
User.user_name = "Song";
User.user_age = "25";
User.user_address = "Changwon";

intent = new Intent(this, ChangeActivity.class);
intent.putExtra("user", parcelUser);
startActivity(intent)

전달한 엑티비티에선 인텐트를 가져온 뒤, deserialization 하기 위해 getParcelableExtra 메소드를 사용한다.

// ChangeActivity.java
Intent intent = getIntent();
User user = intent.getParcelableExtra("user")

Toast.makeText(this, "Hello!, " + user.user_name, Toast.LENGTH_LONG).show();

안드로이드 스튜디오에서 implements Parcelable 시 오버라이드 메소드를 생성할 수 있다. Parcelable 은 내부에 보일러 플레이트 코드가 들어가기 때문에 상대적으로 Serializable 코드보다 유지보수에 필요한 시간이 더 걸린다. 어플리케이션의 크기가 커질수록 유지보수 측면에서 봤을 땐 Serializable 가 낫다. 하지만 성능 면에서 봤을 땐, Mashaling 해야하는 객체들이 많아지면 Serializable 는 성능이 저하되므로 보일러 플레이트 코드를 직접 생성해주는 Parcelable 가 더 우세하다.

다음은 SerializableParcelable 비교한 내용을 표로 정리한 것이다.

항목SerializableParcelable
보일러 플레이트 사용미사용사용
유지 보수쉬움어려움
성능낮음높음

실습

다른 엑티비티로 SerializableParcelableimplement 한 클래스를 보내는 실습을 한 전체 소스를 깃허브에 저장하였다. 역시 머리로 읽는 거보단 실습하는 게 최고다.
devSeungGwan/android_serializable_parcelable

Reference

Marshalling

Serialize

Marshalling & Serializable

Parcelable & Serializable

Boilerplate

Parcelable IPC

profile
변화를 두려워하지 않는 개발자입니다.

0개의 댓글