Similar presentations:
Классы Looper, Handler и Handler Thread
1.
Классы Looper, Handler иHandlerThread
Подготовил:
Руденок Евгений
ПИ-21
2.
Подготовка RecyclerView к выводу изображенийНачнем с создания нового файла макета для элементов фотогалереи в файле
list_item_gallery.xml. Макет будет состоять из единственного виджета ImageView
Макет элементов галереи (res/layout/list_item_gallery.xml)
Эти виджеты ImageView
находятся
под
управлением
экземпляра GridLayoutManager
компонента RecyclerView, что
означает, что их ширина будет
величиной переменной. При этом
высота
будет
оставаться
фиксированной.
3.
Чтобы наиболее эффективно использовать пространство виджета ImageView, следуетзадать его свойству scaleType значение centerCrop.
Изменить код класса PhotoHolder, чтобы вместо TextView он содержал ImageView.
Заменить bindTitle функцией, назначающей объект Drawable виджету ImageView.
Обновление PhotoHolder (PhotoGalleryFragment.kt)
Ранее конструктор
PhotoHolder предполагал, что
ему будет передаваться просто
объект TextView. Новая версия
рассчитывает получить
ImageView.
4.
Изменить функцию onCreateViewHolder() в PhotoAdapter, чтобы оназаполняла файл list_item_gallery.xml и передавала его конструктору PhotoHolder.
Добавить ключевое слово inner, чтобы у PhotoAdapter появился прямой доступ
к свойству layoutInflater родительской activity.
Обновление функции onCreateViewHolder() класса PhotoAdapter (PhotoGalleryFragment.kt)
5.
Также понадобится временное изображение (плейсхолдер) для каждого виджета ImageView,которое будет отображаться до завершения загрузки изображения. Необходимо найти файл
bill_up_close.png в файле решений (www.bignerdranch.com/ solutions/AndroidProgramming4e.zip) и
поместитеь его в каталог res/drawable. Внести изменения в код функции onBindViewHolder()
класса PhotoAdapter, чтобы временное изображение назначалось объектом Drawable виджета
ImageView.
Назначение временного изображения (PhotoGalleryFragment.kt)
Обратите внимание, мы
передаем пустой объект
ColorDrawable, если
ContextCompat.getDrawable(...)
возвращает null
6.
Запустив приложение PhotoGallery,увидим набор увеличенных
фотографий Билла
7.
Подготовка к загрузке через URLНаш интерфейс AP-Retrofit пока не поддерживает загрузку изображения. Это нужно исправить
прямо сейчас. Добавляем новую функцию, которая принимает на вход строку с URL-адресом и
возвращает исполняемый объект вызова (retrofit2. Call), результатом которого будет
okhttp3.ResponseBody.
Изменение кода FlickrApi (api/FlickrApi.kt)
Новая функция API выглядит слегка иначе. На вход ей подается
URL-адрес, который используется для определения того, откуда
загружать данные. Использование беспараметрической
аннотации @GET в сочетании с аннотацией первого параметра в
fetchUrlBytes(...) с @Url приводит к тому, что Retrofit полностью
переопределяет базовый URL. Вместо этого Retrofit будет
использовать URL, переданный в функцию fetchUrlBytes(...).
8.
Добавляем функцию в FlickrFetchr, чтобы загружать данные по заданному URLадресу и декодировать их в изображение Bitmap.Добавление загрузки изображения во FlickrFetchr (FlickrFetchr.kt)
9.
Здесь используется функция Call.execute(), которая синхронно выполняет веб-запрос. Работа с сетью в основномпотоке не разрешена. Аннотация @WorkerThread указывает, что эта функция должна вызываться только в фоновом
потоке.
Однако аннотация только дает указания, но не создает фоновый поток и не перемещает туда задачу. Это уже ваша
работа. (В аннотации @WorkerThread появится ошибка Lint, если вызываемая функция будет аннотирована с
помощью @MainThread или @UiThread). В результате вы будете вызывать функцию fetchPhoto(String) из создаваемого
фонового потока.
Объект java.io.InputStream извлекается из тела ответа с помощью функции ResponseBody.byteStream(). Получив
поток байтов, мы передаем его функции BitmapFactory.decodeStream(InputStream), которая создаст Bitmap из данных в
потоке.
Ответный и байтовый потоки должны быть закрытыми. Так как InputStream реализует атрибут Closeable, то
стандартная функция библиотеки Kotlin use(...) выполнит чистку при возвращении BitmapFactory.decodeStream(...).
Наконец возвращаем растровое изображение, которое построила наша BitmapFactory. Благодаря этому интерфейс
API и репозиторий теперь готовы к загрузке изображений.
10.
Множественные загрузкиСейчас работа с сетью у приложения PhotoGalletry организована так:
PhotoGalleryViewModel вызывает функцию FlickrFetchr().fetchPhotos() для
загрузки JSON-данных с сайта Flickr. FlickrFetchr сразу же возвращает пустой
объект LiveData<List> и выполняет асинхронный запрос Retrofit для получения
данных с Flickr. Этот сетевой запрос выполняется в фоновом потоке.
По завершении загрузки данных FlickrFetchr разбирает данные JSON в списке
GalleryItem и публикует список возвращаемому объекту LiveData. У каждого
элемента GalleryItem есть URL, с которого загружается миниатюра фотографии.
Следующим шагом должна стать загрузка этих миниатюр. По умолчанию
FlickrFetchr запрашивает лишь 100 URL-адресов, поэтому в списке GalleryItem
будет не более 100 URL-адресов. Тогда, как вариант, можно загружать
изображения одно за другим, пока их не станет 100, затем уведомлять ViewModel,
и в конечном счете выводить изображения в RecyclerView.
11.
Единовременная загрузка всех миниатюр создает две проблемы:Загрузка займет много времени, а пользовательский
интерфейс не будет обновляться до момента ее
завершения.
Хранение полного набора изображений требует ресурсов.
Сотня миниатюр легко уместится в памяти. Но что, если их
будет 1000? Со временем свободная память будет исчерпана.
С учетом этих проблем реальные приложения часто загружают изображения только тогда,
когда они должны выводиться на экране. Загрузка по мере надобности предъявляет
дополнительные требования к RecyclerView и его адаптеру. Адаптер инициирует загрузку
изображения как часть реализации onBindViewHolder(...).
Ну и как это сделать? Вы можете выдавать отдельный асинхронный запрос на Retrofit для
каждой загрузки изображения. Однако вам придется отслеживать все объекты Call и
управлять их жизненным циклом по отношению к каждому контейнеру представления и
самому фрагменту.
Вместо этого мы создадим фоновый поток. Этот поток будет получать и поочередно
обрабатывать запросы на загрузку, а также предоставлять результирующее изображение для
каждого отдельного запроса по мере завершения загрузки. Поскольку все запросы
управляются фоновым потоком, без проблем можно удалять запросы или даже останавливать
поток, вместо того чтобы управлять целой кучей отдельных запросов.
12.
Создание фонового потокаСоздайте новый класс ThumbnailDownloader, расширяющий HandlerThread. Определите
для него конструктор, заглушку реализации функции queueThumbnail() и переопределение
функции quit(), которая говорит о завершении потока.
Исходная версия кода потока (ThumbnailDownloader.kt)
Классу передается один обобщенный аргумент <т> .
Пользователю ThumbnailDownloader понадобится объект
для идентификации каждой загрузки и определения
элемента пользовательского интерфейса, который должен
обновляться после завершения загрузки. Вместо того чтобы
ограничивать пользователя одним конкретным типом
объекта, мы используем обобщенный параметр и сделаем
реализацию более гибкой.
Функция queueThumbnail() ожидает получить объект типа T,
выполняющий функции идентификатора загрузки, и String с
URL-адресом для загрузки. Эта функция будет вызываться
PhotoAdapter в его реализации onBindViewHolder(...).
13.
Передача информации о жизненном цикле в потокТак как единственной целью ThumbnailDownloader является загрузка и передача
изображений в PhotoGalleryFragment, жизненный цикл потока привязывается к
предполагаемой пользователем продолжительности жизни фрагмента. Иными словами,
поток запускается, когда пользователь впервые открывает экран. Поток
завершается, когда пользователь заканчивает работу с экраном (например, нажимая
кнопку «Назад» или завершая приложение). Не нужно уничтожать, а затем
воссоздавать поток, когда пользователь поворачивает устройство, так как экземпляр
потока должен сохраняться после изменения конфигурации.
Жизненный цикл ViewModel соответствует циклу жизни фрагмента. Однако
управление потоком в PhotoGalleryViewModel сделает реализацию более сложной, чем
необходимо, а также приведет к проблемам с утечкой представлений.
Необходимо привязать экземпляр ThumbnailDownloader непосредственно к вашему
PhotoGalleryFragment. Сначала сохраните PhotoGalleryFragment таким образом, чтобы
жизнь экземпляра фрагмента соответствовала предполагаемой пользователем жизни
фрагмента.
14.
Сохранение PhotoGalleryFragment (PhotoGalleryFragment.kt)Теперь, когда фрагмент сохранен, нужно запускать
поток
при
вызове
функции
PhotoGalleryFragment.onCreate(...) и завершать его при
вызове функции PhotoGalleryFragment.onDestroy().
Этого можно добиться, добавив код непосредственно в
функции жизненного цикла PhotoGalleryFragment, но это
избыточно усложнит класс. Вместо этого абстрагируйте код
в
ThumbnailDownloader,
сделав
жизненный
цикл
ThumbnailDownloader публичным.
Компонент, наблюдающий за жизненным циклом — наблюдатель, — наблюдает за жизненным циклом владельца
жизненного цикла. Activity и фрагмент являются владельцами жизненного цикла — у них есть жизненный цикл и
реализуется интерфейс LifecycleOwner.
15.
Добавьте в ThumbnailDownloader реализацию интерфейса LifecycleObserver и наблюдение зафункциями onCreate(...) и onDestroy владельца жизненного цикла. Пусть загрузчик эскизов
запускает сам себя при вызове onCreate(...) и останавливается при вызове функции onDestroy().
Связь ThumbnailDownloader с жизненным циклом (ThumbnailDownloader.kt)
Реализация LifecycleObserver означает, что вы можете подписать
ThumbnailDownloader на получение обратных вызовов жизненного
цикла от любого владельца LifecycleOwner. Для этого используется
аннотация @OnLifecycleEvent(Lifecycle. Event), позволяющая
ассоциировать функцию в вашем классе с обратным вызовом
жизненного цикла. Lifecycle.Event.ON_CREATE регистрирует вызов
функции ThumbnailDownloader.setup() при вызове функции
LifecycleOwner.onCreate(...). Lifecycle.Event.ON_DESTROY
регистрирует вызов функции ThumbnailDownloader. tearDown()при
вызове функции LifecycleOwner.onDestroy().
Список доступных констант Lifecycle.Event можно посмотреть на
странице API
(developer.android.com/reference/android/arch/lifecycle/Lifecycle.Event).
16.
Далее нужно создать экземпляр ThumbnailDownloader и подписать его на получение обратных вызововжизненного цикла из PhotoGalleryFragment. Выполните это в файле PhotoGalleryFragment.kt.
Создание ThumbnailDownloader (PhotoGalleryFragment.kt)
17.
Вы можете указать любой тип для общего аргумента ThumbnailDownloader. В нашем случаеудобно использовать PhotoHolder, так как он также является местом, куда в конечном итоге будут
направлены загруженные изображения.
Так как Fragment реализует LifecycleOwner, у него есть свойство lifecycle. Это свойство мы
будем использовать для добавления наблюдателя в жизненный цикл фрагмента. Вызов
lifecycle.addObserver(thumbnailDownloader) подписывает экземпляр загрузчика эскизов на получение
обратных вызовов жизненного цикла фрагмента. Теперь при вызове функции PhotoGalleryFragment.
onCreate(...) вызывается функция ThumbnailDownloader.setup(). При вызове функции
PhotoGalleryFragment.onDestroy() вызывается функция ThumbnailDownloader.tearDown().
Функция lifecycle.removeObserver(thumbnailDownloader) во Fragment.onDestroy() вызывается
для снятия thumbnailDownloader с роли наблюдателя за жизненным циклом при уничтожении
экземпляра фрагмента. Вообще, можно было бы оставить эту работу сборщику мусора. Однако мы
предпочитаем управлять ресурсами точно, а не ждать, когда произойдет уборка мусора. Это
помогает легче отслеживать ошибки.
Запустите ваше приложение. Перед вами должна быть стена фотографий Билла. Проверьте
записи в своем журнале на наличие сообщения ThumbnailDownloader: Starting background thread,
чтобы убедиться, что функция ThumbnailDownloader. setup() была выполнена один раз. Нажмите
кнопку «Назад», чтобы выйти из приложения и закончить (и уничтожить) PhotoGalleryActivity (и ее
PhotoGalleryFragment). Проверьте отчеты журнала еще раз: сообщение ThumbnailDownloader:
Destroying background thread говорит о том, что функция ThumbnailDownloader.tearDown() была
выполнена один раз.
18.
Запуск и остановка HandlerThreadТеперь, когда ThumbnailDownloader наблюдает за жизненным циклом PhotoGalleryFragment,
добавьте в ThumbnailDownloader запуск при вызове функции PhotoGalleryFragment.onCreate(...)
и остановку при вызове функции PhotoGalleryFragment.onDestroy().
Запуск и остановка потока ThumbnailDownloader (ThumbnailDownloader.kt)
1. обратите внимание, что вы получаете доступ к looper
после вызова функции start() на ThumbnailDownloader.
Это способ убедиться, что поток готов к выполнению,
чтобы избежать потенциального состояния гонки. До
первого обращения к looper нет никаких гарантий, что
была вызвана функция onLooperPrepared(), так что
существует
вероятность
того,
что
вызовы
queueThumbnail(...) будут неудачными из-за нулевого
обработчика.
2. нужно вызывать функцию quit() для завершения потока.
Если вы не выйдете из HandlerThreads, он будет жить
вечно.
19.
В функции PhotoAdapter.onBindViewHolder(...) вызываем функцию queueThumbnail()потока и передаем целевую папку PhotoHolder, где в конечном итоге будет размещено
изображение и URL-адрес GalleryItem для скачивания.
Подключение ThumbnailDownloader (PhotoGalleryFragment.kt)
Еще раз запустите PhotoGallery
и проверьте содержимое панели
Logcat. При прокрутке RecyclerView
вы должны увидеть на панели Logcat
строки, сигнализирующие о том, что
ThumbnailDownloader
получает
каждый ваш запрос на загрузку.
Стена фотографий Билла пока никуда
не делась.
Теперь, когда у вас запущен
HandlerThread, следующим шагом
будет общение между основным
потоком вашего приложения и вашим
новым фоновым потоком
20.
Сообщения и обработчики сообщенийПоток, работающий с
использованием очереди
сообщений, называется циклом
сообщений (message loop); он
снова и снова проверяет новые
сообщения, которые могли
появиться в очереди.
Цикл сообщений состоит из
потока и объекта Looper,
управляющего очередью
сообщений потока.
Цикл сообщений
21.
Главный поток представляет собой цикл сообщений, и у негоесть управляющий объект, который извлекает сообщения из
очереди сообщений и выполняет задачу, описанную в
сообщении.
Мы создадим фоновый поток,
который тоже использует цикл
сообщений.
При
этом
будет
использоваться класс HandlerThread,
который
предоставляет
готовый
объект Looper.
Основной и фоновый потоки
будут взаимодействовать друг с
другом, помещая сообщения в очередь
друг друга с помощью обработчиков.
Основной поток — это HandlerThread
Взаимодействие с обработчиками
22.
Структура сообщенияНачнем с сообщений. Сообщения, которые Флэш кладет в почтовый ящик (свой собственный
или принадлежащий другому продавцу), содержат не ободряющие записки типа «Ты бегаешь
очень быстро», а описания задач, которые необходимо выполнить.
Сообщение является экземпляром класса Message и состоит из нескольких полей.
Для нашей реализации важны три поля:
Приемником сообщения Message является экземпляр Handler. Когда вы создаете сообщение, оно
автоматически присоединяется к Handler. А когда ваше сообщение будет готово к обработке, именно
Handler становится объектом, отвечающим за эту обработку.
23.
Структура обработчикаИтак, для выполнения реальной работы с
сообщениями необходимо иметь экземпляр Handler.
Объект Handler — не просто приемник для обработки
сообщений; он также предоставляет интерфейс для
создания и отправки сообщений.
• Сообщения Message отправляются и потребляются
объектом Looper, потому что Looper является
владельцем почтового ящика объектов Message.
Соответственно, Handler всегда содержит ссылку на
своего коллегу Looper.
• Handler всегда присоединяется ровно к одному объекту
Looper, а Message присоединяется ровно к одному
объекту Handler, называемому его приемником. Объект
Looper обслуживает целую очередь сообщений
Message. Многие сообщения Message могут содержать
ссылку на один целевой объект Handler .
Looper, Handler, HandlerThread
Messages
24.
К одному объекту Looper могут бытьприсоединены несколько объектов Handler. Это
означает, что сообщения Message объекта Handler
могут сосуществовать с сообщениями другого
объекта Handler.
Несколько объектов Handler, один объект Looper
25.
Использование обработчиковСхема отношений между объектами изображена на рисунке ниже.
Создание сообщения и его отправка
26.
В нашем случае реализация handleMessage(...) будет использовать FlickrFetchrдля загрузки байтов по URL-адресу и их преобразования в растровое
изображение.
Добавление констант и переменных (ThumbnailDownloader.kt)
Значение
MESSAGE_DOWNLOAD
будет
использоваться для идентификации сообщений как
запросов на загрузку.
В переменной requestHandler будет храниться
ссылка на объект Handler, отвечающий за постановку в
очередь запросов на загрузку в фоновом потоке
ThumbnailDownloader.
Переменная
requestMap
содержит
ConcurrentHashMap
—
разновидность
HashMap,
безопасную по отношению к потокам.
В свойстве flickrFetchr хранится ссылка на
экземпляр FlickrFetchr. Таким образом, весь код
установки Retrofit будет выполняться только один раз в
течение жизни потока.
27.
Затем добавьте в queueThumbnail(...) код обновления переменной requestMap и постановкинового сообщения в очередь сообщений фонового потока.
Отправка сообщения (ThumbnailDownloader.kt)
Сообщение
берется
непосредственно
из
переменной requestHandler, в результате чего поле target
нового объекта Message немедленно заполняется
переменной requestHandler. Это означает, что переменная
requestHandler будет отвечать за обработку сообщения
при его извлечении из очереди сообщений. Поле what
сообщения
заполняется
значением
MESSAGE_DOWNLOAD. В поле obj заносится значение
T target (PhotoHolder в данном случае), переданное
функцией queueThumbnail(...).
Новое сообщение представляет запрос на загрузку
заданного T target (PhotoHolder из RecyclerView).
В само сообщение URL-адрес не входит. Вместо
этого переменная requestMap обновляется связью между
идентификатором запроса (PhotoHolder) и URL-адресом
запроса. Позднее мы получим URL-адрес из переменной
requestMap, чтобы гарантировать, что для заданного
экземпляра PhotoHolder всегда загружается последний из
запрашивавшихся URL-адресов.
28.
Наконец, инициализируйте переменную requestHandler и определите, что будет делать объект Handler,когда сообщения извлекаются из очереди и передаются ему.
Обработка сообщения (ThumbnailDownloader.kt)
До настоящего момента мы
ограничивались использованием
обработчиков и сообщений в одном
потоке — помещением сообщений в
собственный почтовый ящик
ThumbnailDownloader. В следующем
разделе вы увидите, как
ThumbnailDownloader использует
Handler для отправки запросов главному
потоку.
29.
Передача обработчиковИтак, вы знаете, как спланировать выполнение работы в фоновом потоке из главного
потока с использованием значения переменной requestHandler объекта
ThumbnailDownloader.
30.
Аналогичным образом можно планировать операции в главном потоке из фонового потокас использованием экземпляра Handler, присоединенного к главному потоку
31.
В файле ThumbnailDownloader.kt добавьте упоминавшуюся выше переменную responseHandler дляхранения экземпляра Handler, переданного из главного потока. Затем замените конструктор другим,
который получает Handler и задает переменную, и добавьте интерфейс слушателя для передачи ответов
(загруженных изображений) запрашивающей стороне (главному потоку).
Добавление responseHandler (ThumbnailDownloader.kt)
Свойство типа функции, определенное в новом конструкторе, будет
рано или поздно использовано, когда полностью загруженное
изображение появится в интерфейсе. Использование слушателя
передает ответственность за обработку загруженного изображения
другому классу, а не ThumbnailDownloader (в данном случае
PhotoGalleryFragment). Тем самым задача загрузки отделяется от
задачи обновления пользовательского интерфейса (связывания
изображений с ImageView), чтобы класс ThumbnailDownloader при
необходимости мог использоваться для загрузки данных других
разновидностей объектов View.
32.
Затем измените класс PhotoGalleryFragment так, чтобы он передавал классуThumbnailDownloader объект Handler, присоединенный к главному потоку. Также назначьте
анонимную функцию для обработки загруженного изображения после завершения загрузки.
Подключение к обработчику ответа (PhotoGalleryFragment.kt)
33.
Теперь ThumbnailDownloader имеет доступ к экземпляру Handler, связанному сэкземпляром Looper главного потока, через поле responseHandler. В нем также есть реализация
типа функции для реализации интерфейса при возврате Bitmap. В частности, функция,
переданная в функцию высшего порядка onThumbnailDownloaded, устанавливает Drawable
запрошенного PhotoHolder на только что загруженный Bitmap.
Аналогичным образом можно отправить главному потоку нестандартный объект Messsage,
запрашивающий добавление изображения в пользовательский интерфейс, по аналогии с тем,
как мы ставили в очередь запрос к фоновому потоку на загрузку изображения. Для этого
потребуется другой подкласс Handler с переопределением функции handleMessage(...). Однако
вместо этого мы используем другую удобную функцию Handler — post (Runnable).
34.
Если у Message задано поле callback, то вместо передачи приемнику Handler приизвлечении из очереди сообщений выполняется объект Runnable из поля callback. В
функции ThumbnailDownloader.handleRequest() запишите Runnable в очередь основного
потока через responseHandler.
Загрузка и вывод изображений (ThumbnailDownloader.kt)
А поскольку responseHandler связывается с Looper главного
потока, весь код функции run() в Runnable будет выполнен в
главном потоке.
Что делает этот код? Сначала он проверяет requestMap. К тому
времени, когда ThumbnailDownloader завершит загрузку Bitmap,
может оказаться, что виджет RecyclerView уже переработал
ImageView и запросил для него изображение с другого URLадреса. Эта проверка гарантирует, что каждый объект
PhotoHolder получит правильное изображение, даже если за
прошедшее время был сделан другой запрос.
Затем
проверяется
hasQuit.
Если
выполнение
ThumbnailDownloader уже завершилось, выполнение какихлибо обратных вызовов небезопасно.
Наконец, мы удаляем из requestMap связь «PhotoHolder —URL»
и назначаем изображение для PhotoHolder.
35.
Прослушивание жизненного цикла представленияПеред запуском приложения PhotoGallery и просмотром снимков нужно обратить
внимание на одну опасность.
Если пользователь поворачивает экран, ThumbnailDownloader может повиснуть на
недействительном PhotoHolder. Ваше приложение может аварийно завершить работу,
если программа ThumbnailDownloader попытается отправить растровое изображение
PhotoHolder, которое было частью представления, уничтоженного во время вращения.
Исправьте утечку, удалите все запросы из очереди при уничтожении
представления фрагмента. Для этого ThumbnailDownloader должен иметь
информацию о жизненном цикле представления фрагмента.
Во-первых, выполните рефакторинг кода наблюдателя за жизненным циклом
фрагмента, чтобы освободить место для реализации второго наблюдателя за
жизненным циклом.
36.
Рефакторинг наблюдателя за жизненным циклом фрагмента (ThumbnailDownloader.kt)37.
Определите нового наблюдателя, который в конце концов будет слушать обратныевызовы жизненного цикла с точки зрения фрагмента
Добавление наблюдателя жизненного цикла представления (ThumbnailDownloader.kt)
При наблюдении за жизненным циклом
представления фрагмента Lifecycle.
Event.ON_DestROY сопоставляет его с
функцией
Fragment.onDestroyView().
Чтобы
узнать,
как
константы
Lifecycle.Event
сопоставляются
с
обратными вызовами при наблюдении
жизненного
цикла
представления
фрагмента,
загляните
в
раздел
getViewLifecycleOwner для Fragment по
адресу developer.android.com/reference/
kotlin/ androidx/fragment/app/Fragment.
38.
Теперь научимся PhotoGalleryFragment регистрировать восстановленного наблюдателя зафрагментом. Также зарегистрируйте вновь добавленного наблюдателя жизненного цикла, чтобы
отслеживать жизненный цикл представления фрагмента.
Регистрация наблюдателя жизненного цикла
представления (PhotoGalleryFragment.kt)
Жизненный цикл представления можно безопасно
наблюдать в функции Fragment. onCreateView(...), до
тех пор пока в конце функции onCreateView(...) не
будет возвращено ненулевое представление.
39.
Наконец, отменим регистрацию thumbnailDownloader в качестве наблюдателя за жизненным цикломфрагмента представления в функции Fragment.onDestroyView(). Обновим существующий код, который
отменяет регистрацию thumbnailDownloader в качестве наблюдателя за жизненным циклом фрагментов.
Отказ от регистрации наблюдателя жизненного цикла
представления (PhotoGalleryFragment.kt)
40.
Запустите приложение PhotoGallery. Покрутите экран, чтобы увидетьдинамическую загрузку изображений.
PhotoGallery выполняет свою цель — выводит
изображения с Flickr.
41.
Сохраненные фрагментыПо умолчанию свойство retainInstance фрагмента имеет значение
false. Это означает, что он не сохраняется, а уничтожается и
воссоздается при повороте вместе с activity, на которой он расположен.
Вызов функции setRetainInstance(true) сохраняет фрагмент. При
сохранении фрагмента он не удаляется. Вместо этого он сохраняется и
передается новой activity в целости и сохранности.
При сохранении фрагмента можно рассчитывать на то, что все
его экземпляры будут иметь одинаковые значения и всегда будут на
месте.
42.
Поворот и сохранение фрагментовСохраненные фрагменты работают за счет
того, что представление фрагмента можно
уничтожить и воссоздать заново, не
уничтожая сам фрагмент.
При смене конфигурации FragmentManager
сначала уничтожает представления фрагментов
в списке. Представления фрагментов всегда
уничтожаются и воссоздаются при изменении
конфигурации по тем же причинам, что и
представления activity: если у вас есть новая
конфигурация, то вам могут понадобиться
новые ресурсы. Если они есть, вы
восстанавливаете представление с нуля.
Далее FragmentManager проверяет свойство
remainInstance каждого фрагмента. Если оно
ложное, что по умолчанию так и есть, то
FragmentManager
уничтожает
экземпляр
фрагмента. Фрагмент и его представление
будут воссозданы новым FragmentManager
новой activity «с другой стороны»
43.
С другой стороны, если retainInstance имеет значение true, то представлениефрагмента уничтожается, а сам фрагмент — нет. Когда создается новая
activity, новый FragmentManager находит сохраненный фрагмент и воссоздает
его.
44.
Сохраненный фрагмент не уничтожается, а отделяется от завершаемой activity. Это переводитфрагмент в сохраненное состояние. Фрагмент все еще существует, но он не размещен внутри
activity.
Фрагмент находится в сохраненном состоянии в течение
очень короткого промежутка времени — между
отсоединением от старой activity и повторным
присоединением к новой activity, которая создается сразу
же.
Жизненный цикл фрагмента
45.
Когда меняется конфигурация устройства, вы получаете наиболее подходящие ресурсы, создаваясовершенно новое представление, и у вас есть простой способ сохранить данные и объекты.
Но почему мы тогда не сохраняем каждый фрагмент или почему фрагменты не сохраняются по
умолчанию? В целом не рекомендуется использовать этот механизм по нескольким причинам.
Первая причина заключается в том,
что сохраняемые фрагменты
сложнее, чем не сохраненные. Когда
с ними что-то не так, то докопаться
до сути того, что пошло не так,
сложно.
Третья причина заключается в том, что
класс ViewModel в большинстве случаев
устраняет необходимость сохранения
фрагментов. Используйте ViewModel
вместо сохраненного фрагмента для
сохранения состояния UI при изменении
конфигурации.
Вторая причина заключается в том, что фрагменты,
обрабатывающие поворот с использованием сохраненного
состояния экземпляра, обрабатывают все ситуации
жизненного цикла, но сохраненные фрагменты
обрабатывают только тот случай, когда activity
уничтожается для изменения конфигурации. Если же ваша
activity уничтожена из-за того, что операционной системе
требуется память, то уничтожаются и все сохраненные вами
фрагменты, что может означать потерю некоторых данных.