Двадцать лет назад использовать Android было невозможно. Десять лет назад было еще рано. Сегодня — уже поздно. Конечно, я утрирую, но, когда операционная система молода, многие ее недостатки можно списать на «детские болезни». Их можно понять, простить и не замечать, ожидая скорейших багфиксов от компании-производителя, но… Android уже дорос, пожалуй, до бальзаковского возраста, а мы все так же решаем проблемы, которые давным-давно должны быть исправлены. Сегодня мы рассмотрим те затруднения при разработке под Android, с которыми ты точно когда-нибудь столкнешься. И которые, кстати, дико бесят. 🙂
 

Жизненный цикл Activity может в любой момент перечеркнуть все твои планы

Если ты программируешь под Android, то наверняка видел схему жизненного цикла для самой простой активити. А знал ли ты жизненный цикл окошка с кнопочками, которое создавал компилятор старого и доброго Delphi? Я думаю — нет, не знал, и знать тебе это было не нужно. Интересно, как бы на тебя посмотрели олдскульные дельфисты-паскалисты, если бы ты им сказал: «когда пользователь перевернет свой экран, нам надо пересоздать все формы и восстановить состояние всех компонентов». Скорее всего, они бы пальцем у виска покрутили. И не только потому, что переворачивать старые мониторы и системные блоки было не принято. 🙂

А меж тем Android именно так и устроен: повернул экран — начинай все сначала. Конечно, в настройках ты можешь отключить поворот экрана, но это не всегда удобно, да и рядовые пользователи не знают и десяти процентов возможностей своего смартфона. Поэтому сейчас имеем мы то, что имеем, и единственное, что ты можешь сделать при разработке, — это отключить в манифесте пересоздание Activity, добавив свойства

android:configChanges="keyboardHidden|orientation|screenSize"

Конечно, «знающие люди» так делать не рекомендуют, но такой костыль используют даже серьезные социальные приложения. Если не веришь — посмотри сам манифесты из их APK (вот инструкция).

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

Какой известный лончер для Android сейчас старательно хоронит его производитель?

Загрузка ... Загрузка ...
 

Сбор ошибок, которого не было

Я с тоской вспоминаю старые времена виндовой разработки. Кому нужны системы для сбора ошибок? Да никому! Большинство ошибок лежало «на поверхности», программа или работает, или нет. Если она периодически вылетает — значит, «проклятая винда опять глючит». 🙂 А если она вылетает по вине программиста, то и не страшно, ведь онлайн-сторы программного обеспечения еще не придумали и никто не засыплет твою программу минусами и гневными комментариями. Максимум — под пивко поплачет в жилетку своему дилеру пиратского ПО на Митинском радиорынке.

Сегодня ты лично, оперативно и максимально прозрачно отвечаешь за каждый свой begin и end в Kotlin-коде. Поэтому собирать ошибки нужно сразу при старте приложения.

Систем сбора сейчас расплодилось превеликое множество, но лично я пользуюсь Crashlytics. В ее пользу говорит и то, что ее недавно купила сама Google. Включать сбор ошибок можно для каждой активити отдельно, но проще сразу включить его в классе Application:

@Override
protected void onCreate() {
  super.onCreate(savedInstanceState);
  Fabric.with(this, new Crashlytics());
  try {
    FirebaseApp.initializeApp(this);
    mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
    Crashlytics.log(Log.INFO, "App.onCreate", "Firebase initialized");
  } catch (Exception e) {
    e.printStackTrace();
    Crashlytics.log(Log.ERROR, "App.onCreate",
    "Error initialize Firebase with: " + e.getMessage());
  }
}

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

Как видно из приведенного выше кода, один инструмент Google — Crashlytics — следит за работой другого — Firebase. Несмотря на то что оба они могут выполнять одинаковые задачи — собирать события, Crashlytics действует так:

Crashlytics.log(Log.INFO, "App.onCreate", "Firebase initialized");

А Firebase — вот так:

public static void selectContent(String type, String id) {
  if (mFirebaseAnalytics != null) {
    Bundle bundle = new Bundle();
    bundle.putString(FirebaseAnalytics.Param.ITEM_ID, id);
    bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, type);
    mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);
  }
}
 

Теперь я стреляю у девушек телефон... чтобы отловить на нем баги

Про изменение прошивки производителем ты уже слышал. Но как быть, если неприятность уже случилась и твое приложение, к примеру, стабильно падает у производителя X на устройстве Y с прошивкой Z?

Все бы ничего — подумаешь, одно падение у одного устройства. Но таких устройств было продано несколько миллионов штук, так что гневных отзывов и однозвездных комментариев тебя ждет несколько десятков. Как быть? Использовать фермы для тестирования. Наш материал про семь лучших ферм все еще актуален, советую его прочитать.

Из халявных инструментов я бы предпочел Firebase, который на бесплатном тарифе дает прогнать тесты по десяти реальным устройствам. Кроме того, не забывай погуглить собственные веселые фермы от разработчиков смартфонов. И заводи контакты с людьми, которые работают в магазинах электроники или сервисных центрах (особенно неплохо это получается в маленьких городах). Тогда ты сможешь делать, как я, — брать устройства для тестов у них. Главное, не забудь вернуть гаджет в первоначальное состояние и скрой меню для разработчиков, если оно было заблокировано ранее.

Почему на двух одинаковых смартфонах одно и то же приложение работает по-разному?

Загрузка ... Загрузка ...
 

Хочешь меньше багов в приложении? Выкидывай фрагменты и асинктаски (лоадеры тоже не забудь)

Говорят, что секрет 99,9% удачных пусков приложения — в отказе от фрагментов в любом виде. Дело в том, что у разных производителей они порой выкидывают такие замысловатые ошибки, что понять их можно только с помощью хрустального шара. Если шаром ты владеть не обучен, лучше вовсе откажись от фрагментов. Тот же ViewPager можно сделать и без них.

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

 

Фрагментация ухудшает внешний вид

Огромный парк разноформатных устройств — одновременно баг и фича мира Андроида. Из-за большой фрагментации приходится вымерять разметку под каждую плотность экрана. Редактор макетов экранов при этом крайне неудобный. Из-за этого особо отчаянные (Telegram) вообще его не используют, а генерируют экраны динамически, из кода.

А как там с редизайном стандартных меню и вкладок? Легко! Два дня вдумчивого гугления, ящик пива, блок сигарет, пробы, ошибки, несколько костылей — и пожалуйста, приложение выглядит почти как нарисовал дизайнер. 🙂 Уверен, что «отличные» компоненты DrawerLayout и ViewPager отлично научат тебя пользоваться поиском при любом нестандартном поведении или когда просто захочешь сменить шрифты в меню.

 

Библиотекам поддержки самим нужна «помощь и поддержка»

Серьезно, каждый новый релиз — новые костыли! Почти все компоненты из библиотеки поддержки несут неизлечимые баги, которые не исправляются годами. У меня было несколько ошибок, которые умерли вместе с устройствами, но так и не были исправлены. К примеру, отличный класс для уведомлений NotificationCompat меняется каждую версию Android, и ты меняешь свой код вслед за ней.

Библиотеки поддержки очень большие и сами тянут в проект библиотеки для поддержки. Программисты даже шутят: «Каждый класс в этих библиотеках будет иметь суффикс CompatCompat». Кстати, чтобы увидеть, что тянет та или иная библиотека за собой, можно воспользоваться инструментом gradle — dependencies.

Дерево зависимостей
Дерево зависимостей

Для удаления хотя бы части ненужного кода можно прописать правило для gradle:

configurations.all {
  exclude group : 'com.android.support', module :'transition'
  exclude group : 'com.android.support', module :'customtabs'
  exclude group : 'com.android.support', module :'support-media-compat'
  exclude group : 'com.android.support', module :'support-fragment'
}

После отработки правил исключения не забудь еще раз все протестировать.

 

«Мы хотели помочь вам c разметкой и написали Constraint Layout, но он падает и тормозит»

Новинка, которая должна была упростить нам жизнь, — Constraint Layout — сложна в использовании, и приложение из-за нее становится тяжелее и медленнее. Чтобы работать с этим инструментом эффективнее, советую прочесть статью. А в преддверии версии Constraint Layout 2.0 можно изучить это выступление. По слухам, его представят на Google IO 2018.

Ты уже писал приложения для Android?

Загрузка ... Загрузка ...
 

Просто записать файл на флешку не получится: спроси разрешения, а лучше городи контент-провайдер

Сама идея ограничивать доступ программ к файловой системе — благая и по сравнению с «виндовой вольницей», где каждый делает что хочет, конечно, секьюрная. Но какой же это геморрой, коллеги!

Перекинуть данные через простой файл не разрешает сама ОС. Хочешь обмена? Пиши контент-провайдер. Точно нужно записать что-нибудь на диск? Реально нужно? Изволь спросить у пользователя кучу разрешений и заранее продумать, что делать, если он их тебе не даст.

 

Все стандартные медиакомпоненты просто ужасны

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

Для работы с камерой изучи «Побеждаем Android Camera2 API с помощью RxJava2».

Как лучше проигрывать музыку? Подсмотри у Timber и Phonograph.

 

Любой желающий может декомпилировать твое приложение и изменить его, как захочет, — оно просто создано для этого

Исторически сложилось так (и это уже точно не исправить), что код для Android работает в виртуальной машине. Кому пришло в голову поднимать на низкопроизводительных ARM-процессорах виртуалки? Вряд ли теперь мы это узнаем. Apple сделала все, как надо, и завоевала приз наших зрительских симпатий, а вот «корпорация добра» решила извратиться, и теперь нас напрягает (спасибо современным процессорам) даже не производительность, а тот факт, что исходный код приложения можно восстановить почти что до запятой.

Пиши свой код качественнее, и его будут читать не только китайские хакеры, но и программисты из развитых стран — считай это успехом. 😉 А стоит тебе пренебречь обфускацией, и любой школьник сможет модифицировать твою программу и сделать на ее основе свой шедевр.

Тебе доводилось изменять код чужих приложений для Android?

Загрузка ... Загрузка ...

3 комментария

  1. Аватар

    baragoz

    08.05.2018 в 14:58

    Знал, что настанут времена и Дельфи с его 200Кб программой — хеллоувордом будут вспоминать добрым словом 🙂

  2. Аватар

    vaca

    09.05.2018 в 11:14

    Вот посмотришь WWDC, а там про десятки новых функций API, фреймворки встроенные, мастерклассы для разработчиков по ним. У гугла на IO есть что-то или рассказывать не о чем и незачем, если новая версия к следующей конфе наберёт процентов 10?

  3. Аватар

    Atos4444

    13.05.2018 в 09:49

Оставить мнение