Parcelable. Передаём объекты
Очень часто программисту приходится передавать данные из одной активности в другую. Когда речь идёт о простых типах, то используется метод intent.putExtra() и ему подобные. Данный способ годится для типов String, int, long или массивов. А вот объекты таким образом передать не получится.
В Java существует специальный интерфейс Serializable, но он оказался слишком неповоротлив для мобильных устройств и пользоваться им настоятельно не рекомендуется. Для передачи объектов следует использовать другой интерфейс Parcelable.
В интерфейсе Parcelable используются два метода describeContents() и writeToParcel():
@Override public int describeContents() < >@Override public void writeToParcel(Parcel dest, int flags)
Метод describeContents() описывает различного рода специальные объекты, описывающие интерфейс.
Метод writeToParcel() упаковывает объект для передачи.
Также необходимо реализовать статическое поле Parcelable.Creator CREATOR, которое генерирует объект класса-передатчика.
Напишем пример. Попробуем передать объект «Мои документы». Как известно, признаком настоящего документа являются Усы, лапы, хвост (я ещё добавил бы имя). Создадим новый класс DocumentInfo, который реализует интерфейс Parcelable:
package ru.alexanderklimov.parcelable; import android.os.Parcel; import android.os.Parcelable; public class DocumentInfo implements Parcelable < private String mName; // имя кота private String mWhiskers; // усы private String mPaws; // лапы private String mTail; // хвост public DocumentInfo(String name, String whiskers, String paws, String tail) < mName = name; mWhiskers = whiskers; mPaws = paws; mTail = tail; >public DocumentInfo(Parcel in) < String[] data = new String[4]; in.readStringArray(data); mName = data[0]; mWhiskers = data[1]; mPaws = data[2]; mTail = data[3]; >public void setCatName(String name) < mName = name; >public String getCatName() < return mName; >public void setWhiskers(String whiskers) < mWhiskers = whiskers; >public String getWhiskers() < return mWhiskers; >public String getPaws() < return mPaws; >public String getTail() < return mTail; >@Override public int describeContents() < return 0; >@Override public void writeToParcel(Parcel dest, int flags) < dest.writeStringArray(new String[] < mName, mWhiskers, mPaws, mTail >); > public static final Parcelable.Creator CREATOR = new Parcelable.Creator() < @Override public DocumentInfo createFromParcel(Parcel source) < return new DocumentInfo(source); >@Override public DocumentInfo[] newArray(int size) < return new DocumentInfo[size]; >>; >
Теперь мы может передать объект через Intent. Создадим две активности. В первой активности напишем код для отправки объекта:
public void onClick(View view)
Вторая активность должна принять данные от первой активности:
@Override protected void onCreate(Bundle savedInstanceState)
Получив объект из первой активности, мы можем извлечь необходимые данные и разложить по полочкам.
Если класс слишком большой, то вручную его переделывать утомительно. Существует онлайн-сервис parcelabler а также плагин для Android Studio mcharmas/android-parcelable-intellij-plugin: IntelliJ Plugin for Android Parcelable boilerplate code generation.
Parcelize. Или как одна аннотация упрощает работу с Parcelable в Kotlin.
Serializable — интерфейс стандартной Java библиотеки. Его очень просто имплементировать, но он бьёт по скорости, так как использует рефлексию и создаёт большое количество временных объектов. В итоге наше приложение начинает тормозить из-за частого вызова сборщика мусора. Есть ли альтернатива для использования в ОС Анроид?
Тут на помощь приходит другой интерфейс. Parcelable. Он явно описывает процесс сериализации без использования рефлексии. В добавок было проведено немало оптимизаций кода, чтобы повысить производительность. Всё прекрасно, но у нас появляется большое количество boilerplate кода. Как можно этого избежать при написании приложения на Kotlin?
Parcelize
Данная аннотация позволяет расширению генерировать логику сереализации/десериализации для полей класса. В результате, методы writeToParcel() и createFromParcel() будут сгенерированы автоматически. Рассмотрим два примера. Класс-сущность на Kotlin с использованием аннотации Parcelize и без.
Довольно большое количество кода. В случае, если нам необходимо в какой-то момент времени менять поля класса-сущности, то ещё придётся и менять логику сериализации. Это не всегда удобно. К тому же приходится создавать Companion object для поля Creator. Да, можно конечно использовать различные плагины для генерации этого кода, но нас никто не избавлял от его поддержки.
Как мы видим, одна аннотация скрывает весь boilerplate код и теперь нам больше не нужно париться за процесс сериализации. Лишь достаточно добавить @Parcelize в начале объявления класса.
Для того, чтобы иметь возможность использовать эту аннотацию, необходима версия Котлина 1.1.4 или выше. Данная аннотация является экспериментальной, так что нужно добавить в файл проекта градла код ниже.
androidExtensions experimental = true
>
Также в файл градла следует добавить apply plugin: ‘kotlin-android-extensions’. Если проект создавался изначально с поддержкой котлина, то поддержка этого плагина подключена по умолчанию.
И это всё! Осталось только пометить класс аннотацией @Parcelize. Если Lint будет ругаться на то, что нету поля Creator, то можно проигнорировать и сбилдить приложение. Android Studio перестанет обращать на это внимание.
Стоит отметить, что если вам необходима своя реализация процессов сериализации/десериализации, то это можно сделать с помощью написания своего Parceler’a в качестве Companion object.
Надеюсь, статья была полезной 🙂 По всем вопросам можно обращаться ко мне в телеграм t.me/Arzumanyasha
Kotlin parcelize как использовать
Возможность сериализации объектов предоставляется напрямую инфраструктурой языка Java. Однако Android также предоставляет интерфейс Parcelable , который по сути также позволяет сериализовать объекты, как и Serializable, но является более оптимизированным для Android. И подобные объекты Parcelable также можно передавать между двумя activity или использовать каким-то иным образом.
Например, в прошлой теме данные передавались между activity в виде объектов User, которые использовали сериализацию. Теперь пусть класс User применяет интерфейс Parcelable:
package com.example.viewapp; import android.os.Parcel; import android.os.Parcelable; public class User implements Parcelable < private String name; private String company; private int age; public static final CreatorCREATOR = new Creator() < @Override public User createFromParcel(Parcel source) < String name = source.readString(); String company = source.readString(); int age = source.readInt(); return new User(name, company, age); >@Override public User[] newArray(int size) < return new User[size]; >>; public User(String name, String company, int age) < this.name = name; this.company = company; this.age = age; >public String getName() < return name; >public void setName(String name) < this.name = name; >public String getCompany() < return company; >public void setCompany(String company) < this.company = company; >public int getAge() < return age; >public void setAge(int age) < this.age = age; >@Override public int describeContents() < return 0; >@Override public void writeToParcel(Parcel dest, int flags) < dest.writeString(name); dest.writeString(company); dest.writeInt(age); >>
Интерфейс android.os.Parcelable предполагает реализацию двух методов: describeContents() и writeToParcel() . Первый метод описывает контент и возвращает некторое числовое значение. Второй метод пишет в объект Parcel содержимое объекта User.
Для записи данных объекта в Parcel используется ряд методов, каждый из которых предназначен для определенного типа данных. Основные методы:
- writeString()
- writeInt()
- writeFloat()
- writeDouble()
- writeByte()
- writeLong()
- writeIntArray()
- writeValue() (записывает объект типа Object)
- writeParcelable() (записывает объект типа Parcelable)
Кроме того, объект Parcelable должен содержать статическое поле CREATOR, которое представляет объект Creator . Причем этот объект реализует два метода. Они нужны для создания их ранее сериализованных данных исходных объектов типа User.
Так, метод newArray() создает массив объект User.
Метод createFromParcel создает из Parcel новый объект типа User. То есть этот метод противоположен по действию методу writeToParcel. Для получения данных из Parcel применяются методы типа readString() , readInt() , readParcelable() и так далее — для чтения определенных типов данных.
Причем важно, что данные в createFromParcel считываются из объекта Parcel именно в том порядке, в котором они добавляются в этот объект в методе writeToParcel.
Допустим в activity, которая называется SecondActivity мы будем получать объект User:
package com.example.viewapp; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class SecondActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); TextView textView = new TextView(this); textView.setTextSize(26); textView.setPadding(16, 16, 16, 16); Bundle arguments = getIntent().getExtras(); User user; if(arguments!=null)< user = arguments.getParcelable(User.class.getSimpleName()); textView.setText("Name: " + user.getName() + "\nCompany: " + user.getCompany() + "\nAge: " + String.valueOf(user.getAge())); >setContentView(textView); > >
Для получения объекта Parcelable, переданного в activity, применяется метод getParcelable() . Причем никакого приведения типов не требуется.
Для тестирования передачи Parcelable определим в файле activity_main.xml простейший интерфейс для MainActivity:
А в коде MainActivity определим передачу данных в SecondActivity:
package com.example.viewapp; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.EditText; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); >public void onClick(View v) < EditText nameText = findViewById(R.id.name); EditText companyText = findViewById(R.id.company); EditText ageText = findViewById(R.id.age); String name = nameText.getText().toString(); String company = companyText.getText().toString(); int age = Integer.parseInt(ageText.getText().toString()); User user = new User(name, company, age); Intent intent = new Intent(this, SecondActivity.class); intent.putExtra(User.class.getSimpleName(), user); startActivity(intent); >>
Урок 68. Немного о Parcel
Сам по себе Parcel мне никогда еще использовать не приходилось и не знаю, придется ли. Меня он заинтересовал, когда я начал разбираться с интерфейсом Parcelable. Этот интерфейс используется при передаче объектов через Intent и мне стало интересно, как создавать свои объекты с поддержкой такой передачи. В итоге я немного разобрался в Parcel и Parcelable, хотя понял далеко не все. Попробую теперь рассказать об этом.
Parcel – это контейнер для передачи данных. У него есть куча методов для помещения и извлечения данных. В этом уроке рассмотрим самые простейшие из них.
Project name: P0681_Parcel
Build Target: Android 2.3.3
Application name: Parcel
Package name: ru.startandroid.develop.p0681parcel
Create Activity: MainActivity
В этом уроке экран нам не понадобится, main.xml оставляем без изменений. Работать будем с логом.
Кодим в MainActivity.java:
Методы readParcel и logReadInfo – пока пустые. Позже заполним.
Все сохраняем и запускаем приложение. Смотрим лог.
before writing: dataSize = 0
byte: dataSize = 4
int: dataSize = 8
long: dataSize = 16
float: dataSize = 20
double: dataSize = 28
String: dataSize = 52
Разбираем по порядку.
before writing: перед записью у нас размер данных равен 0. Записали byte: dataSize = 4 (для записи данных типа byte использовались 4 байта). Записали int: dataSize = 8 (для записи данных типа int использовались еще 4 байта в дополнение к ранее заполненным 4 байтам для byte). Записали long: dataSize = 16 (для записи long использовались еще 8 байтов в дополнение к ранее заполненным 8 байтам для byte и int). И т.д. В итоге видим, что dataSize показывает, сколько всего занято байт.
Обратите внимание, что типы int, long, float и double заняли столько байт, сколько они действительно занимают в Java – соответственно 4, 8, 4 и 8. byte – вместо одного байта почему-то занял целых 4. А String под каждый символ использует два байта, но пишет еще служебную информацию, поэтому получается больше.
Теперь попробуем прочесть то, что записали. Заполним пустые методы чтения:
Все сохраним, запустим приложение и смотрим лог.
Первые строки лога про запись нам уже знакомы. Нас интересуют строки чтения.
before reading: dataPosition = 52
byte = 1: dataPosition = 4
int = 2: dataPosition = 8
long = 3: dataPosition = 16
float = 4.0: dataPosition = 20
double = 5.0: dataPosition = 28
string = abcdefgh: dataPosition = 52
Перед тем, как мы установим позицию в 0 (before reading), видим, что она равна 52. Там она находится после записи. Каждая запись данных перемещает позицию на кол-во, равное размеру записываемых данных. Размер всех последовательно записанных данных у нас составил 52, и позиция соответственно переместилась в 52. Вы можете в качестве эксперимента выводить в лог позицию после каждой записи данных. Я же вывожу только для процедур чтения.
Итак, мы устанавливаем позицию в 0 и начинаем читать данные. Прочли значение byte, оно равно 1, как мы и записывали. Позиция сместилась на размер прочтенного значения, и теперь мы будем читать с позиции 4. Читаем int, оно равно 2, позиция сместилась и равна 8. И т.д.
Все значения, которые мы последовательно записывали, мы в том же порядке считали. Здесь надо понимать, что если вы записали int, а читать потом будете double, то результат получится не тот, что нужен. Т.к. int пишет 4 байта, а double считывает 8. Тем самым он залезет на следующий записанный тип и возьмет из него недостающие 4 байта. Получится каша. Поэтому тут надо быть аккуратным.
Вы всегда можете установить нужную вам позицию и считать хранимое значение. Главное – знать, какой тип там хранится. Например, у нас сейчас при записи double пишется с позиции 20. Поэтому мы можем перевести позицию в 20 и выполнить readDouble. Мы успешно получим записанный туда double, а позиция станет равна 28.
Если вы хотите глянуть содержимое Parcel можно использовать его метод marshall(), он вернет массив записанных в Parcel байтов.
Вот такой краткий экскурс. Эти знания понадобятся для понимания следующего урока.
На следующем уроке:
— добавляем своему объекту поддержку Parcelable
— передаем объект с помощью Intent
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня