Смотрите видеоурок бесплатно на удобной для вас платформе:
Введение
На предыдущем уроке мы узнали что такое объект Intent, как с помощью него осуществлять открытие нового Activity и передавать простые типы данных. Это база, так что если не смотрели, обязательно начните с предыдущего видео. Продолжаем тему и переходим к самому интересному – передаче объектов между Activity.
Рассматривать будем прямые способы передачи данных с помощью интерфейсов Serializable и Parcelable, а также специального класса Bundle.
Тестировать передачу данных будем все также в рамках наших демонстрационных Активити. А вот дополнительный класс, который будем перекидывать, придумывать не нужно. Так как мы разрабатываем приложение для изучения иностранных слов – здесь лежит файлик с логикой, где есть класс для хранения изучаемого слова. Я скопирую его в свою демо Активити и переименую, чтобы он не аффектил основной код. Если что ссылка на полный код есть в телеграм канале.
Создам объект слова с рандомными данными. Наша задача будет передать его во вторую Активити и отобразить одно из его свойств в уже созданную View с айдишником tvText
.
val word = ExtraWord(
"galaxy",
"галактика",
)
Попробую передать объект с использованием putExtra, также прописываю уникальный ключ и вижу ошибку. Наводим курсор и видим список валидных типов. Все значения вам должны быть знакомы за исключением трех.
Это как раз то, что позволит нам передавать именно объекты.
Serializable и Parcelable. Это два интерфейса в Android, которые используются для сериализации объектов, то есть преобразования объектов в форму (в поток байтов), которую можно сохранить или передать между компонентами приложения.
Обратный процесс сериализации – это десериализация. То есть процесс восстановления объектов из их сериализованного представления. Теперь подробнее о каждом из них.
Интерфейс Serializable в Kotlin / Android
Интерфейс Serializable — классический способ сериализации объектов в Java. Этот интерфейс не имеет методов, но он указывает JVM, что объекты этого класса могут быть сериализованы. Он работает, создавая копию объекта в памяти и затем записывая эту копию в байтовый поток. Serializable обычно используется в случаях, когда требуется сериализация объектов, которые содержат небольшой объем данных.
Под небольшим объемом данных имею в виду как раз объекты с несколькими свойствами, типа слова, как у нас. Или, например, часто встречающийся объект User с набором свойств типа id, name и так далее.
Как отправить Serializable объект
Чтобы объект можно было отправить через Extra – необходимо наследовать класс от интерфейса Serializable. Это все, что нужно сделать. Как видим ошибка пропала.
data class ExtraWord(
val original: String,
val translate: String,
var learned: Boolean = false,
) : Serializable
Еще раз, что здесь происходит:
- Вызываем
putExtra()
и передаем данные в виде пары ключ-значение. - Внутри
Intent
‘a создается (или используется существующий) объектBundle
.Bundle
— это контейнер для хранения данных. Он может сохранять как примитивные типы (например, строки и числа), так и сериализованные объекты (которые реализуют либоSerializable
, либоParcelable
). Напомню, на прошлом уроке мы смотрели сигнатуру методаputExtra()
и убедились, что под капотом метода используетсяBundle
. - Далее, если объект реализует один из этих интерфейсов (как сейчас), он сериализуется (то есть преобразуется в поток байтов) и добавляется в
Bundle
.
Как получить Serializable объект
Получение Serializable с помощью intent.getSerializableExtra
Чтобы получить объект также обращаюсь к проперти intent и вызываю метод getSerializableExtra.
Смотрите, здесь два одинаковых метода с разной сигнатурой. Первый включает два параметра, где вторым параметром уточняется тип передаваемого объекта. Нам нужен именно он. Это более свежая версия метода, сейчас вы поймете как использовать их оба.
Вписываю ключ, по которому нужно получить объект из интента, а вторым параметром указываем ссылку на data class нашего слова. Причем через контекстное меню можно выполнить импорт класса в файл, чтобы сократить код и оставить только его название.
intent.getSerializableExtra("EXTRA_KEY_WORD", ExtraWord::class.java)
Все хорошо, но метод подсвечивается красным. Call requires API level 33 (current min is 26)
говорит о том, что этот метод актуален для версии API от 33. А в настройках градл у нас минимально поддерживаемая версия на момент записи ролика стоит 26.
Студия предлагает повесить аннотацию, мол требуется версия Тирамису. Но от этого на устройствах с API до 33 версии все равно ничего работать не будет. Нам нужен второй deprecated метод, который не требует второго параметра с уточнением передаваемого класса. Продублирую его здесь для наглядности и добавлю переменные, чтобы посмотреть на тип, который будет в них приходить.
intent.getSerializableExtra("EXTRA_KEY_WORD", ExtraWord::class.java)
intent.getSerializableExtra("EXTRA_KEY_WORD") // deprecated
Как видим устаревший метод возвращает тип Serializable
, который нужно самостоятельно привести к требуемому типу. И это небезопасное приведение типа.
В другом методе благодаря тому, что мы передаем в качестве второго параметра класс – метод автоматически делает приведение к нужному типу. Тем самым гарантируя безопасность.
Но как убрать ошибку? Мы можем проверять текущую версию API операционной системы и в зависимости от нее вызывать нужный метод получения объекта. Вызываем контекстное меню и добавляем условие проверки текущей версии SDK.
И в else
будет запускаться код на устройствах ниже версии TIRAMISU. Можно перейти в переменную и убедиться, что это 33 версия API. Сюда переносим deprecated функцию чтения Extra. Причем она возвращает тип Serializable?
, тем самым теперь переменная word стала с таким же типом. Поэтому в конце везде нужно выполнить ручное приведение типа к нужному нам ненулябельному классу с помощью ключевого слова as
.
val word: ExtraWord = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getSerializableExtra("EXTRA_KEY_WORD", ExtraWord::class.java) as ExtraWord
} else {
intent.getSerializableExtra("EXTRA_KEY_WORD") as ExtraWord
}
Получение Serializable с помощью intent.extras?.getSerializable
Есть еще пара форм записей обращения к экстра у интента. Например, можно обратиться к проперти extras (бывший метод getExtras()), затем вызвать getSerializable(). Тот же самый принцип работы, только это метод класса Bundle. А getSerializableExtra() располагается в классе Intent. Вот и вся разница.
val word2 = intent.extras?.getSerializable("EXTRA_KEY_WORD", ExtraWord::class.java)
Получение Serializable с помощью intent.extras?.get
Ну или вы можете встретить совсем древний метод get, который возвращает Any и также надо было руками приводить переданные данные к требуемому типу.
val word3 = intent.extras?.get("EXTRA_KEY_WORD")
Я рекомендую использовать самый первый актуальный способ, а этими поделился с вами для прокачивания вашей насмотренности.
Все, мне остается только добавить TextView для выводимого слова. Свойств в объекте несколько, но я ограничусь просто выводом оригинального слова. Сам факт передачи объекта таким образом на новый экран полагаю должен быть понятен. Обращаюсь к объекту и к свойству original
. Запускаем и проверяем вывод.
Проблемы с производительностью у Serializable
Резюмируя можно сказать, что это действительно простой и удобный способ передачи простых структур данных. Однако, для сложных объектов Serializable не очень подходит и вот почему:
Serializable
использует стандартную Java-сериализацию, которая преобразует объект в поток байтов через механизм рефлексии. Это делает его относительно медленным, за счет того, что JVM (Java Virtual Machine) анализирует объект на лету .Serializable
также создает множество временных объектов в процессе сериализации, что может приводить к повышенному расходу памяти, особенно на устройствах с ограниченными ресурсами.
Для тех, кто собрался стать Android-разработчиком
Пошаговая
схема
Описание процесса обучения от основ Kotlin до Android-разработчика
Бесплатные
уроки
Авторский бесплатный курс по основам языка программирования Kotlin
Обучающий
бот
Тренажер и самоучитель по Котлин – бесплатные тесты и практика