Урок 69. Передаем Parcelable объекты с помощью Intent


В этом уроке:

- добавляем объекту поддержку Parcelable
- передаем с помощью Intent

С Parcel мы немного поработали на прошлом уроке. Этих знаний хватит, чтобы понять, как реализовать в своем объекте интерфейс Parcelable. Создадим свой объект, реализуем в нем интерфейс Parcelable и попробуем передать в другое Activity через Intent.


Создадим проект:

Project name: P0691_Parcelable
Build Target: Android 2.3.3
Application name: Parcelable
Package name: ru.startandroid.develop.p0691parcelable
Create Activity: MainActivity

В strings.xml пропишем тексты:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="main">MainActivity</string>
<string name="second">SecondActivity</string>
<string name="send">Send</string>
<string name="app_name">Parcelable</string>
</resources>


В main.xml нарисуем кнопку:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/main">
</TextView>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onclick"
android:text="@string/send">
</Button>
</LinearLayout>


Перед тем как кодить MainActivity.java, создадим свой объект для передачи MyObject.java:

package ru.startandroid.develop.p0691parcelable;

import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;

public class MyObject implements Parcelable {
 
 
final static String LOG_TAG = "myLogs";

 
public String s;
 
public int i;

 
// обычный конструктор
 
public MyObject(String _s, int _i) {
   
Log.d(LOG_TAG, "MyObject(String _s, int _i)");
    s = _s;
    i = _i;
 
}

 
public int describeContents() {
   
return 0;
 
}

 
// упаковываем объект в Parcel
 
public void writeToParcel(Parcel parcel, int flags) {
   
Log.d(LOG_TAG, "writeToParcel");
    parcel.writeString
(s);
    parcel.writeInt
(i);
 
}

 
public static final Parcelable.Creator<MyObject> CREATOR = new Parcelable.Creator<MyObject>() {
   
// распаковываем объект из Parcel
   
public MyObject createFromParcel(Parcel in) {
     
Log.d(LOG_TAG, "createFromParcel");
     
return new MyObject(in);
   
}

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

 
// конструктор, считывающий данные из Parcel
 
private MyObject(Parcel parcel) {
   
Log.d(LOG_TAG, "MyObject(Parcel parcel)");
    s = parcel.readString
();
    i = parcel.readInt
();
 
}

}

Объект сам по себе несложный: пара переменных s и i, и конструктор. Все остальное используется для реализации Parcelable. Давайте смотреть.

Про метод describeContents ничего сказать не могу. Я не понял, зачем он нужен.

В методе writeToParcel мы получаем на вход Parcel и упаковываем в него наш объект. Т.е., в нашем случае, помещаем туда переменные s и i.  flags не используем.

CREATOR типа Parcelable.Creator<MyObject> используется для создания экземпляра нашего MyObject и заполнения его данными из Parcer.

Для этого используется его метод createFromParcel, который мы должны реализовать. На вход нам дается Parcel, а вернуть мы должны готовый MyObject. В нашем примере мы используем здесь конструктор MyObject(Parcel parcel), который реализован чуть дальше.

Смысл метода newArray остался для меня непонятен.

Конструктор MyObject(Parcel parcel) принимает на вход Parcel и заполняет объект данными из него. Этот метод использовался нами чуть ранее в CREATOR.createFromParcel.


Создадим второе Activity, в которое будем передавать объект.

Сначала создаем экран second.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/second">
</TextView>
</LinearLayout>


Кодим SecondActivity.java:

package ru.startandroid.develop.p0691parcelable;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class SecondActivity extends Activity {
 
 
final String LOG_TAG = "myLogs";

 
protected void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
    setContentView
(R.layout.second);
    Log.d
(LOG_TAG, "getParcelableExtra");
    MyObject myObj =
(MyObject) getIntent().getParcelableExtra(
       
MyObject.class.getCanonicalName());
    Log.d
(LOG_TAG, "myObj: " + myObj.s + ", " + myObj.i);
 
}

}

Мы вытаскиваем наш MyObject-объект из Intent и в лог выводим значения s и i.


Кодим MainActivity.java:

package ru.startandroid.develop.p0691parcelable;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class MainActivity extends Activity {

 
final String LOG_TAG = "myLogs";

 
/** Called when the activity is first created. */
 
public void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
    setContentView
(R.layout.main);
 
}

 
public void onclick(View v) {
   
MyObject myObj = new MyObject("text", 1);
    Intent intent =
new Intent(this, SecondActivity.class);
    intent.putExtra
(MyObject.class.getCanonicalName(), myObj);
    Log.d
(LOG_TAG, "startActivity");
    startActivity
(intent);
 
}
}


Создаем Intent, помещаем туда объект MyObject. В качестве ключа используем его имя класса (разумеется, это необязательно, вы можете свое имя использовать). И отправляем Intent с вызовом SecondActivity.

Все сохраним и запустим приложение.



Жмем Send, Intent уходит в SecondActivity



Смотрим лог:

MyObject(String _s, int _i)
startActivity
writeToParcel
getParcelableExtra
createFromParcel
MyObject(Parcel parcel)
myObj: text, 1


Сначала вызвался конструктор MyObject(String _s, int _i) – это мы создали myObj.

startActivity – начинаем вызов Activity

writeToParcel  - мы поместили объект в Intent, и похоже, что при отправке он упаковался в Parcer. Т.к. сам Parcer не знает, как именно упаковать объект, он вызвал метод writeToParcel, где мы реализовали упаковку.

getParcelableExtra – извлекаем объект из Intent

createFromParcel – это был вызван метод CREATOR.createFromParcel, которому на вход дали Parcer, а он должен вернуть MyObject. Этот метод в свою очередь для создания MyObject использует конструктор MyObject(Parcel parcel), в котором мы расписали, как надо читать Parcer и заполнить объект.

myObj: text, 1 – вывели в лог значения объекта.


Итак. Чтобы нам передать объект через Intent, нам надо реализовать в нем интерфейс Parcelable. В этом случае Intent без проблем запакует, передаст и распакует наш объект. И я так подозреваю, что делает он это с помощью Parcel. Т.е. в реализации интерфейса Parcelable мы полностью описываем алгоритм упаковки и распаковки объекта, а Parcel эти алгоритмы потом использует. Т.к. сам он не может знать, как правильно распаковать и создать передаваемый объект.


Если кто разберется, зачем нужны непонятые мною методы – пишите на форуме в ветке этого урока. Я добавлю вашу инфу в урок.


На следующем уроке:

- сохраняем данные при повороте экрана