Parcelable. Передаём объекты
Очень часто программисту приходится передавать данные из одной активности в другую. Когда речь идёт о простых типах, то используется метод intent.putExtra() и ему подобные. Данный способ годится для типов String, int, long или массивов. А вот объекты таким образом передать не получится.
В Java существует специальный интерфейс Serializable, но он оказался слишком неповоротлив для мобильных устройств и пользоваться им настоятельно не рекомендуется. Для передачи объектов следует использовать другой интерфейс Parcelable.
В интерфейсе Parcelable используются два метода describeContents() и writeToParcel():
Метод describeContents() описывает различного рода специальные объекты, описывающие интерфейс.
Метод writeToParcel() упаковывает объект для передачи.
Также необходимо реализовать статическое поле Parcelable.Creator CREATOR, которое генерирует объект класса-передатчика.
Напишем пример. Попробуем передать объект «Мои документы». Как известно, признаком настоящего документа являются Усы, лапы, хвост (я ещё добавил бы имя). Создадим новый класс DocumentInfo, который реализует интерфейс Parcelable:
Теперь мы может передать объект через Intent. Создадим две активности. В первой активности напишем код для отправки объекта:
Вторая активность должна принять данные от первой активности:
Получив объект из первой активности, мы можем извлечь необходимые данные и разложить по полочкам.
Если класс слишком большой, то вручную его переделывать утомительно. Существует онлайн-сервис parcelabler а также плагин для Android Studio mcharmas/android-parcelable-intellij-plugin: IntelliJ Plugin for Android Parcelable boilerplate code generation.
Источник
Полный список
— знакомимся с 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:
Метод writeParcel – получаем экземпляр Parcel, описываем набор переменных и пишем их в Parcel, используя для этого соответствующие методы. После каждой записи выводим в лог информацию о Parcel, используя метод logWriteInfo.
Метод logWriteInfo пишет в лог данные о Parcel. dataSize – это объем записанных данных.
Методы 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 под каждый символ использует два байта, но пишет еще служебную информацию, поэтому получается больше.
Теперь попробуем прочесть то, что записали. Заполним пустые методы чтения:
В методе readParcel мы устанавливаем (метод setDataPosition) позицию в 0, т.к. нам нужно читать с начала. Читаем данные в том же порядке, как и записывали: byte, int, long, float, double, String. В лог выводим результат чтения и текущую позицию (dataPosition).
Все сохраним, запустим приложение и смотрим лог.
Первые строки лога про запись нам уже знакомы. Нас интересуют строки чтения.
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, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Полный список
— добавляем объекту поддержку 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 пропишем тексты:
В main.xml нарисуем кнопку:
Перед тем как кодить MainActivity.java, создадим свой объект для передачи MyObject.java:
Объект сам по себе несложный: пара переменных s и i, и конструктор. Все остальное используется для реализации Parcelable. Давайте смотреть.
Про метод describeContents ничего сказать не могу. Я не понял, зачем он нужен.
В методе writeToParcel мы получаем на вход Parcel и упаковываем в него наш объект. Т.е., в нашем случае, помещаем туда переменные s и i. flags не используем.
CREATOR типа Parcelable.Creator используется для создания экземпляра нашего MyObject и заполнения его данными из Parcel.
Для этого используется его метод createFromParcel, который мы должны реализовать. На вход нам дается Parcel, а вернуть мы должны готовый MyObject. В нашем примере мы используем здесь конструктор MyObject(Parcel parcel), который реализован чуть дальше.
Смысл метода newArray остался для меня непонятен.
Конструктор MyObject(Parcel parcel) принимает на вход Parcel и заполняет объект данными из него. Этот метод использовался нами чуть ранее в CREATOR.createFromParcel.
Создадим второе Activity, в которое будем передавать объект.
Сначала создаем экран second.xml:
Мы вытаскиваем наш MyObject-объект из Intent и в лог выводим значения s и i.
Создаем 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, и похоже, что при отправке он упаковался в Parcel. Т.к. сам Parcel не знает, как именно упаковать объект, он вызвал метод writeToParcel, где мы реализовали упаковку.
getParcelableExtra – извлекаем объект из Intent
createFromParcel – это был вызван метод CREATOR.createFromParcel, которому на вход дали Parcel, а он должен вернуть MyObject. Этот метод в свою очередь для создания MyObject использует конструктор MyObject(Parcel parcel), в котором мы расписали, как надо читать Parcel и заполнить объект.
myObj: text, 1 – вывели в лог значения объекта.
Итак. Чтобы нам передать объект через Intent, нам надо реализовать в нем интерфейс Parcelable. В этом случае Intent без проблем запакует, передаст и распакует наш объект. И я так подозреваю, что делает он это с помощью Parcel. Т.е. в реализации интерфейса Parcelable мы полностью описываем алгоритм упаковки и распаковки объекта, а Parcel эти алгоритмы потом использует. Т.к. сам он не может знать, как правильно распаковать и создать передаваемый объект.
Если кто разберется, зачем нужны непонятые мною методы – пишите на форуме в ветке этого урока. Я добавлю вашу инфу в урок.
На следующем уроке:
— сохраняем данные при повороте экрана
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Using Parcelable
Due to Android’s memory management scheme, you will often find yourself needing to communicate with different components of your application, system components, or other applications installed on the phone. Parcelable will help you pass data between these components.
Android uses Binder to facilitate such communication in a highly optimized way. The Binder communicates with Parcels, which is a message container. The Binder marshals the Parcel to be sent, sends and receives it, and then unmarshals it on the other side to reconstruct a copy of the original Parcel.
With the update to kotlin, you can use the plugin kotlin-parcelize Add
to the top of your app’s build.gradle . See kotlin-parcelize for more examples of how to use @Parcelize annotation
The simplest way to create a Parcelable in Java is to use a third-party library called Parceler. See this guide on Parceler to see how to use it. By annotating your Java classes that you intend to use, the library is able to create much of the boilerplate code needed as discussed in the manual steps below.
To allow for your class instances to be sent as a Parcel you must implement the Parcelable interface along with a static field called CREATOR , which itself requires a special constructor in your class.
Here is a typical implementation:
Note that the Parcelable interface has two methods defined: int describeContents() and void writeToParcel(Parcel dest, int flags) . After implementing the Parcelable interface, we need to create the Parcelable.Creator CREATOR constant for our class which requires us to define createFromParcel , newArray .
We can now pass the parcelable data between activities within an intent:
and then access the data in the NewActivity that was launched using:
Now we can access the parcelable data from within the launched activity!
If a launched activity is returning a data result back to the parent activity, the onActivityResult() method in the parent activity is invoked:
Instead of using getIntent() to retrieve the passed object in this case, we access the Intent object via the parameter representing the result (in this example, «data»).
You may notice some similarities between Parcelable and Serializable . DO NOT, I repeat, DO NOT attempt to persist Parcel data. It is meant for high-performance transport and you could lose data by trying to persist it.
Using Parcelable compared to Serializable can achieve up to 10x performance increase in many cases for transport which is why it’s the Android preferred method.
There are a few common gotchas associated to Parcelable to consider below:
One very important thing to pay close attention to is the order that you write and read your values to and from the Parcel. They need to match up in both cases. In my example, I write the int and then the String to the Parcel. Afterwards, I read them in that same exact order. The mechanism that Android uses to read the Parcel is blind and completely trusts you to get the order correct, or else you will run into run-time crashes.
Another problem I have encountered is with ClassNotFound exceptions. This is an issue with the Classloader not finding your class. To fix this you can manually set the Classloader to use. If nothing is set, then it will try the default Classloader which leads to the exception.
As mentioned before you can only put primitives, lists and arrays, Strings, and other Parcelable objects into a Parcel. This means that you cannot store framework dependent objects that are not Parcelable. For example, you could not write a Drawable to a Parcel. To work around this problem, you can instead do something like writing the resource ID of the Drawable as an integer to the Parcel. On the receiving side you can try to rebuild the Drawable using that. Remember, Parcel is supposed to be fast and lightweight! (though it is interesting to see Bitmap implementing Parcelable)
Where is boolean !? For whatever odd reason there is no simple way to write a boolean to a Parcel. To do so, you can instead write a byte with the corresponding value with out.writeByte((byte) (myBoolean ? 1 : 0)); and retrieve it similarly with myBoolean = in.readByte() != 0;
There is a Parcelable plugin that can be imported directly into IntelliJ or Android Studio, which enables you to generate the boilerplate code for creating Parcelables. You can install this plugin by going to Android Studio -> File -> Settings -> Plugins -> Browse repositories :
Источник