[Из песочницы] Droidutils — набор решений, которые ускоряют разработку приложений под Android

При разработке приложений я заметил, что каждый раз, когда мне приходилось сталкиваться с решением похожих задач (реализовывать работу с http, json, multithreading и т.п.), приходилось делать одну и туже роботу, причем на это уходило много времени. Поначалу это было не критично, но в больших проектах занимало слишком много времени. Чтобы сэкономить свое и ваше время, решил написать универсальное решение для этих задач, которым и хочу поделиться с сообществом.Начнем с парсинга JSONDroidutils предоставляет удобный класс для работы с JSON, который позволяет конвертировать данные в JSON и обратно в объект класса, реализующего структуру конкретного JSON. Давайте посмотрим на примере.У нас есть JSON:

{ «example»:{ «test»: «Hello World» }, «company_name»: «Google», «staff»:[ { «Name»: «David» }, { «Name»: «Mike» } ], } Теперь нам нужен класс, в который мы запишем данные. Каждое поле, в которое мы хотим записать определенные данные, нужно пометить аннотацией и указать ключ, по которому хранятся данные в JSON. public class Company {

// можно указывать конкретное поле из JSONObject @JsonKey («test») private String mTest;

@JsonKey («company_name») private String mCompanyName;

@JsonKey («staff») private LinkedList mStaff;

public class Employee {

@JsonKey («Name») private String mName;

} } Все готово, теперь можем парсить JSON. JsonConverter converter = new JsonConverter (); try { // Получаем объект нашего класса уже с данными из JSON Company company = converter.readJson (exampleJson, Company.class); } catch (Exception e) { e.printStackTrace (); } Возможно и обратное действие. Для этого нужно создать экземпляр класса и заполнить его данными (поля тоже нужно пометить аннотациями) и передать парсеру, в результате получим JSON строку: String json = converter.convertToJsonString (new Company ()); Все просто. Но сейчас вы скажете, что есть куча разных и мощных фреймворков, которые все это уже умеют (например jackson). Я с вами согласен, но в большинстве случаев мы не используем всех мощностей данных фреймворков. В таких случаях зачем нам лишний балласт, если можно обойтись одним классом? Маленькое отступление При разработке приложений, старайтесь избегать множества зависимостей. Не спешите подключать к проекту кучу библиотек только лишь потому, что вам лень своими ручками писать. Или потому что разработчик данной библиотеки вовсю кричит, что его разработка решает данную проблему. Я не говорю, что зависимости — это плохо, просто перед тем, как что-то внедрять в свой проект, лучше подумайте несколько раз, нужно ли вам это.Основные причины, почему много зависимостей плохо:

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

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

Работа с Http Для того, что бы работать с Http в Android, мы можем использовать одно из двух стандартных решений: ApacheHttpClient или HttpURLConnection. Я выбрал HttpURLConnection, так как ребята из Google сами его используют и нам рекомендуют.Теперь о достоинствах и недостатках: — HttpURLConnection немного быстрее, но менее удобный (как по мне, так это только на первый взгляд); — ApacheHttpClient гораздо удобнее по отношению к предыдущему, но медленнее, и в нем есть парочка багов;

Давайте представим, что мы разрабатываем приложение, которое тесно общается с сервером. У нас есть куча разных запросов, которые нужно посылать на сервер. Некоторые из них сами периодически ходят на сервер за обновлениями, а другие мы сами посылаем. И еще нам нужно некоторые данные кэшировать. Возьмем за пример новостную ленту. Представим, что у нас есть запрос, с помощью которого мы получаем новую информацию, назовем его «update_news_request».

Приступим к созданию запроса Для построения Url есть удобный builder: String url = new Url.Builder («http://base_url?») .addParameter («key1», «value1») .addParameter («key2», «value2») .build (); // На выходе получаем http://base_url? key1=value1&key2=value2 Тело запроса можно создать очень просто: // создаем объект класса, который реализует структуру тела запроса // как в примере с JSON Company сompany = new Company (); // передаем наш объект в конструктор HttpBody HttpBody body = new HttpBody(сompany); С хедерами все тоже просто: HttpHeaders headers = new HttpHeaders (); headers.add («header1», «value1»);

HttpHeader header = new HttpHeader («header2», «value2»); headers.add (header); Теперь создадим Http запрос, для этого у нас есть удобный builder: HttpRequest updateNewsRequest= new HttpRequest.Builder () .setRequestKey («update_news_request») // указываем ключ, о нем чуть мы еще поговорим .setHttpMethod (HttpMethod.GET) // указываем тип запроса (по умолчанию HttpMethod.GET) .setUrl (url) .setHttpBody (body) .setHttpHeaders (header) .setReadTimeout (10000) // устанавливает максимальное время ожидания входного потока для чтения // по умолчанию 30 сек. .setConnectTimeout (10000) // максимальное время ожидания подключения (по умолчанию 30 сек.) .build (); Вот мы и создали наш запрос. Для выполнения запросов нам нужен класс HttpExecutor: HttpURLConnectionClient httpURLConnectionClient = new HttpURLConnectionClient (); httpURLConnectionClient.setRequestLimit («update_news_request», 30000); httpExecutor = new HttpExecutor (httpURLConnectionClient); Давайте разбираться. Конструктор HttpExecutor требует реализацию интерфейса HttpConnection. В нашем случае я использую реализацию HttpURLConnection (можно использовать и другую реализацию). Во второй строчке задается временное ограничение для конкретного запроса (здесь используется тот самый ключ, который был указан при создании запроса). То есть обращение к серверу будет происходить не чаще чем 30 сек (в нашем случае), все остальные попытки этого запроса будут обращаться в кэш или вовсе ничего не делать. Это удобно, когда нужно уменьшить нагрузку на сервер.Теперь можно выполнить запрос:

RequestResponse response = httpExecutor.execute (request, RequestResponse.class, new Cache() { @Override public RequestResponse syncCache (RequestResponse data, String requestKey) { // пишем в кеш и возвращаем данные уже с кеша // так мы избежим проблем синхронизации данных на сервере и в кеше return data; }

@Override public RequestResponse readFromCache (String requestKey) { RequestResponse response = new RequestResponse (); response.hello = «hello from cache»; return response; } }); Первый параметр — собственно объект нашего запроса, Второй параметр — это класс, в который будет записан результат запроса и Третий параметр — реализация интерфейса Cache, сюда мы и будем обращаться, если запрос будет делаться чаще, чем указано в лимите. При желании можно не использовать Cache. Все просто и удобно.Работа с потоками Для работы с потоками решил использовать java.util.concurrent. Этот пакет предоставляет нам кучу всяких удобных инструментов и потокобезопасных структур данных для работы с многопоточностью.При коммуникации с сервером возникает ряд проблем, которые нужно решить.Первая проблема, которую нужно решить, это сделать так, что бы два потока одновременно не выполняли один и тот же запрос на сервер.

Тут нам на помощь приходит Semaphore. Давайте посмотрим на код:

public class CustomSemaphore {

private Map mRunningTask;

public CustomSemaphore (){ mRunningTask = new HashMap(); }

public void acquire (String taskTag) throws InterruptedException {

Semaphore semaphore = null; if (! mRunningTask.containsKey (taskTag)) { semaphore = new Semaphore (1); } else { semaphore = mRunningTask.get (taskTag); } semaphore.acquire (); mRunningTask.put (taskTag, semaphore); }

public void release (String taskTag) throws InterruptedException {

if (mRunningTask.containsKey (taskTag)) { mRunningTask.remove (taskTag).release (); } } } Итак, как же эта штука работает? Когда поток выполняет запрос на сервер, мы отдаем этому потоку блокировку и сохраняем в Map наш Semaphore, где ключом является ключ нашего запроса «update_news_request». Пока первый поток выполняет запрос, приходит второй поток с таким же самым запросом и в этот момент он проверяет, хранится ли в Map по данному ключу Semaphore. Если такой есть, тогда он пытается взять в данного Semaphore блокировку, а так как первый поток уже забрал ее, второй поток останавливается и ждет, пока первый поток отпустит блокировку. Таким образом, два потока не смогут сделать одновременно один и тот же запрос.

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

Вторая важная проблема возникает, когда нужно сделать несколько запросов подряд.

Например, нужно залогинится в какой-то социальной сети, потом получить профайл пользователя, потом с необходимыми данными пройти регистрацию на нашем сервере. Таким образом у нас получается три запроса. В таких случаях не нужно делать вложенные callback-и. Например, вы из UI потока запускаете другой поток, который делает запрос на сервер, а потом дергает callback в UI потоке, который в свою очередь запускает еще один поток, который делает следующий запрос — и так далее. Этот подход создает в коде многоэтажные вложенности, которые сложно читать и дебажить. Создается много ненужного кода. Но самое главное, с точки зрения многопоточности это плохая практика создавать без надобности кучу потоков и постоянно дергать UI поток. В таких случаях лучше сделать эти три запроса синхронными в одном потоке и там же обработать всю информацию, а в UI поток отправить только результат.

Есть и удобное решение для того, чтобы делать что-то по таймеру. Например, ходить на сервер за обновлениями каждые 30 секунд:

ScheduledFuture scheduledFuture = ThreadExecutor.doTaskWithInterval (new Runnable () { @Override public void run () { // ходим на сервер } }, 0, 30, TimeUnit.SECONDS); Этот метод также возвращает нам реализацию интерфейса ScheduledFuture>, с помощью которого мы можем остановить роботу нашего таймера, а также запросить результат с помощью метода get (). Только нужно помнить, что этот метод блокирующий.Еще в классе ThreadExecutor есть два удобных метода:

doNetworkTaskAsync (final Callable task, final ExecutorListener listener) doBackgroundTaskAsync (final Callable task, final ExecutorListener listener) Отличие заключается в том, что у каждого свой пул потоков, что довольно удобно.Заключение Вот мы и добрались до финиша. Всем спасибо за внимание.Все исходные коды можно найти здесь.Здравая критика приветствуется.

© Habrahabr.ru