IT_Programming/Android_Java

Parcelable을 사용한 오브젝트 전달 (Object serialization using Parcelable)

JJun ™ 2011. 8. 2. 17:58

------------------------------------------------------------------------------------------------

 출처: http://samse.tistory.com/tag/parcelable%20object, http://arsviator.blogspot.com/2010/10/parcelable%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%A0%84%EB%8B%AC-object.html,

 

------------------------------------------------------------------------------------------------

 

 

1. Parcel Class 란 ?

 

   

    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를 통해 주고 받을 수는 없습니다.

 

    ( 요약: IBinder를 통해서 보내질수 있는 데이타와 object reference의 메시지를 담고있는 container. )

 

      참고 : http://developer.android.com/reference/android/os/Parcelable.html

 

 

 


 

   2. Parcelable Interface

 

 

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

       그리고 Parcelable Interfaceimplements한 클래스가 (당연히) parcelable 합니다.

     

      사용자에 의해 정의되는 자료형을 알수 없는(int, byte 같은 java의 primitive 자료형이

      아닌) 객체를 생성후 액티비티 간에 전달하고자 할 때 사용합니다.

 

      주의점 : CREATOR라는 Parcelable.Creator 객체를 만들어줘야 한다.

 

      참고 : http://developer.android.com/reference/android/os/Parcelable.html

 

 

  

    3. Parcelable 을 사용할 때 주의 사항

     

         ① read와 write 순서를 주의한다.

         ② 멤버 변수로 boolean은 사용하지 않는다.

         ③ boolean은 byte또는 int로 변환해서 캐스팅 할수 있게 수정한다.

    

    public class TestPacel implements Pacelable

    {

          boolean _Example;

       

          public boolean is_Example(){

                 return _Example;

          }

 

          public void set_Example(boolean example){

                 _Example = example;

          }

    }

 

     이것을 다음과 같이 변형한다.

 

 

     public class TestPacel implements Pacelable

     {

  

          byte _Example;

 

          public boolean is_Example(){

               if ( _Example == 0 ) {

                    return false;

               }

               return true;

          }

 

          public void set_Example(boolean example){

               if ( example == false ) {

                    _Example = 0;

               } else {

                    _Example = 1;

               }

          }

     }

 

 

 

 

          ④ Message의 setData에 Parcel을 추가 하지 않는다.

 

 

     Message msg = new Message();

     msg.what = CMD_TEST

    

     Bundle data = new Bundle();

     data.putParcel("xxxx",yyyyy);

     msg.setData(data);

 

     Intent targetIntent = new Intent( blahblah_receiver);

     targetIntent.putExtra("EXTRA_MSG",msg);

     sendBroadcast(targetIntent);

 

 

     이렇게 데이터 전달을 하게 되면 받는 곳에서 마샬링 에러가 뜬다.

     (android 1.6까지 해당) 하지만 이렇게 바꾸면 문제 없다.

 

 

     Message msg = new Message();

     msg.what = CMD_TEST

    

     Bundle data = new Bundle();

     data.putParcel("xxxx",yyyyy);

    

     Intent targetIntent = new Intent( blahblah_receiver);

     targetIntent.putExtra("EXTRA_MSG",msg);

     targetIntent.putExtra("EXTRA_DATA",data);

     sendBroadcast(targetIntent);

 

 

     Receiver 쪽에서는

 

     public void onReceive(Context context, Intent intent)

     {

 

          Message msg = intent.getParcelableExtra("EXTRA_MSG");
          Bundle data = intent.getParcelableExtra("EXTRA_DATA");

          msg.setData(data);

          process(msg);

     }

 

     요런식으로 처리 하면 된다.

 

 

 

 

 

 


 

 

 

 

앱을 만들다 보면 인텐트를 통해 단순히 String, int, boolean 같은 기본 타입 뿐 아니고 커스텀 클래스나

오브젝트를 다른 컴포넌트에 전달해 줘야 할 경우가 많다. 그 경우 단순히 그냥 인텐트에 putExtra() 로는

넣어줄 수가 없다.


안드로이드에서는 그런 경우를 위해 자바의 Serialization 개념과 유사한 Parcelable 이라는

클래스있다.

먼저 이런것이 왜 필요한가 살펴보겠다. 예를 들어 다음과 같은 클래스가 있다고 하자.

public class BookData {
     int _id;
     String title;
     String author;
     String publisher;
     int price;
}


도서관리 앱에서 ListView로 화면에 표시하기 위해 ArrayList<BookData>에 책들의 정보를 넣어 인텐트로 넘겨주려고 하면 BookData 클래스를 그대로 사용할수는 없다.

오브젝트를 Parcelable 클래스로 만들어 주려면 android.os.Parcelable 인터페이스를 구현해야 한다.

그러므로 아래와 같이 클래스 정의를 변경한다.

public class BookData implements Parcelable {
     int _id;
     String title;
     String author;
     String publisher;
     int price;
}


그리고 android.os.Parcelable 인터페이스에 있는 2개의 메소드를 오버라이드 해 줘야만 한다.

describeContents()

- Parcel 하려는 오브젝트의 종류를 정의한다.


writeToParcel(Parcel dest, int flags)

- 실제 오브젝트 serialization/flattening을 하는 메소드.

   오브젝트의 각 엘리먼트를 각각 parcel 해줘야 한다.

 

public void writeToParcel(Parcel dest, int flags) {
     dest.writeInt(_id);
     dest.writeString(title);
     dest.writeString(author);
     dest.writeString(publisher);
     dest.writeInt(price);
}


다음으로 해야 할 일은 Parcel에서 데이터를 un-marshal/de-serialize하는 단계를 추가해줘야 한다.

그러기 위해서 Parcelable.Creator 타입의 CREATOR라는 변수를 정의해야 한다. 이 변수를 정의하지

않으면 안드로이드는 다음과 같은 익셉션을 발생한다.

Parcelable protocol requires a Parcelable.Creator object called CREATOR


아래는 위의 예제인 BookData 클래스를 위한 Parcelable.Creator<BookData>의 코드이다.

public class CustomCreator implements Parcelable.Creator<BookData> {
     public BookData createFromParcel(Parcel src) {
          return new BookData(src);
     }

     public BookData[] newArray(int size) {
          return new BookData[size];
     }
}


BookData.java에 모든 parcel 된 데이터를 복구하는 생성자를 정의해 줘야만 한다.

public BookData(Parcel src) {
     _id = src.readInt();
     title = src.readString();
     author = src.readString();
     publisher = src.readString();
     price = src.readInt();
}


주의할것은 writeToParcel() 메소드에서 기록한 순서와 동일하게 복구해야만 한다.

 

전체 코드는 다음과 같다.

...
public class BookData implements Parcelable

 

{

     private String title;
     private String author;
     private String publisher;
     private String isbn;
     private String description;
     private int price;
     private String photoUrl;

     public BookData() { }

     public BookData(Parcel in) {
          readFromParcel(in);
     }

     public BookData(String _title, String _author, String _pub, String _isbn, String _desc, int _price, String _photoUrl)  

     {  

          this.title = _title;

          this.author = _author;
          this.publisher = _pub;
          this.isbn = _isbn;
          this.description = _desc;
          this.price = _price;
          this.photoUrl = _photoUrl;
     }

// -------------------------------------------------------------------------
// Getters & Setters section - 각 필드에 대한 get/set 메소드들
// 여기서는 생략했음

// ....
// ....
// -------------------------------------------------------------------------



     public void writeToParcel(Parcel dest, int flags) {
          dest.writeString(title);
          dest.writeString(author);
          dest.writeString(publisher);
          dest.writeString(isbn);
          dest.writeString(description);
          dest.writeString(photoUrl);
          dest.writeInt(price);
     }

     private void readFromParcel(Parcel in){
          title = in.readString();
          author = in.readString();
          publisher = in.readString();
          isbn = in.readString();
          description = in.readString();
          photoUrl = in.readString();
          price = in.readInt();
     }

     public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
          public BookData createFromParcel(Parcel in) {
               return new BookData(in);
          }

          public BookData[] newArray(int size) {
               return new BookData[size];
          }
     };
}


 

Parcelable 오브젝트를 인텐트로 보내는 경우는 다음과 같이 하면 된다.

BookData book = new BookData();

 

// 각 필드에 값을 넣어줌


Intent i = new Intent(this, ShowBook.class);
i.putExtra("bookInfo", book);
startActivity(i);


인텐트를 받을 ShowBook.java에서는 다음과 같이 Parcelable 오브젝트를 복구하면 된다.

Bundle bundle = getIntent().getExtras();
BookData book = bundle.getParcelable("bookInfo");


ArrayList<BookData>인 경우는 Intent를 만들어 보내는 쪽에서는 다음과 같이 하면 된다.

ArrayList<BookData> bookList = new ArrayList<BookData>();
...
// bookList.add() 메소드를 사용해서 bookList에 BookData 엔트리를 추가
...

Intent i = new Intent(this, BookList.class);
i.putParcelableArrayListExtra("myBooks", bookList);
startActivity(i);


BookList.java (인텐트에 의해 호출되는 액티비티)에서는 다음과 같이 오브젝트를 복구하면 된다.

ArrayList<BookData> bookList;
...
Intent i = getIntent();
bookList = i.getParcelableArrayListExtra("myBooks");

 


 

더보기
 

Sample 출처: http://enjoydev.co.kr/65

 

 

 

 

 

 

Sample Code 

 

 Student.java - 객체(모델)이 되는 클래스

package com.test.vo;

 

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

 

/**
* @author sungsik81
*
*/


public class Student implements Parcelable

{
     public String name;
     public int age;
     public String birthday;

     public Student() { }

 

     public Student(Parcel in) {
           readFromParcel(in);
     }

     public Student(String name, int age, String birthday) {
          this.name = name;
          this.age = age;
          this.birthday = birthday;
     }

 

/* (non-Javadoc)
* @see android.os.Parcelable#describeContents()
*
* Parcel 하려는 오브젝트의 종류를 정의한다.
* 어떤 특별한 객체를 포함하고 있는지에 대한 설명을 리턴값으로 표현 하는 것이라고 보면된다.
* bit mask 형식의 integer를 리턴 하며,값을 체크 할 때 bit mask 체크를 해서 어떤 것들이

* 들어 있는지 알 수 있다.
*/
     @Override
     public int describeContents() {
          return 0;
     }

/* (non-Javadoc)
* @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
*
* Parcel에 데이터를 쓰는 부분.
*/
     @Override
     public void writeToParcel(Parcel dest, int flags) {
          dest.writeString(name);
          dest.writeInt(age);
          dest.writeString(birthday);
     }

 

     private void readFromParcel(Parcel in) {
          name = in.readString();
          age = in.readInt();
          birthday = in.readString();
     }

/**
* @author sungsik81
*
* Parcelable.Creator<T> 클래스는 createFromParcel() 과 newArray() 메소스가 필요하다.
* Parcel로 부터 값을 읽어 오기 위해서는 Parcelable.Creator Interface 가 필요하다.
*/
     public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator() {

          // writeToParcel() 메소드에서 썼던 순서대로 읽어 오는 것입니다.
          @Override
          public Object createFromParcel(Parcel source) {
               return new Student(source);
          }

 

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

 

/*
* 이하, Getter/Setter
*/
     public String getName() {
          return name;
     }

 

     public void setName(String name) {
          this.name = name;
     }

 

     public int getAge() {
          return age;
     }

 

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

 

     public String getBirthday() {
          return birthday;
     }

 

     public void setBirthday(String birthday) {
          this.birthday = birthday;
     }
}

 

 

 

 


 

 

    MainActivity.java

 

    package com.test;

 

     import com.test.vo.Student;

     import android.app.Activity;
     import android.content.Intent;
     import android.graphics.drawable.Drawable;
     import android.os.Bundle;
     import android.view.View;
     import android.view.View.OnClickListener;
     import android.widget.Button;
     import android.widget.EditText;

 

     public class MainActivity extends Activity

     {
          Button submitButton;
          EditText myTextView;
          EditText myAge;
          EditText myBirthday;
          String myText;

          @Override
          public void onCreate(Bundle savedInstanceState)

          {
               super.onCreate(savedInstanceState);
               setContentView(R.layout.main);

               myTextView = (EditText) findViewById(R.id.myName);
               myAge = (EditText) findViewById(R.id.myAge);
               myBirthday = (EditText) findViewById(R.id.myBirthday);

               submitButton = (Button) findViewById(R.id.ok);
               submitButton.setOnClickListener(new onClickListener(){

                    String inputMyName;
                    int inputMyAge;
                    String inputMyBirthday;
                    Student student = null;

                    @Override
                    public void onClick(View v) {
                         inputMyName = myTextView.getText().toString();
                         inputMyAge = Integer.valueOf(myAge.getText().toString());
                         inputMyBirthday = myBirthday.getText().toString();

                         student = new Student(inputMyName, inputMyAge, inputMyBirthday);

                         Intent intent = new Intent(MainActivity.this, SubActivity.class);
                         intent.putExtra("STUDENT", student);
                         startActivity(intent);
                   }
               });
          }
     }

 

 

 


 

 

 

SubActivity.java

 

     package com.test;

     

     import com.test.vo.Student;

     import android.app.Activity;
     import android.os.Bundle;
     import android.view.View;
     import android.view.View.OnClickListener;
     import android.widget.Button;
     import android.widget.TextView;

 

     public class SubActivity extends Activity

     {
           Button backBtn;
           Bundle bundle;
           TextView resultMyNameTextView;
           TextView resultMyAgeTextView;
           TextView resultMyBirthdayTextView;

           @Override
           protected void onCreate(Bundle savedInstanceState)

           {
                 super.onCreate(savedInstanceState);
                 setContentView(R.layout.sub);

                 bundle = this.getIntent().getExtras();
                 Student student = bundle.getParcelable("STUDENT");

                 resultMyNameTextView = (TextView) findViewById(R.id.resultMyName);
                 resultMyAgeTextView = (TextView) findViewById(R.id.resultMyAge);
                 resultMyBirthdayTextView = (TextView) findViewById(R.id.resultMyBirthday);

                 resultMyNameTextView.setText(student.getName());
                 resultMyAgeTextView.setText(String.valueOf(student.getAge()));
                 resultMyBirthdayTextView.setText(student.getBirthday());

                 backBtn = (Button) findViewById(R.id.back);
                 backBtn.setOnClickListener(new onClickListener(){

 

                      @Override
                      public void onClick(View v) {
                           onBackPressed();
                      }
                 });
          }

     }

 

 

 


 

 

 

     main.xml

 

     <?xml version="1.0" encoding="utf-8"?>
     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent" >

          <LinearLayout
                android:orientation="horizontal"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content" >

               <TextView
                      android:layout_width="150dip"
                      android:layout_height="wrap_content"
                      android:text="name : " />
           

               <EditText
                      android:id="@+id/myName"
                      android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      android:text="sungsik" />


          </LinearLayout>

          <LinearLayout
                 android:orientation="horizontal"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content" >

                 <TextView
                       android:layout_width="150dip"
                       android:layout_height="wrap_content"
                       android:text="age : "  />

                <EditText
                       android:id="@+id/myAge"
                       android:layout_width="fill_parent"
                       android:layout_height="wrap_content"
                       android:text="30" />


          </LinearLayout>

          <LinearLayout
                 android:orientation="horizontal"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content" >

                <TextView
                       android:layout_width="150dip"
                       android:layout_height="wrap_content"
                       android:text="birthday : "  />

                <EditText
                       android:id="@+id/myBirthday"
                       android:layout_width="fill_parent"
                       android:layout_height="wrap_content"
                       android:text="0810" />

          </LinearLayout>

          <Button
                 android:id="@+id/ok"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:text="Submit To SubActivity" />

     </LinearLayout>


 

 


 


     sub.xml

     <?xml version="1.0" encoding="utf-8"?>
     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" >

            <LinearLayout
                   android:orientation="horizontal"
                   android:layout_width="fill_parent"
                   android:layout_height="wrap_content" >

                   <TextView
                          android:layout_width="150dip"
                          android:layout_height="wrap_content"
                          android:text="name : "  />

                   <TextView
                          android:id="@+id/resultMyName"
                          android:layout_width="fill_parent"
                          android:layout_height="wrap_content" />


           </LinearLayout>

          <LinearLayout
                 android:orientation="horizontal"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content" >

                 <TextView
                        android:layout_width="150dip"
                        android:layout_height="wrap_content"
                        android:text="age : "  />

                 <TextView
                        android:id="@+id/resultMyAge"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content" />

          </LinearLayout>

          <LinearLayout
                 android:orientation="horizontal"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content" >

                <TextView
                       android:layout_width="150dip"
                       android:layout_height="wrap_content"
                       android:text="birthday : "  />

               <TextView
                       android:id="@+id/resultMyBirthday"
                       android:layout_width="fill_parent"
                       android:layout_height="wrap_content" />
         

          </LinearLayout>

          <Button
                 android:id="@+id/back"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:text="Submit To SubActivity" />

     </LinearLayout>

 

 

 


 



    AndroidManifest.xml

     <?xml version="1.0" encoding="utf-8"?>
     <manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="com.test"
            android:versionCode="1"
            android:versionName="1.0">


          <application android:icon="@drawable/icon" android:label="@string/app_name">
               <activity android:name=".MainActivity" android:label="@string/app_name">
                    <intent-filter>
                         <action android:name="android.intent.action.MAIN" />
                         <category android:name="android.intent.category.LAUNCHER" />
                    </intent-filter>
               </activity>

               <activity android:name=".SubActivity" android:label="@string/app_name" />


          </application>

          <uses-sdk android:minSdkVersion="7" />

     </manifest>