Intent에 primitive 데이터들(int, float, String...)을 실어서 다른 Activity로 전송하는 것은 쉽게 구현이 가능하다. 그러나 객체 자체를 넘기는 것은 그냥은 안된다.

이를 위해 필요한 클래스가 ParcelParcelable이다.


  • Parcel

⇒ Container for a message (data and object references) that can be sent through an IBinder. 

Parcel은 객체를 IBinder를 통해서 다른 Process, 다른 Activity에 실어나르기 위한 컨테이너 역할을 한다.

한 마디로 Parcel에 객체(클래스)를 담아서 이 Parcel을 Intent에 실어서 보내면 된다.

예를 들어 SomeObject라는 클래스가 Parcel에 담겨 있다면 다음과 같은 코드가 가능하다.


SomeObject so = new SomeObject(arg1, arg2);

Intent it = new Intent(packageName, packageName.호출할ClassName);

it.putExtra("SOME_KEY", so);

startActivity(it);


이렇게 특정 Process나 Activity로 객체 so를 전송할 수 있다.

이렇게 전송하기 위해서는 SomeObject를 직렬화 해야 되는데 객체 직렬화 처리를 해 주는 interface가 Parcelable이다.

Intent에 담아 전송하고자 하는 클래스가 Parcelable를 implements해 주면 해당 클래스의 직렬화는 Parcelable이 담당해 준다.


  • Parcelable

⇒ Interface for classes whose instances can be written to and restored from a Parcel. 

직렬화해서 보낼 객체를 Parcel에 담거나 Parcel에 담겨진 직렬화된 객체를 복원하는 역할을 해주는 interface이다.


객체를 Intent에 담아서 전송하기 위해 처리해 주어야 할 가장 중심 작업은 2가지이다.

(1) 객체(class)의 멤버 변수를 Parcel에 담는 행위(전송하는 쪽에서 필요한 기능)

 -. public void writeToParcel(Parcel dest, int flags)에서 객체의 데이터를 Parcel에 담는 행위


public class FacialFeatures implements Parcelable{

private int pinNumber; //직렬화 할 실제 데이터

private byte[] faceFeatures; //직렬화 할 실제 데이터

public FacialFeatures(int pin_num, byte[] facialFT){

pinNumber = pin_num;

faceFeatures = facialFT;

}

@Override

public int describeContents() {

return 0;

}

//아래 메소드에서 직렬화할 객체의 데이터를 Parcel에 담는다.

//송신할 때 플랫폼의 IPC에서 이 메소드 자동 호출

@Override

public void writeToParcel(Parcel dest, int flags) {

dest.writeInt(pinNumber); //Parcel에 객체의 데이터 담기

dest.writeByteArray(faceFeatures); //Parcel에 객체의 데이터 담기

}



(2) Parcel에 담겨진 객체를 읽어내는 행위(수신쪽에서 필요한 기능)

-. Intent에 담아 다른 Process나 다른 Activity로 넘길 클래스에 CREATRO라는 static 멤버 변수를 선언해 주어야 한다. CREATOR의 데이터 type은 Parcelable.Creator 타입인데 Parcelable.Creator는 interface이므로 2개의 추상 메소드를 재정의 해 주어야 한다.

수신쪽에서 CREATOR라는 변수를 이용해서 Parcel에 담겨진 객체를 읽어낸다. 


public static Parcelable.Creator<FacialFeatures> CREATOR = new Parcelable.Creator(){

//아래 메소드가 Parcel에 담겨져 있는 객체 데이터를 읽는 기능 수행

//수신시 플랫폼의 IPC에서 이 메소드 자동 호출

@Override

public FacialFeatures createFromParcel(Parcel source) {

int _pinNum = source.readInt();

byte[] _faceFeatures = source.createByteArray();

return new FacialFeatures(_pinNum, _faceFeatures);

}

@Override

public FacialFeatures[] newArray(int size){

return new FacialFeatures[size];

}

};


정리하면 객체를 Intent에 담아서 다른 Process나 다른 Activity에 전송할려면 해당 객체를 직렬화 해 주어야 하는데 이 기능을 담당해 주는 Interface가 Parcelable이다.

객체를 직렬화 하기 위해서는 2가지를 처리해 주어야 하는데

(1) public void writeToParcel(Parcel dest, int flags)에서 해당 객체의 데이터를 Parcel에 담기

(2) Parcelable.Creator 타입의 static 변수 CREATOR를 재 정의하는 메소드 public T createFromParcel (Parcel source)에서 Parcel에 담겨져 있는 객체의 데이터를 읽는 작업을 처리해 주면 객체를 Intent에 담아서 전송하는 것이 가능하다.


그런데 이러한 실행 중 다음과 같은 에러를 종종 만나게 된다. 


java.lang.RuntimeException: bad array lengths


이 에러의 원인은 객체를 직렬화하면 객체의 데이터(멤버 변수)가 순서대로 일렬로 정렬되는데 해당 데이터의 범위를 벗어났을 경우 통상적으로 발생하는 에러이다.

특히 직렬화 할 데이터 중에 배열이 포함되어 있을 때 다음과 같은 실수를 범하기 쉽고 이때 발생하는 문제이다.


public static Parcelable.Creator<FacialFeatures> CREATOR = new Parcelable.Creator(){

@Override

public FacialFeatures createFromParcel(Parcel source) {

int _pinNum = source.readInt();

//public final byte[] createByteArray ()

//==>Read and return a byte[] object from the parcel.

//createByteArray()가 그냥 크기만 Parcel에 있는 byte 배열과 동일한 사이즈를 가진

//비어 있는 array를 만드는 것이 아니라 다음 2가지를 동시에 처리한다.

// (1) Parcel에 있는 배열의 크기와 동일한 크기의 배열을 만들고

// (2) Parcel로부터 배열의 내용까지 read해서 = 좌측에 대입시키는 것까지를 처리한다.

//따라서 createByteArray()를 하고 나면 직렬화 순서상 그 다음 순서로 position이 

//넘어 간다. 따라서 createByteArray() 후에 readByteArray(byte[] b)를 수행하면

//readByteArray()에서 아래와 같은 에러가 발생하고 앱이 crash된다.

//Caused by: java.lang.RuntimeException: bad array lengths

byte[] _faceFeatures = source.createByteArray();

//위의 createByteArray()에서 이미 배열을 읽었기 때문에 data position이

//이미 배열의 위치를 지나가 버렸다.

//따라서 readByteArray()를 하면 bad array lengths가 발생하는 것이다.

//따라서 readByteArray()로 읽든지 아니면 createByteArray()읽든지 둘 중 어느 

//하나만 있으면 된다.

// source.readByteArray(_faceFeatures);

return new FacialFeatures(_pinNum, _faceFeatures);

}

@Override

public FacialFeatures[] newArray(int size){

return new FacialFeatures[size];

}

};



+ Recent posts