tgoop.com/dev_easy_notes/103
Last Update:
Вот это по мне так самый интересный пост за всю эту серию.❗Есть одна ошибка которую совершают все при работе с фрагментами. Причем, под все я не сильно утрирую, за всю мою карьеру был только один проект, в котором про эту ошибку не забыли.
Начнем с простого, когда создаем фрагмент мы переопределяем метод onCreateView
. В этом методе можно просто вернуть null. Это самый простой кейс как может быть Fragment без View. В этом случае немного изменяется ЖЦ фрагмента. В частности не вызывается метод onViewCreated
, потому как View не создалась. Может возникнуть вопрос, а зачем это нужно? Для этого окунемся в историю.
Раньше в стародавние времена, не было такой штуки как ViewModel, которая умела переживать смерть Activity. Большие объекты сохраняли либо через статику, которая несет свои сложности, либо через retain фрагмент.
У фрагментов есть такая настройка setRetainInstance
, которая позволяет указать нужно ли пересоздавать фрагмент при смерти Activity. Если выставляем retainInstance = true,
то при пересоздании Activity фрагмент умирать не будет. Это позволяло использовать такие фрагменты, как сейчас, используются ViewModel. Однако есть важное ограничение. Нельзя делать такие фрагменты с View это может привести к утечкам памяти.
У View всегда есть ссылка на Activity (сейчас не рассматриваем кейс с виджетами), и соответственно у Activity всегда есть ссылка на View которую мы видим на экране. Когда умирает Activity, умирает и View которая к ней привязана. Фрагмент как мы помним это в некотором смысле сложная View. Если в retain фрагменте сохранить ссылку на View в поле этого фрагмента, то при перевороте у нас будет жить ссылка на старую Activity которая умерла: Fragment -> View -> Activity
.
Из-за такой возможности легко сделать утечку памяти, а также из-за смешивания View и презентационной логики были сделаны ViewModel. Сейчас настройка retainInstance deprecated и очень не рекомендуется к использованию. retainInstance ушел в прошлое, правда иногда спрашивают на собесах.
Однако это еще не все интересные кейсы где может быть фрагмент без View.
Неочевидно поведение проявляется при транзакции с заменой фрагмента. Начнем с примера, у вас есть FirstFragment в Activity, затем мы делаем транзакцию с заменой фрагмента FirstFragment на SecondFragment. Что при этом происходит с FirstFragment?
Вот тут интересная часть, у него удаляется View, однако сам фрагмент какое-то время продолжает жить. Это сделано для оптимизации. Если нажмем на кнопку назад, то нам нужно будет создать заново фрагмент FirstFragment, а это накладные расходы т.к рефлексия и вот это все. Теперь вдумайтесь, обычный фрагмент, может в некоторых кейсах вести себя очень похоже на retain фрагмент. Да разумеется, он уничтожится при перевороте и тогда уже нет проблем. Однако не факт что этот переворот случится. Это именно тот момент когда можно подорваться и вот каким образом.
Самая распространённая ошибка это сохранение Adapter во фрагменте. Когда вы используете RecycleView вы обязаны создать Adapter чтобы с ним работать. Часто этот Adapter сохраняют в поле Fragment, потому как пересоздавать его каждый раз когда обновляются данные такая себе затея и это правильно. Однако в таком случае важно в onDestroyView
зачищать ссылку на этот Adapter у RecyclerView:
recycler.adapter = null
Интересный факт о котором не пишет Гугл – Adapter держит ссылку на RecyclerView. И по факту мы можем оказаться в ситуации когда у нас фрагмент, который ведет себя как retain фрагмент, да еще и с ссылкой на View. Он конечно удалится со временем, однако не понятно когда, да и удалится ли вообще, до того момента, как умрет Activity.
Поэтому просто возьмите за правило – не сохраняйте ссылки на View в поля фрагментов, или очищайте их в onDestroyView
, что делает ViewBindingPropertyDelegate. Помимо это затирайте ссылку на Adapter у RecyclerView.
BY Dev Easy Notes
Share with your friend now:
tgoop.com/dev_easy_notes/103