티스토리 뷰

Development

[Android] Parcelable Obejct

devbible 2010. 7. 26. 15:42

Android의 핵심 중에서 Binder Driver라는 녀석이 있습니다.
Linux Kernel의 driver로 되어 있고, IPC이긴 하지만 기존의 IPC와는 살짝 다른 녀석 입니다.

 

저도 어떻게 만들었는지는 잘 모릅니다만,
shared memory를 통하여 오버헤드도 적고 프로세스 사이에 비동기로 호출도 이루어 진다고 합니다.
그리고 Binder는 기존 IPC처럼 데이터만 전달 하는게 아니라,
하나의 프로세스에서 다른 프로세스로 Object를 넘길 수도 있게끔 설계 되어 있습니다.
(물론 Serialize 기술을 사용하면 Object도 주고 받을 순 있지요.)

 

Binder를 통해서 넘기는 메세지 (data 와 object references) 는
Parcel 클래스에 저장이 되어서 넘어가게 됩니다. Parcel은 메세지 컨테이너인 셈이죠.

Parcel is not a general-purpose serialization mechanism. This class (and the corresponding Parcelable API for placing arbitrary objects into a Parcel) is designed as a high-performance IPC transport.

Parcel 클래스는 고성능 IPC 전송을 가능하게 끔 설계가 되어 있기 때문에,

모든 객체에 대해서 평범하게 serialization하지 않습니다.
그래서 모든 Object들을 Binder를 통해 주고 받을 수는 없습니다.

 


[Parcelable Interface]

 

Primitive Type과 Primitive Array Type은 기본적으로 parcelable 합니다.

뭐 그냥 일반 데이터인거죠.
그리고 Parcelable Interface를 implements한 클래스가 (당연히) parcelable 합니다.

 

오늘 우리가 할 일이 바로 이 Parcelable Interface를 사용하여
parcelable Object를 만드는 일 인 것이죠.

사실 parcelable한 type들이 많이 있습니다만, 나머지는 잘 모르겠군요... 어렵습니다.
여튼 오늘은 parcelable Object를 만드는 것만 생각 합시다.

 


[Parcelable Rect]

 

Rect 클래스야 다들 쉽게 만드실 겁니다.
left, top, right, bottom 4개의 필드만 있으면 되죠.


integer든 float이든 상관 없습니다만....
Android에는 Rect 클래스의 float 버전이 RectF 클래스 라고 따로 되어 있기 때문에,

저는 integer로 만들겁니다.


public class Rect {
    public int left;
    public int top;
    public int right;
    public int bottom;
}


뭐 아주 간단 합니다. 그냥 C에서의 구조체 수준이네요.
이것을 parcelable하게 바꾸어 봅시다.

 

Step 1.


public class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;
    
    public int describeContents() {
        return 0;
    }
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(left);
        dest.writeInt(top);
        dest.writeInt(right);
        dest.writeInt(bottom);
    }
}


Parcelable을 implements하게 되면 꼭 추가해야 한다는 메소드 두 개도 같이 넣었습니다.
내용도 채워 봤어요.

 

describeContents() 메소드는 일단 건너뛰고...
writeToParcel() 메소드는 Parcel에 데이터를 쓰는 부분 입니다.
그냥 무작정 쓰면 됩니다. 전 아무 생각 없이 순서대로 그냥 썼습니다.

 

그럼 이제 에러도 없으니 parcelable한 Rect 클래스가 되었느냐... 라고 한다면 아직 아닙니다.
쓰는건 있는데 읽는건 없네요...

 

Parcel로 부터 값을 읽어 오기 위해서는 Parcelable.Creator Interface 가 필요합니다.

 


[Parcelable.Creator Interface]

 

Parcel 에서 Parcelable 클래스의 인스턴스를 만들 때
CREATOR라는 static field를 찾아서 실행 합니다.
CREATOR는 Parcelable.Creator<T> type 으로 만들어져야 하는데
이건 선언과 동시에 반드시 initialize 되어야 합니다.

 

클래스 따로 만들어서 initialize 하기도 쉽지 않습니다.
그냥 익명 클래스로 만들어 버립시다.

 

Step 2.


public class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;
    
    public int describeContents() {
        return 0;
    }
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(left);
        dest.writeInt(top);
        dest.writeInt(right);
        dest.writeInt(bottom);
    }
    
    public static final Parcelable.Creator<Rect> CREATOR = new Creator<Rect>() {
        public Rect createFromParcel(Parcel source) {
            Rect r   = new Rect();
            r.left   = source.readInt();
            r.top    = source.readInt();
            r.right  = source.readInt();
            r.bottom = source.readInt();
            return r;
        }
        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };
}


Parcelable.Creator<T> 클래스는 createFromParcel() 과 newArray() 메소스가 필요 합니다.

 

newArray() 메소드도 일단 넘어가고...
createFromParcel() 메소드를 보면 리턴이 Rect입니다.
그래서 Parcel에서 값을 읽어 새로운 Rect 인스턴스를 리턴 하는 구조로 만들면 끝입니다.

 

readInt()를 했는데, 이것은 writeToParcel() 메소드에서 썼던 순서대로 읽어 오는 것입니다.
쓸 때는 무작정 썼지만 읽을 때는 조심스럽게 읽어야하죠.

 

이제 비로소 parcelable Rect 클래스를 만들었습니다.
그냥 별거 없군요...

 


[Appendix]

 

위에서 describeContents() 메소드와 newArray() 메소드는 그냥 넘어 갔었습니다.
네... 뭐 사실 잘 모르기도 합니다만,
그냥 보통 parcelable Object를 만드는데에 크게 중요한 부분은 아니지요. 아하하...

 

describeContents() 에서는 어떤 특별한 객체를 포함하고 있는지에 대한 설명을
리턴값으로 표현 하는 것이라고 보면 됩니다.
bit mask 형식의 integer를 리턴 하며,
값을 체크 할 때 bit mask 체크를 해서 어떤 것들이 들어 있는지 알 수 있습니다.

 

현재 사용하는 플래그는 Parcelable.CONTENTS_FILE_DESCRIPTOR (0x00000001)
하나 밖에 정의 되어 있지 않습니다.
소스를 다 뒤져 보지 않아서 또 어떤 값들이 쓰이는지는 확인 해보지 못했네요...

 

newArray() 메소드는 그냥 배열로 만들어서 리턴 하는 겁니다.
Parcel.createTypedArray() 메소드에서 newArray() 메소드를 호출 하는 걸 확인 했습니다.
나중에 createTypedArray() 메소드를 사용 할 일이 있다면 newArray()가 호출이 되는 것이지요.

[▼추가]

[In Case of Inner Class]

 

저번에는 아주 간단한 Rect 클래스로 parcelable하게 만들어 보았습니다.
이번에는 좀 더 복잡한 형태의 클래스를 가지고 parcelable하게 만들어 보도록 하죠.

이번에 parcelable하게 만들어 볼 클래스는 EyePoint 클래스 입니다.

 

public class EyePoint {
    public Point left;
    public Point right;
    
    public class Point {
        public int x;
        public int y;
    }
}

 

눈 좌표를 나타내는 클래스 인데, 왼쪽 눈과 오른쪽 눈 좌표를 가지고 있습니다.
그리고 다시 한쪽 눈 좌표는 x, y integer 값을 가지고 있죠.

뭐 물론 주우욱 풀어서 leftX, leftY, rightX, rightY 4개의 필드를 만들어서 표현 할 수도 있겠습니다만,
전 그냥 EyePoint 클래스 안에 다시 Point 클래스를 만들었습니다. 뭐 제 마음이죠.

 

그럼 일단 안쪽에 있는 EyePoint$Point 클래스 부터 parcelable하게 만들어 봅시다.

 

Step 1.

 

public class EyePoint implements Parcelable {
    public Point left;
    public Point right;

    
    
    // EyePoint$Point Inner Class
    public static class Point implements Parcelable {
        public int x;
        public int y;
        
        public int describeContents() {
            return 0;
        }
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(x);
            dest.writeInt(y);
        }
        public static final Parcelable.Creator<Point> CREATOR = new Creator<Point>() {
            public Point createFromParcel(Parcel source) {
                Point p = new Point();
                p.x = source.readInt();
                p.y = source.readInt();
                return p;
            }
            public Point[] newArray(int size) {
                return new Point[size];
            }
        };
    }
}

 

저번에 만들었던 Rect 클래스 보다 무려 필드의 수가 반 밖에 안됩니다.
이쯤이야 금방 만들 수 있지요...


한가지 주의할 점은 Point 클래스의 CREATOR가 static이니
Point 클래스도 static이 되어야 EyePoint를 통해서 접근 할 수 있는것이 좀 다른 점입니다.
어차피 Eclipse에서 다 잡아 주니까 부담없이 코딩 하시면 되겠습니다.

 


[Write Parcelable And Read Parcelable]

 

자... 그럼 이제 EyePoint 클래스를 parcelable하게 만들어 봅시다.
Point 타입의 필드가 left, right 두개가 있으니 이것을 Parcel에다가 써야 하는데...

어떤 타입으로 써야 할까요?

 

눈치가 빠르신 분들은 이미 writeParcelable() 메소드를 사용하면 될거라 생각 하셨을 겁니다.

 

이미 Point는 Parcelable Interface를 implements 했으니 이미 parcelable합니다.
그래서 writeParcelable() 메소드를 사용해서 Parcel에다 쓸 수 있는 것이죠.

 

그런데 writeParcelable() 메소드를 잘 살펴보면,

다른 Primitive 타입과는 다른 인자가 하나 더 붙어 있습니다.

public final void writeParcelable (Parcelable p, int parcelableFlags)

parcelableFlags의 정체는 데체 무어란 말인가...
근데 지금은 그냥 별 생각 없이 ZERO로 채워 주시면 됩니다. 아핫핫... 넘어갑시다.

 

Step 2.

 

public class EyePoint implements Parcelable {
    public Point left;
    public Point right;

    
    public int describeContents() {
        return 0;
    }
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(left,  0);
        dest.writeParcelable(right, 0);
    }
   
    
    // EyePoint$Point Inner Class
    public static class Point implements Parcelable {
        public int x;
        public int y;
        
        public int describeContents() {
            return 0;
        }
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(x);
            dest.writeInt(y);
        }
        public static final Parcelable.Creator<Point> CREATOR = new Creator<Point>() {
            public Point createFromParcel(Parcel source) {
                Point p = new Point();
                p.x = source.readInt();
                p.y = source.readInt();
                return p;
            }
            public Point[] newArray(int size) {
                return new Point[size];
            }
        };
    }
}

 

보시는 바와 같이 그냥 0을 썼습니다. 뭐 별거 없네요...

그럼 이제 Parcel에다 쓰는 부분이 끝났으니 CREATOR를 만들어 봅시다.


아까는 writeParcelable() 메소드를 사용 했으니

반대로 readParcelable() 메소드를 사용 하면 됩니다.


참 말은 쉽군요. 그럼 한번 살펴 봅시다.

public final T readParcelable (ClassLoader loader)

잉? 다른건 그냥 읽었는데 이건 ClassLoader가 필요하군요.
데체 이 ClassLoader는 쌩뚱맞게 어디서...

 

우리가 읽고 싶은것은 Point 타입입니다.
그래서 Point의 ClassLoader를 지정해 주어야지 Point 값을 읽어 올 수 있습니다.

 

ClassLoader를 얻어내는 방법은 의외로 간단합니다.
Intent를 쓸 때와 비슷한 방법으로 얻어 오면 되는 것이죠.

 

ClassLoader loader = EyePoint.Point.class.getClassLoader();

 

Intent를 쓸 때는 Class까지만 넘겨주면 되는 거였는데,

Class 객체에서 getClassLoader()를 호출하게 되면 ClassLoader를 쉽게 얻어 올 수 있습니다.

 

Step 3.

 

public class EyePoint implements Parcelable {
    public Point left;
    public Point right;

    
    public int describeContents() {
        return 0;
    }
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(left,  0);
        dest.writeParcelable(right, 0);
    }
    public static final Parcelable.Creator<EyePoint> CREATOR = new Creator<EyePoint>() {
        public EyePoint createFromParcel(Parcel source) {                   
            EyePoint e = new EyePoint();
                   
            ClassLoader loader = EyePoint.Point.class.getClassLoader();
            e.left  = source.readParcelable(loader);
            e.right = source.readParcelable(loader);
            
            return e;
        }
        public EyePoint[] newArray(int size) {
            return new EyePoint[size];
        }
    };
   
    
    // EyePoint$Point Inner Class
    public static class Point implements Parcelable {
        public int x;
        public int y;
        
        public int describeContents() {
            return 0;
        }
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(x);
            dest.writeInt(y);
        }
        public static final Parcelable.Creator<Point> CREATOR = new Creator<Point>() {
            public Point createFromParcel(Parcel source) {
                Point p = new Point();
                p.x = source.readInt();
                p.y = source.readInt();
                return p;
            }
            public Point[] newArray(int size) {
                return new Point[size];
            }
        };
    }
}

 

네.. 이런 것이죠.
이제 왠만한 클래스들은 parcelable하게 만들 수 있겠죠?

 


[Appendix]

네... 위에서도 그냥 지나 쳤던 문제의 parcelableFlags가 아직 남아 있죠...
역시 이것의 사용 용도에 대해서는 미궁입니다.

이것역시 integer값으로, bit mask 형식으로 사용하기 위한 인자 입니다.


지금 사용 가능한 플래그는

Parcelable.PARCELABLE_WRITE_RETURN_VALUE (0x00000001) 뿐입니다.

소스를 거의다 뒤져 보았는데, 이걸 사용하는 곳은 ParcelFileDescriptor 클래스 밖에 없었습니다.
대부분은 0으로 채워져 있었지요. 

[출처] http://blog.vizpei.kr/
[원본] http://blog.vizpei.kr/74522627
[작성자] 비즈페이

댓글