Полный список
— сохраняем данные при повороте экрана
Теорию по этому вопросу можно почитать тут. Я здесь вкратце дам вольный перевод.
Когда работа Activity приостанавливается(onPause или onStop), она остается в памяти и хранит все свои объекты и их значения. И при возврате в Activity, все остается, как было. Но если приостановленное Activity уничтожается, например, при нехватке памяти, то соответственно удаляются и все его объекты. И если к нему снова вернуться, то системе надо заново его создавать и восстанавливать данные, которые были утеряны при уничтожении. Для этих целей Activity предоставляет нам для реализации пару методов: первый позволяет сохранить данные – onSaveInstanceState, а второй – восстановить — onRestoreInstanceState.
Эти методы используются в случаях, когда Activity уничтожается, но есть вероятность, что оно еще будет востребовано в своем текущем состоянии. Т.е. при нехватке памяти или при повороте экрана. Если же вы просто нажали кнопку Back (назад) и тем самым явно сами закрыли Activity, то эти методы не будут выполнены.
Но даже если не реализовать эти методы, у них есть реализация по умолчанию, которая сохранит и восстановит данные в экранных компонентах. Это выполняется для всех экранных компонентов, у которых есть ID.
Создадим простое приложение, чтобы протестить все эти тезисы. Посмотрим, в какой момент вызываются эти методы, попробуем в них что-нить сохранить. Также убедимся, что необходимо вызывать соответствующие методы супер-класса, чтобы сохранялись данные экранных компонентов.
Т.к. нам надо будет поворачивать экран, используйте при разработке Android 2.2. В AVD с версией 2.3 поворот глючит.
Project name: P0701_SaveInstanceState
Build Target: Android 2.2
Application name: SaveInstanceState
Package name: ru.startandroid.develop.p0701saveinstancestate
Create Activity: MainActivity
В strings.xml пропишем тексты:
В main.xml нарисуем кнопку и пару полей для ввода текста:
Обратите внимание, что второй EditText без ID.
В MainActivity будем вызывать все методы Lifecycle и два вышеописанных:
В каждом из них пишем лог, чтобы отследить последовательность вызовов. Метод onclick пока не реализуем.
Все сохраним и запустим. Введем в текстовые поля какие-нить данные:
и повернем экран CTRL+F12.
Данные в первом поле сохранились при повороте, а во втором пропали. Это произошло потому, что дефолтовые методы сохранения/восстановления умеют работать только с компонентами, которые имеют ID. Посмотрим лог.
Эти три метода выполнились при запуске.
Затем мы повернули экран:
onSaveInstanceState
onPause
onStop
onDestroy
onCreate
onStart
onRestoreInstanceState
onResume
Первым делом вызывается onSaveInstanceState, здесь нам надо будет реализовывать сохранение своих данных. Далее идет уничтожение Activity (onPause, onStop, onDestroy) и создание нового onCreate, onStart. И перед onResume вызывается метод восстановления данных – onRestoreInstanceState.
Последовательность мы рассмотрели — сохраняются данные перед onPause, а восстанавливаются перед onResume. Попробуем теперь что-нибудь сохранить и восстановить. У нас на экране есть кнопка, будем по ее нажатию увеличивать счетчик нажатий на единицу и выводить всплывающее сообщение с итоговым кол-вом нажатий. Переменная cnt у нас уже есть. Реализуем onclick:
Повернем эмулятор обратно в вертикальную ориентацию. Запустим приложение, и жмем на кнопку Count. Видим сообщение с кол-вом нажатий. Нажмем еще несколько раз, получим, например 5.
Теперь повернем экран и снова нажмем кнопку.
Мы видим, что счетчик сбросился.
Это произошло потому, что текущий объект Activity был уничтожен и потерял значения всех переменных, в том числе и cnt. При создании нового Activity значение cnt равно 0 и отсчет пошел заново. Давайте это пофиксим. Реализуем метод сохранения onSaveInstanceState:
В объект outState мы пишем значение переменной cnt. Механизм аналогичен помещению данных в Intent.
Метод восстановления onRestoreInstanceState:
Из savedInstanceState вытаскиваем значение и помещаем в переменную cnt. Теперь при уничтожении и воссоздании Activity переменная cnt сохранит свое значение, и наш счетчик продолжит работать.
Проверим. Вернем AVD в вертикальную ориентацию. Все сохраним, запустим приложение. Понажимаем на кнопку, немного накрутим счетчик
и поворачиваем экран.
Жмем снова кнопку
счетчик не сбросился, а продолжил увеличиваться с последней позиции.
Итак, методы onSaveInstanceState и onRestoreInstanceState по дефолту сохраняют данные в экранных компонентах. Если мы реализуем их самостоятельно, то вызываем методы супер-класса и пишем свой код для своих переменных. Ради интереса, можете попробовать убрать вызовы методов суперкласса из onSaveInstanceState и onRestoreInstanceState. Данные в текстовом поле перестанут сохраняться при повороте экрана.
Кроме метода onRestoreInstanceState, доступ к сохраненным данным также можно получить в методе onCreate. На вход ему подается тот же самый Bundle. Если восстанавливать ничего не нужно, он будет = null.
Есть еще один полезный механизм сохранения данных. Android дает нам возможность сохранить ссылку на какой-либо объект и вернуть ее в новый созданный Activity. Для этого существуют методы:
onRetainNonConfigurationInstance – в нем мы сохраняем ссылку, передавая ее на выход (return) метода
Т.е., например, у нас есть какой то объект myObj (класс MyObject) и нам надо сохранить ссылку на него при повороте экрана.
Мы реализуем в Activity метод onRetainNonConfigurationInstance:
Этот метод будет вызван перед уничтожением Activity. От нас требуется дать на выход этому методу наш объект, который надо сохранить.
А, при создании нового Activity, в onCreate (например) мы используем метод getLastNonConfigurationInstance:
Мы получили обратно объект класса Object и привели его к нашему классу MyObject.
На следующем уроке:
— используем Preferences для работы с настройками приложения
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Повороты экрана в Android без боли
Важно!
Изначально в статье была реализация с ошибкой. Ошибку исправил, статью немного поправил.
Предисловие
Истинное понимание проблем каждой платформы приходит после того, как попробуешь писать под другую платформу / на другом языке. И вот как раз после того, как я познакомился с разработкой под iOS, я задумался над тем, насколько ужасна реализация поворотов экрана в Android. С того момента я думал над решением данной проблемы. Попутно я начал использовать реактивное программирование везде, где только можно и уже даже не представляю как писать приложения по-другому.
И вот я узнал про последнюю недостающую деталь — Data Binding. Как-то эта библиотека прошла мимо меня в свое время, да и все статьи, что я читал (что на русском, что на английском) рассказывали не совсем про то, что мне было необходимо. И вот сейчас я хочу рассказать про реализацию приложения, когда можно будет забыть про повороты экранов вообще, все данные будут сохраняться без нашего прямого вмешательства для каждого активити.
Когда начались проблемы?
По настоящему остро я почувствовал проблему, когда в одном проекте у меня получился экран на 1500 строк xml, по дизайну и ТЗ там было целая куча различных полей, которые становились видимыми при разных условиях. Получилось 15 различных layout’ов, каждый из которых мог быть видимым или нет. Плюс к этому была еще куча различных объектов, значения которых влияют на вьюху. Можете представить уровень проблем в момент поворота экрана.
Возможное решение
Сразу оговорюсь, я против фанатичного следования заветам какого-либо подхода, я пытаюсь делать универсальные и надежные решения, несмотря на то, как это смотрится с точки зрения какого-либо паттерна.
Я назову это реактивным MVVM. Абсолютно любой экран можно представить в виде объекта: TextView — параметр String, видимость объекта или ProgressBar’а — параметр Boolean и т.д… А так же абсолютно любое действие можно представить в виде Observable: нажатие кнопки, ввод текста в EditText и т.п…
Вот тут я советую остановиться и прочитать несколько статей про Data Binding, если еще не знакомы с этой библиотекой, благо, на хабре их полно.
Да начнется магия
Перед тем как начать создавать нашу активити, создадим базовые классы для активити и ViewModel’ли, где и будет происходить вся магия.
Update!
После общения в комментариях, осознал свою ошибку. Суть в том, что в моей первой реализации ничего не сериализуется, но все работает при поворотах экрана, да даже при сворачивании, разворачивании экрана. В комментариях ниже обязательно почитайте почему так происходит. Ну а я исправлю код и поправлю комментарии к нему.
Для начала, напишем базовую ViewModel:
Я уже говорил, что все что угодно можно представить как Observable? И библиотека RxBinding отлично это делает, но вот беда, мы работает не напрямую с объектами, типа EditText, а с параметрами типа ObservableField. Что бы радоваться жизни и дальше, нам необходимо написать функцию, которая будет делать из ObservableField необходимый нам Observable RxJava2:
Тут все просто, передаем на вход ObservableField и получаем Observable RxJava2. Именно для этого мы наследуем базовый класс от BaseObservable. Добавим этот метод в наш базовый класс.
Теперь напишем базовый класс для активити:
Я постарался подробно прокомментировать код, но заострю внимание на нескольких вещах.
Активити, при повороте экрана всегда уничтожается. Тогда, при восстановлении снова вызывается метод onCreate. Вот как раз в методе onCreate нам и нужно восстанавливать данные, предварительно проверив, сохраняли ли мы какие-либо данные. Сохранение данных происходит в методе onSaveInstanceState.
При повороте экрана нас интересует порядок вызовов методов, а он такой (то, что интересует нас):
1) onDestroy
2) onSaveInstanceState
Что бы не сохранять уже не нужные данные мы добавили проверку:
Дело в том, что метод isFinishing вернет true только если мы явно вызвали метод finish() в активити, либо же ОС сама уничтожила активити из-за нехватки памяти. В этих случаях нам нет необходимости сохранять данные.
Реализация приложения
Представим условную задачу: нам необходимо сделать экран, где будет 1 EditText, 1 TextView и 1 кнопка. Кнопка не должна быть кликабельной до тех пор, пока пользователь не введет в EditText цифру 7. Сама же кнопка будет считать количество нажатий на нее, отображая их через TextView.
Update!
Пишем нашу ViewModel:
Update
Вот тут и и были самые большие проблемы. Все работало и при старой реализации, ровно до того момента, пока в настройках разработчика не включить параметр «Don’t keep activities».
Что бы все работало как надо, необходимо реализовывать интерфейс Parcelable для ViewModel. По поводу реализации ничего писать не буду, только уточню еще 1 момент:
Данные-то мы возвращаем, а вот Observable мы теряем. Поэтому пришлось выводить в отдельный метод и вызывать его во всех конструкторах. Это очень быстрое решение проблемы, не было времени подумать лучше, нужно было было указать на ошибку. Если у кого-то есть идеи как реализовать это лучше, пожалуйста, поделитесь.
Теперь напишем для этой модели view:
Ну и теперь, мы пишем нашу активити:
Запускаем приложение. Кнопка не кликабельна, счетчик показывает 0. Вводим цифру 7, вертим телефон как хотим, через 2 секунды, в любом случае кнопка становится активной, тыкаем на кнопку и счетчик растет. Стираем цифру, вертим телефоном снова — кнопка все равно через 2 секунды будет не кликабельна, а счетчик не сбросится.
Все, мы получили реализацию безболезненного поворота экрана без потери данных. При этом будут сохранены не только ObservableField и тому подобные, но так же и объекты, массивы и простые параметры, типа int.
Источник
Настройки и состояние приложения
Сохранение состояния приложения
В одной из предыдущих тем был рассмотрен жизненный цикл Activity в приложении на Android, где после создания Activity вызывался метод onRestoreInstanceState , который восстанавливал ее состояние, а перед завершением работы вызывался метод onSaveInstanceState , который сохранял состояние Actiity. Оба этих метода в качестве параметра принимают объект Bundle , который как раз и хранит состояние activity:
В какой ситуации могут быть уместно использование подобных методов? Банальная ситуация — переворот экрана и переход от портретной ориентации к альбомной и наоборот. Если, к примеру, графический интерфейс содержит текстовое поле для вывода TextView, и мы программно изменяем его текст, то после изменения ориентации экрана его текст может исчезнуть. Или если у нас глобальные переменные, то при изменении ориентации экрана их значения могут быть сброшены до значений по умолчанию.
Чтобы точнее понять проблему, с которой мы можем столкнуться, рассмотрим пример. Изменим файл activity_main следующим образом:
Здесь определено поле EditText, в которое вводим имя. И также определена кнопка для его сохранения.
Далее для вывода сохраненного имени предназначено поле TextView, а для получения сохраненного имени — вторая кнопка.
Теперь изменим класс MainActivity :
Для хранения имени в программе определена переменная name. При нажатии на первую кнопку сохраняем текст из EditText в переменную name, а при нажатии на вторую кнопку — обратно получаем текст из переменной name в поле TextView.
Запустим приложение введем какое-нибудь имя, сохраним и получим его в TextView:
Но если мы перейдем к альбомному режиму, то TextView окажется пустым, несмотря на то, что в него вроде бы уже получили нужное значение:
И даже если мы попробуем заново получить значение из переменной name, то мы увидим, что она обнулилась:
Чтобы избежать подобных ситуаций как раз и следует сохранять и восстанавливать состояние activity. Для этого изменим код MainActivity:
В методе onSaveInstanceState() сохраняем состояние. Для этого вызываем у параметра Bundle метод putString(key, value) , первый параметр которого — ключ, а второй — значение сохраняемых данных. В данном случае мы сохраняем строку, поэтому вызываем метод putString() . Для сохранения объектов других типов данных мы можем вызвать соответствующий метод:
put() : универсальный метод, который добавляет значение типа Object. Соответственно поле получения данное значение необходимо преобразовать к нужному типу
putString() : добавляет объект типа String
putInt() : добавляет значение типа int
putByte() : добавляет значение типа byte
putChar() : добавляет значение типа char
putShort() : добавляет значение типа short
putLong() : добавляет значение типа long
putFloat() : добавляет значение типа float
putDouble() : добавляет значение типа double
putBoolean() : добавляет значение типа boolean
putCharArray() : добавляет массив объектов char
putIntArray() : добавляет массив объектов int
putFloatArray() : добавляет массив объектов float
putSerializable() : добавляет объект интерфейса Serializable
putParcelable() : добавляет объект Parcelable
Каждый такой метод также в качестве первого параметра принимает ключа, а в качестве второго — значение.
В методе onRestoreInstanceState происходит обратный процесс — с помощью метода getString(key) по ключу получаем из сохраненного состояния строку по ключу. Соответственно для получения данных других типов мы можем использовать аналогичные методы:
get() : универсальный метод, который возвращает значение типа Object. Соответственно поле получения данное значение необходимо преобразовать к нужному типу
getString() : возвращает объект типа String
getInt() : возвращает значение типа int
getByte() : возвращает значение типа byte
getChar() : возвращает значение типа char
getShort() : возвращает значение типа short
getLong() : возвращает значение типа long
getFloat() : возвращает значение типа float
getDouble() : возвращает значение типа double
getBoolean() : возвращает значение типа boolean
getCharArray() : возвращает массив объектов char
getIntArray() : возвращает массив объектов int
getFloatArray() : возвращает массив объектов float
getSerializable() : возвращает объект интерфейса Serializable
getParcelable() : возвращает объект Parcelable
Для примера рассмотрим сохранение-получение более сложных данных. Например, объектов определенного класса. Пусть у нас есть класс User :
Класс User реализует интерфейс Serializable , поэтому мы можем сохранить его объекты с помощью метода putSerializable() , а получить с помощью метода getSerializable() .
Пусть у нас будет следующий интерфейс в activity_main.xml :
Здесь определены два поля ввода для имени и возраста соответственно.
В классе MainActivity пропишем логику сохранения и получения данных:
Здесь также сохраняем данные в переменную User, которая предварительно инициализированна некоторыми данными по умолчанию. А при нажатии на кнопку получения получем данные из переменной и передаем их для вывода в текстовое поле.
Источник