Введение
Доброго времени суток, уважаемые читатели. Во введении мне хотелось бы сказать, что код, который Вы встретите в данном цикле статей, не претендует на использование в качестве примера "Это есть хорошо", служит он лишь для ознакомительных целей и разбора необходимых моментов.
Так же, я не являюсь профессиональным Java-разработчиком, поэтому программный код может иметь определенные недочеты и, разумеется, я уверен, что поставленную задачу можно было решить более элегантно. Тем не менее, тем, кто столкнулся со схожей задачей данная статья послужит хоть каким-никаким, но подспорьем в её решении.
Статья 1.
Проблемы, с которыми может столкнуться начинающий Android-разработчик.
Проблема 1. Не любите асинхронность? А придется полюбить. Ну или стерпеть.
И это не является шуткой или гиперболой. Рассмотрим в качестве примера любую конфигурацию. Асинхронность встречается в любой более-менее серьезной конфигурации, но используется там лишь в тех местах, где есть необходимость - к примеру, во время формирования сложных отчетов, где она служит для того, чтобы пользователь мог продолжить работу с программой во время этой операции.
В Android все немного по-другому. Так или иначе, но работая с Android-приложениями (по крайней мере, в нашем случае), мы все равно столкнемся с необходимостью хранения и получения данных.
Каждое Android-приложение имеет поставляемую "из коробки" базу данных SQLite 3. Имеются различные обертки, но мы будем рассматривать в качестве обертки Room Persistence Library от Google. Несмотря на возможность удобной работы с базой данных и отсутствие необходимости вручную писать запросы, изучая особенности SQLite 3, мы имеем одну большую проблему.
Пример 1.
Приложение, которое мы с Вами разработаем к концу данного цикла, будет иметь возможность работать с веб-сервисом определенной структуры в рамках нескольких информационных баз. Следовательно, возникает необходимость хранить где-то данные для подключения к веб-сервису и прочую служебную информацию. Этот пример послужит нам основой для разбора данной проблемы.
Продолжаем рассуждать.
Любой 1С-разработчик скажет - Что тут сложного? Написал запрос, забрал данные и готово!, но здесь все немного иначе. Дело в том, что Android имеет один основной поток приложения, который, можно сказать, отдан под работу с UI. Вызвать оттуда какой-либо метод какого-либо класса, который запрашивает и возвращает данные для их дальнейшей обработки, просто так нельзя - в противном случае, наше приложение даже не соберется, а компилятор скажет нам, что было бы неплохо сначала заглянуть в документацию.
Суть данной проблемы заключается в том, что независимо от количества записей в таблице - даже если там всего одна запись - запрашивать эти данные необходимо в отдельном потоке во избежание блокировки UI.
Из этого вырастает потеря огромного количества времени на написание асинхронных задач и настройку взаимодействия между потоками и процессами приложения (IPC). Примерно по такой схеме будет происходить запрос некоторого количества записей из таблицы локальной базы данных нашего приложения:
Как видите, начинается всё с активности. Эта активность должна описывать заранее созданный интерфейс для реализации callback'а. В активности создается экземпляр класса асинхронной задачи, после чего данная задача начинает выполняться (после вызова соответствующего метода .execute()). При создании экземпляра класса, необходимо предусмотреть передачу в конструкторе экземпляра активности - именно через неё мы будем получать доступ к ресурсам приложения, а так же именно через экземпляр класса активности мы и получим возможность создать callback обратно к ней.
Класс асинхронной задачи должен содержать как минимум 3 обязательных перегруженных метода - onPreExecute(), который вызывается перед тем, как задача начнет выполняться, doInBackground(), в теле которого и происходит выполнение задачи и onPostExecute(), который вызывается непосредственно после выполнения задачи. Пока что можно не вдаваться в такие подробности, более подробно мы рассмотрим это в следующих статьях. В onPostExecute() вызывается полученный от экземпляра активности описываемый интерфейсом метод, который и служит нам для оповещения активности о завершении выполнения задачи и позволяет передавать результат без излишней возни.
Примечание 1.
Методы onPreExecute(), doInBackground() и onPostExecute() являются перегруженными, аналогично перегрузить необходимо и метод, который принимает сигнал о завершении задачи с результатом.
Примечание 2.
В конструктор нам необходимо передавать экземпляр активности по двум причинам. Во-первых, иначе нам не получить доступ к интерфейсу для организации callback'а, а так же именно через экземпляр активности мы сможем получить доступ к ресурсам приложения - в том числе, для поиска Views, строковых значений, контекстов приложения и активности.
Примечание 3.
Так же, мне хотелось бы заметить, что данное ограничение распространяется и на HTTP-запросы, которые мы будем использовать - их так же необходимо выполнять асинхронно.
Проблема 2. Думаете, что SOAP и Android - близкие друзья? Вы ошибаетесь.
Данная проблема заключается в том, что, несмотря на популярность SOAP, разработчики системы не позаботились о поддержке данного способа обмена данными. Реализовывать поддержку SOAP нам придется собственными силами, используя HTTP-запросы, предусмотренные в пакете java.net.
Тем не менее, данная проблема не является критичной, так как разобраться в принципах работы используемых технологий будет полезно, если Вы этого не сделали ранее.
Примечание 1.
Собственно, отсутствие поддержки SOAP и натолкнуло меня на идею написания данного цикла статей. В сети присутствует множество статей-примеров для работы с SOAP из-под Android, но во всех найденных мной статьях используется библиотека android-ksoap2, а так же пытались использовать Apache. У меня не получилось заставить работать обмен данными ни с Apache, ни с android-ksoap2. К тому же, если имеется возможность реализовать что-то тремястами строками кода, не прибегая к подключению тяжелой библиотеки, - лучше поступить именно так.
Проблема 3. DEX 64K LIMIT.
А вот на этой проблеме я остановлюсь подробнее, нежели на второй. Если Вы ранее не сталкивались с данной проблемой, то поначалу она может поставить в тупик и отбить желание заниматься мобильной разработкой вообще.
Во время сборки приложения, генерируется так же .dex-файл, который содержит в себе скомпилированные Java-классы. Вы не ограничены в количестве методов, которые могут быть описаны в Вашем приложении, но вот вызывать Вы можете только первые 65536 методов.
Это связано с ограничениями в размере поля, отведенного под описание методов в DEX-файлах.
Данная проблема способна действительно вогнать в ступор начинающего разработчика, так как изначально нельзя понять - что можно сделать для решения данной проблемы.
Перед тем, как начать расписывать решение данной проблемы, которое будем использовать мы с вами, я отвечу на логичный вопрос.
- У нас от силы будет около 200 методов, откуда превышение данного лимита?
Все дело в том, что, как бы то ни было, без использования библиотек в своем проекте не обойтись, и актуально это для проекта абсолютно любого размаха. Туда входят библиотеки обратной совместимости, библиотеки для работы с Google Play Services и прочее, и прочее. К слову, только Google Play Services может "съесть" около 20 тысяч методов - что уже довольно-таки весомая часть ограничения. К тому же, Android постоянно развивается - и даже системные библиотеки так или иначе растут в своем объеме, поэтому лучше изначально предусмотреть возникновение такой неприятной ситуации.
А теперь продолжим.
Для решения данной проблемы существует 3 способа - использование плагина для Android Studio, использование различных инструментов для чистки проекта от неиспользуемых библиотек и добавление поддержки MultiDex. Как бы ни заманчивы были первые два способа, я их могу назвать больше костылями, нежели путями для решения, поэтому мы остановимся на третьем варианте.
Что такое поддержка MultiDex? Как правило, это добавление возможности использовать не один DEX-файл, а нескольких, подгружая их в runtime. Но, тем не менее, я должен предупредить, что даже этот способ накладывает определенные ограничения - в том числе, на использование библиотек, поскольку нечаянно можно вызвать библиотеку до того, как она будет загружена (хотя компилятор это поймет и не соберет подобное приложение, это может добавить нервотрепки в и без того неприятную ситуацию). Подробное решение данной проблемы с поэтапным описанием вы сможете увидеть в следующих статьях данного цикла.
Послесловие.
На этой ноте мне хотелось бы закончить первую часть цикла статей, поскольку в этой части я хотел рассмотреть именно основные проблемы при реализации нашего приложения.
Во второй части нашего цикла мы разберем с Вами работу с Room Persistence Lbrary, на пальцах и примерах научимся создавать простые и сложные модели данных, а так же менеджеры для работы с таблицами на примере создания основы для хранения данных соединений и уведомлений приложения, а так же познакомимся с реализацией асинхронных задач в Android на примере асинхронной работы с базой данных.
Я хотел бы ещё раз напомнить о том, что данная статья является пробой пера - поэтому здоровая критика и пожелания по улучшению качества статей только приветствуются.