Мелочи разработки на Android для начинающих

В связи со стремительным развитием мобильных технологий, IT-компаниям все больше требуются разработчики мобильных приложений для своих продуктов. Наша компания не стала исключением. В моем распоряжении оказалось два падавана, которых надо было обучить премудростям разработки на Android (к слову сказать, парни были умные и способные, но практически без опыта). Было решено написать им небольшую памятку касательно основных аспектов разработки. Выкладываю ее на суд хабрапользователей.

Описания своих рассуждений выложу под спойлеры.

UI


Во-первых, не пользуйтесь визуальным конструктором. Никогда. Исключение – совсем уж начинающий разработчик, который не знает основ xml разметки Android, и то, при условии, что он потом вручную пройдется по разметке и вычистит весь мусор.

Спойлер
Проблема заключается в том, что визуальный конструктор – это не Android, а отображаемый там View – это не все ваше приложение, поэтому при попытке выставить его правильно конструктор может использовать отступы вместо центрирования, LinearLayout вместо FrameLayout или вообще, просто излишнюю вложенность. Как-то раз наткнулся на такой кусок верстки (сокращу атрибуты для наглядности), никогда так не делайте:

<RelativeLayout>
	<LinearLayout
		android:orientation=”vertical”
		>
		<LinearLayout
			android:orientation=”horizontal”
			>
			<FrameLayout>
				<!—Какой-то контент -->
			</FrameLayout>
		</LinearLayout>
		<LinearLayout
			android:orientation=”horizontal”
			>
			<FrameLayout>
				<!—Какой-то контент -->
			</FrameLayout>
		</LinearLayout>
	</LinearLayout>
</RelativeLayout>


А надо было всего две картинки нарисовать друг под другом.


Во-вторых, не используйте RelativeLayout где попало. Если вы хотите поставить View по порядку, то LinearLayout к вашим услугам. Если все ваши View строго позиционированы, то всегда можно прибегнуть к FrameLayout.

Спойлер
Встречаю практически повсеместно, но почему-то не все понимают, что разные типы View грузятся разное время. RelativeLayout самый тяжелый и долгий, FrameLayout один из самых легких (из элементов, которые могут содержать в себе дочерние View). И если для root-элемента Activity это никак не скажется, то для списков это будет заметное подтормаживание.

P.S. Это не призыв отказаться от RelativeLayout, в некоторых случаях без него действительно никак. Но только в некоторых. Не во всех!


В-третьих. Margin или padding? Если вы хотите сделать удобный кликабельный элемент, то padding и только он! Если вам необходим отступ, который будет у неактивного элемента, то тогда допускается использование margin.

Спойлер
Истина проста. Чем элемент больше, тем его удобнее нажимать. Margin – отъедает место у самого элемента, padding – у окружения. Не даром отступы есть даже у изображений, причем строго указанные и задокументированные для всех разрешений. Пожалуйста придерживайтесь этих правил, они сильно облегчат жизнь вам и вашим пользователям.


В-четвертых. Не забывайте, xml ничуть не менее код, чем та часть которую вы пишите на Java. Поэтому следите за тем что бы дублирование xml разметки было сведено к минимуму, по возможности создавайте свои элементы управления, если они будут использованы в нескольких местах (да и если не в нескольких, то тоже лучше создать отдельно).

Спойлер
Этот совет скорее для пользы разработчику а не пользователю, так как рано или поздно все равно придется менять поведение или представление у различных контролов вашего приложения. Вам же будет легче если все они будут в одном месте и аккуратно отсортированы.


База данных


Тут будет только один пункт, касательно самой распространенной БД на Android – SQLite. Она медленная. Очень. Не пренебрегайте ее оптимизацией и асинхронной работой с ней.

Спойлер
Этот факт не заметен на проектах с маленьким или статичным набором данных, но если приложение требует постоянных синхронизаций больших объемов данных (например, offline клиент для большого склада), то одновременное обновление и работа приложения могут быть затруднены или даже невозможны. Связано это с одной простой особенностью SQLite: она либо читает из БД, либо пишет, никак не враз (не учитываем такую вещь как грязное чтение, потому что новичку лучше к ней не прибегать кроме крайне необходимых случаев).


Тестирование


Ни для кого не секрет, что хорошее приложение — протестированное приложение. Поэтому не следует пренебрегать такими вещами, как комплексное и модульное тестирование, рецензирование кода другим разработчиком. Стесняться тут нечего, хороший программист не тот, кто не допускает ошибок, а тот, кто их находит и учитывает в будущем.

Говоря о советах могу сказать, что лучше использовать интерфейсы для всего, что работает не только с внутренним устройством вашего приложения (например, адаптеры для работы с сетью, для работы с другим приложением, бд). Потом при тестировании себе «спасибо» скажете.

Спойлер
Вся соль в том, что при модульном тестировании намного проще писать тест, если ты гарантировано знаешь что тебе придет на определенный запрос. В случае работы с внешними источниками (даже если они 100% доступны) всегда можно испортить их набор данных предыдущим тестом. В случае работы с интерфейсом можно просто создавать в тесте класс-заглушку, которая будет возвращать то, что вам нужно.


Локализация и ресурсы


Никогда не используйте захардкоденные строки. Используйте ресурсы (R.string.your_string). Даже если у вас нет и никогда не будет поддержки нескольких языков.

Спойлер
Во-первых, когда ваши строки выгружены в отдельный файл — с ними намного проще работать — и вам, и человеку, который будет проверять их на грамотность написания (да-да, за программистами есть такой грешок, что после написания тысяч строк кода родной язык начинает забываться, да и о банальных опечатках забывать нельзя).

Во-вторых, даже если вы считаете, что поддержки нескольких языков приложения никогда не будет — вы ошибаетесь. Если проект хоть что-то стоит, то его имеет смысл перевести хотя-бы на английский. Захардкоденные строки в этом случае потом вытащить крайне сложно.


Обратите внимание, насколько большое существует количество различных группировок ресурсов: по языку, по разрешению экрана, его ориентации и размеру, по стране и пр. Довольно большая часть этого, возможно, вам никогда не потребуется, но ознакомиться с ними надо. В будущем при проблемах отрисовки/расположения элементов на различных устройствах это сэкономит вам уйму времени.
Share post

Comments 16

    +6
    Статья годная, но уж очень для новичков. И, как мне кажется, от новичка.
    Прокомментирую пункт про потоки:
    Наверное, имелись ввиду классы, именуемые AsyncTask, Loader и прочие, и вызывались в них методы execute() да и только.
    В разных версиях ос Аndroid используется разный по характеристикам Executor по умолчанию — THREAD_POOL_EXECUTOR.
    В какой-то версии один поток и большая очередь, в какой-то 5 и т.д.
    В системе только при старте процесса для него сразу же создается 9 потоков и никто из них не ждет своей очереди:
    main 
    GC 
    Signal Catcher 
    JDWP 
    ReferenceQueueDaemon 
    FinalizerDaemon 
    FinalizerWatchdogDaemon 
    Binder_1 
    Binder_2
    
    Несмотря на то, что потоков в андройде может быть достаточно много, тем не менее следует понимать что с 5-го по 10-й поток будут стоять в очереди, более того, если поток встал в очередь он не будет запущен параллельно при последующем наличии вакантных мест.
    Потоки не будут стоять в очереди, они будут выполняться.
    А вот Runnable, которые вы пробрасываете на Executor по умолчанию, будут становиться в очередь, если в пуле свободных потоков больше нету.
    Если не хотите зависеть от версии ОС, то задайте своё поведение — запускайте AsyncTask на своём Executor при помощи метода
    aTask.executeOnExecutor(YOUR_EXECUTOR);
    
      +4
      Да уж, про потоки повеселило :)

      Я бы добавил, что не нужно использовать AsyncTask в принципе, т.к. он заставляет писать логику (doInBackround) и управление Ui (onPreExecute, onPostExecute) в одном месте, что не здраво, лучше логику писать на уровне модели, а в контроллерах (Activity, Fragment) только слушателей результата.

      К тому же, у AsyncTask не предусмотрен механизм обработки ошибок и от версии к версии они меняют поведение THREAD_POOL_EXECUTOR.
        0
        Google советуют использовать Loader для асинхронных операций, как замену класса AsyncTask — не нужно проверять, закрыта уже наша Activity или нет при обновлении UI, нужно ли перезапросить данные при повороте экрана и т.д.
        Хранить модель вне Activity я бы не советовал, так как они очень шустро уничтожаются системой при нехватке памяти.
        Конечно же это мое скромное мнение, но хранить глобальные динамические данные нужно в Application, постоянные обязательно в SQLite, а все Activity зависимые данные в ней же и хранить с учетом того, что данные могут потеряться.

        В защиту AsyncTask — можно использовать их так (так используются классы Loader, но с привязкой к циклу жизни Activity):
        @Override
        protected Object doInBackground(Object[] params) {
        	return yourModel.anyAction();
        }
        @Override
        protected void onPostExecute(Object o) {
        	// update UI here
        }
        

        Если хотите данные хранить в модели, и чтобы модель обновляла UI, делайте так:
        public void anyAction(Object yourUI){
        new AsyncTask() {
        	@Override
        	protected Object doInBackground(Object[] params) {
        		return anyData;
        	}
        	@Override
        	protected void onPostExecute(Object o) {
        		yourUI.updateWithData(o);
        	}
        }.executeOnExecutor(YOUR_EXECUTOR);
        }
        
        


        Код грязный, но суть я думаю понятна.
        –1
        Да, все верно, инструкция для совсем новичков, от новичка, который только-только усвоил самые азы разработки на Android :)
        Спасибо, про потоки буду иметь ввиду, не знал
          0
          К сожалению, executeOnExecutor() появился только в API 11. Хотя сейчас уже меньше заботятся о поддержке старых версий.
          Вообще, стоит убрать абзац про потоки в статье, либо основательно переделать. Информация в нем касается только AsyncTask, да и то не совсем верная.
            0
            Хорошо, убрал раздел про асинхронность, пока основательно не ознакомлюсь и не доработаю его
          +2
          А модно поподробнее про медленность SQLite? Я читал ровно наоборот.

          И еще, я бы еще упомянул о статических анализаторах кода, как помощь к комплексному тестированию. Если модульное тестирование вещь не особо простая, то статический анализатор довольно прост, а выгоды в нём достаточно много. Тот же Lint что идет вместе с AndroidStudio умеет находить захардкоденные строки и многое другое.

          Немного оффтопа. Как лучше именовать строки в ресурсах, чтобы это было понятно? У меня сейчас с этим честно говоря хаос. С именованием XML файлов и всего что связано с Java кодом, я уже выработал свою методику на основе книги Совершенный код и Code convention. Но вот с такой простой вещью как строки, пока ума не приложу.
            0
            SQLite безусловно быстрый :) Медленный он только в ситуации, когда необходимо прочитать и записать большой объем данных одновременно. Связано это с тем что на устройстве вся БД — это один файл и при записи он блокируется. Как выход, например, грязное чтение. Мы у себя в проекте вообще пришли к такому выходу: храним базу на сервере, на клиент заливаем сразу готовой и сохраняем, благо набор данных меняется очень редко.

            На счет строк, мы храним их как [название модуля]_[элеменет]_[действие, если есть необходимость]. Пришли к этому, когда возникла проблема как у вас.
            +5
            Я не совсем понял ваш пассаж про визуальный конструктор. По большой частью конструктор лишнего ничего не пишет. А ваш пример какой-то дикий. Это не конструктор накидал столько мусора, а сам программист. С таким же успехом он может это написать и вручную, если не понимает, как работает верстка.
            Безусловно, пройтись по XML-коду потом желательно, но для быстрого наброска экрана визуальный конструктор вполне рабочий инструмент.
              0
              Полностью согласен. За все три года разработки под Android всегда начинаю верстку с визуального редактора. Но редактировать верстку, конечно, чаще всего проще непосредственно в XML.
                +3
                Никогда не трогал визуальный редактор, вёрстка под Android проста и очевидна на 99%, не проблема писать её руками. А неймспейс «tools:» визуальный редактор поддерживает?
                  +1
                  Давно уже.
                0
                Мне тоже не понятны упреки в сторону визуального редактора. Если программист захочет сделать говноверстку, он ее сделает и в текстовом редакторе. А визуальный редактор удобен прежде всего не отображением экрана устройства, а полезными панельками, где показана иерархия элементов и их параметры. Куда удобнее ведь просто поставить чекбокс или выбрать нужный ресурс из диалогового окна, чем писать вручную (пусть и с автодополнением).
                0
                Помню где-то читал, что не рекомендуется делать вложенные LinearLayout. А что лучше делать: RelativeLayout или же LinearLayout со вложенным LinearLayout?
                  0
                  основная проблема в том, что невозможно померить за один проход LinearLayout (LL) в котором есть веса. Все элементы, у которых есть веса будут мерится два раза. Если будут вложенные LL, то количество измерений будет расти экспоненциально. например LL1 содержит в качестве ребенка LL2 у которого стоит вес. Все дети LL1 у которых нет веса измерятся один раз, у которых есть измерятся два раза. Все дети LL2 у которых нет весов измерятся два раза, а у которых он есть уже 4 раза. померить некоторые вью довольно тяжело.

                  RelativeLayout расставляет все за один проход, но этот проход довольно сложный.

                  поэтому конкретно ответить на этот вопрос нельзя.
                  часто лучше написать кастомный контролл, который все по-быстрому расставит. чем пытаться собрать лейаут из универсальных блоков.
                  +2
                  Если все ваши View строго позиционированы

                  так и пишите, что если координаты вью в пикселях (pt/mm/dp/sp), либо достаточно стандартной gravity. Что такое «строго» в вашем варианте хрен поймешь.

                  Margin – отъедает место у самого элемента, padding – у окружения

                  это вообще все неверно. Margin это отступы, которые учитывает родительский элемент при позиционировании ребенка, у которого задано Margin. Не все ViewGroup поддерживают Margin и на размер ребенка Margin напрямую не влияет. Так как Margin не принадлежит геометрии ребенка, то и клики ребенку не доходят из этой области, они идут родителю. Padding же работает подругому. Сама View, у которой он задан занимается его расчетом. Например, view не учитывает паддинг когда рисует свой фон, но учитывает его и делает отступы для своего содержимого.

                  Only users with full accounts can post comments. Log in, please.