Содержание статьи
Грабли Activity
Большинство туториалов, демонстрирующих фишки Android-разработки, начинаются одинаково: неопытным разработчикам предлагают накидать все визуальные элементы прямо в XML-разметку главного Activity. Выглядит это примерно так:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
Такая проектировка входит в привычку, и проект заполняется новыми Activity со все более сложной разметкой. Как итог — даже минимально полезное приложение обрастает стеком Activity, сжирающим всю оперативную память, а в разработчика летят камни и двойки в Google Play.
Сегодня я хочу поделиться с тобой широко известным «секретом»: Activity совсем не предназначены для массового использования. Наоборот, в приложении это штучный инструмент, который идет в ход только в крайних случаях. Повсеместная генерация новых Activity создает серьезные проблемы, которые делают работу приложения непредсказуемой. И даже если на твоем устройстве все стабильно, в мире немыслимое количество Android-устройств, на большинстве из которых твое приложение будет падать.
А все потому, что ОС Android совершенно не обещает держать твои Activity живыми. Как ты помнишь, эти компоненты существуют независимо друг от друга, обладая особым жизненным циклом. Если Activity переходит в состояние onPause
, а происходит это довольно часто, он становится котейкой Шредингера: нельзя заранее знать, будет он жив или нет.
Используя дополнительные Activity для вывода меню и других мелочей, ты подвергаешь опасности все логические связи внутри приложения. Activity собираются в стек, и ОС может начать выгружать их по одному или группой. Когда пользователь захочет вернуться в предыдущее окно, Activity может уже быть уничтожен, и юзер выпадет из приложения.
Кроме проблем с сохранением логики, есть и рутина поддержки ООП-кода: плотно завязанные на Activity интерфейсы практически невозможно развивать дальше. Масштабирование, быструю замену одних элементов на другие, анимацию — все это будет очень тяжело реализовать в новых версиях приложения.
Благодаря моде на единую экосистему все мобильные приложения стали в целом очень похожи. Стоит только нащупать тропинку да немного потренироваться, и тогда качество начинает быстро расти. Следуя принципу «критикуя — предлагай», мы сейчас напишем приложение, реализующее универсальный пользовательский интерфейс. Это будет интересно не только с академической точки зрения — уверен, написанный сегодня код ты сможешь легко встроить в свои проекты. Итак, начнем!
Fragments
Чтобы работать с UI было проще и быстрее, Google создала фрагмент (Fragment) — класс — прослойку между Activity и визуальными составляющими программы. С одной стороны, это контейнер для любых View-объектов, которые могут быть показаны пользователю. С другой — продолжение Activity, от которого Fragment получает всю информацию об изменениях в жизненном цикле.

У фрагментов, как и у Activity, есть свой (правда, более оригинальный) жизненный цикл. К примеру, работать с UI сразу после создания фрагмента невозможно, нужно ждать загрузки всех элементов — после метода onCreate
выполнится метод onCreateView
, где и можно будет загрузить элементы.
public class FragmentOne extends Fragment {
...
public View onCreateView(...) {
View view =inflater.inflate(R.layout.fragment_one, container,false);
TextView textView = (TextView)view.findViewById(R.id.fo_text);
textView.setText("Hello, i'm first fragment!");
return view;
...
В фрагменте тоже можно переопределять любые методы, отслеживающие состояние окна. Так, если приложение уйдет в фон, в Activity выполнится onPause
, а затем метод с точно таким же названием выполнится здесь. Это может быть полезно — удобно для отключения от сторонних объектов, например привязанных сервисов (bound service).
FragmentTransaction
Зная, что работа с фрагментами будет насыщенной, Google заблаговременно создала для этого специальные инструменты. Классы FragmentManager
и FragmentTransaction
аккумулируют в себе все процессы: создание новых фрагментов и их удаление, передачу данных и так далее.
Объект FragmentManager
создавать не нужно, он уже есть в каждом Activity, нужно только получить на него ссылку. А все действия будут проходить через FragmentTransaction
, который затем самостоятельно передаст данные менеджеру фрагментов.
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
Хочу заметить, что классы, работающие с фрагментами, доступны в двух вариантах. Рекомендую использовать более новую версию — это те, у которых в пути импорта присутствует строчка android.support.v4. Это большая библиотека, созданная для организации обратной совместимости. Компания Google бережно относится к устройствам на всех версиях ОС, а библиотеки позволяют использовать новшества разработки даже при работе со старым API.
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.Fragment;...
FrameLayout
Часто данные в UI приходят динамически, в зависимости от происходящих событий. Чтобы было куда поместить картинку или текст, существует специальный контейнер — FrameLayout
. В этом и есть его основная задача — зарезервировать на экране место для любого объекта класса View, который можно будет подгрузить позднее. Фрагменты тоже живут в таких контейнерах.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/frame_container"
...
/>
Добавить новый фрагмент в FrameLayout можно по-разному: в FragmentTransaction доступны схожие по функциональности методы замены (replace) и добавления (add).
transaction.replace(R.id.frame_container, fragmentObject);
Несмотря на внешнюю схожесть, нужно хорошо понимать, какого результата ты можешь добиться. Метод replace работает очень просто — добавит фрагмент и, если в контейнере раньше что-то было, удалит старые фрагменты.
transaction.add(R.id.frame_container, fragment);
Метод add
же создает стек из фрагментов, и каждый новый вызов помещает на верх стека новый экземпляр. Важно, что при появлении нового фрагмента старые объекты в стеке не получают никаких оповещений и работают так, как будто бы ничего не произошло. Это значит, что метод onPause для них не выполнится и они продолжат расходовать ресурсы устройства, хотя пользователь перестанет их видеть.
Такая растрата выделенной памяти бывает оправданна, например если хочется организовать предобработку данных: фрагмент может что-нибудь подгрузить, пока будет незаметен, а потом показать уже готовый результат.
Действий с фрагментами может быть несколько, они будут аккумулироваться до тех пор, пока не выполнится метод commit.
transaction.commit();

Продолжение доступно только подписчикам
Вариант 1. Оформи подписку на «Хакер», чтобы читать все материалы на сайте
Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке
Вариант 2. Купи один материал
Заинтересовала информация, но нет возможности оплатить подписку? Тогда этот вариант для тебя! Обрати внимание: этот способ покупки доступен только для материалов, опубликованных более двух месяцев назад.
Уже подписан?
alexmaddys
21.12.2016 at 17:52
а где скачать исходники разобранного примера?
baragoz
22.12.2016 at 10:11
А вот при Джобсе в айфоне с плавностью проблем не было, а андроидофилы до сих пор напрягаются(
craigslist4
23.12.2016 at 06:09
Q1: Скажем, у меня бизнесс апп из 10 «страниц», включая логин/регистрацию. Мне тогда делать один активити и 10 фрагментов, или есть какой-то критерий, когда всё-таки нужен «свой» активити, помимо главного?
Q2: Где же можно скачать обещанные исходники примера?
Groff
11.01.2017 at 12:07
Ну вообще не сильно имеет смысл вообще отказываться от Activity. Нужно смотреть на сложность дизайнов экрана и логики. Если вы используете ViewPager с Fragments внутри фрагмента, то Вам, нужно использовать getChildFragmentManager() и тогда при смерте нашего фрагмента могут умереть и его дочерние фрагменты.
Если у вас флоу регистрации и логинизации то советовал бы вам разбить его на фрагменты и поместить его в пределах одного активити, это максимально логично. Отказываться от Activity ради того что бы сделать всё на фрагментах не лучшая практика. Попробуйте проанализировать и разделить все фрагменты на «логические модули» и уже для каждого такого модуля создавать своё Activity.
Ваша программа должна быть спроектирована так что бы ваша Активити ничего не знала про ваш Преимущество фрагментов в том что если у вас есть экран регистрации и допустим экран редактирования профиля и они одинаковы, вы можете создать один фрагмент для этого скрина и переиспользовать его в разных активити просто передав Bundle с нужно информацией в newInstance()
Андрей Пахомов
23.12.2016 at 13:58
Всем привет!
Исходники почему-то не залились, скоро исправим 🙂
пока их можно тут взять
http://www.filedropper.com/fragmentstar
комментарий по вопросу:
Нужно смотреть, сколько у приложения «точек входа», т.е. сколько есть вариантов запуска твоего приложения. Это может быть клик по иконке, интент или еще что-то. На каждую точку входа в приложение есть смысл выделять отдельное активити, так как отлавливать причину запуска активити не очень удобно.
А если оно будет запускаться только по клику на иконку, то можно и одним активити обойтись.
craigslist4
24.12.2016 at 01:44
Очень благодарен за статью и спасибо за ответы!
baragoz
26.12.2016 at 10:13
Спасибо, жги еще!
CookieWithJam
04.02.2017 at 08:48
Отличная статья (собственно и все предыдущие тоже:))! Спасибо за Ваш труд!