Содержание статьи
Официальный Android SDK содержит в себе достаточный набор классов для создания полноценного приложения. Но будет ли оно лучшим? Сложно с нуля написать одновременно красивое, стабильное и полезное приложение. Иногда ловишь себя на мысли, что метод findViewById ты вызываешь уже в сотый раз и это тебе уже порядком надоело. Благодаря энтузиастам появляются библиотеки, которые существенно упрощают создание сложных конструкций или избавляют от однотипных действий. Сегодня мы разберемся, в чем именно они нам могут помочь.
WARNING
Теоретически создатель библиотеки может допустить малозаметную, но критическую ошибку или даже намеренно вставить деструктивные функции. Будь внимателен!
Внедрение зависимостей
Паттерны в ООП
Паттерны в ООП — это рекомендации, как писать код, чтобы получился гибкий и масштабируемый проект: уменьшать зависимость объектов друг от друга, создавать универсальные классы и так далее. Рекомендую ближе познакомиться с паттернами, прочитав книгу Эрика и Элизабет Фримен «Паттерны проектирования».
Построение многих популярных библиотек основано на использовании ООП-паттерна «Внедрение зависимостей» (Dependecy Injection). Довольно часто требуется создать объект A, для функционирования которого нужен какой-то другой объект B. Возникшая ситуация называется зависимостью, она приводит к дублированию кода и усложняет разработку: как поменять объект B так, чтобы объект A работал?
Решить проблему можно, используя DI-паттерн. В этом случае объекты связываются с помощью графа зависимостей уже в процессе компиляции программы. От нас же требуется создать абстракции таких классов и указать с помощью аннотаций, куда именно объекты этих классов должны быть добавлены. Рассмотрим все это на практике.
Хакер #202. Скажи нет большому брату!
Dagger 2
Библиотека Dagger 2 предоставляет инструменты для самостоятельной реализации DI-паттерна. Создадим приложение, которое будет загружать из сети какие-либо данные, а затем их обрабатывать.
За загрузку будет отвечать класс ImgLoader:
public class ImgLoader {
Bitmap image;
public ImgLoader(String url)
{loadImage(url);}
public Bitmap getImage()
{return image;}
...
}
Теперь создадим класс ImgModifier для дальнейшей обработки данных:
public class ImgModifier {
private Bitmap image;
@Inject
public ImgModifier(ImgLoader loader)
{image=loader.getImage();}
public String perfomModify()
{return "modify made";}}
У нас получилась классическая ситуация, когда объекту ImgModifier для работы требуется другой объект. Аннотацией @Inject мы запрашиваем (то есть добавляем в граф новую зависимость) у библиотеки объект ImgLoader. Где же мы его возьмем? Он будет создан в классе с аннотацией @Module:
@Module
public class ImgModule {
private String urlInt;
public ImgModule(String urlInt)
{this.urlInt=urlInt;}
Следующими аннотациями говорим библиотеке, что для графа зависимостей требуется создать (@Provide) по одному экземпляру (@Singletone) объектов ImgLoader и ImgModifier.
@Provides @Singleton
ImgLoader imgLoader()
{return new ImgLoader(urlInt);}
@Provides @Singleton
ImgModifier provideModifier()
{return new ImgModifier( new ImgLoader(urlInt));}}
Теперь нужно связать объявленные классы, граф зависимостей и созданные объекты. С помощью следующих аннотаций мы создаем интерфейс, в котором указываем (@Component), что для объекта класса ImgModifier требуется один экземпляр класса ImgModule.
@Singleton
@Component(modules={ImgModule.class})
public interface ImgComponent {ImgModifier myDI();}
Итак, DI-паттерн реализован! Теперь создаем объект ImgComponent. Поскольку зависимости разрешатся уже на этапе компиляции, его конструктор будет называться Dagger[Заданноенамиимя].
ImgComponent component = DaggerImgComponent.builder()
.imgModule(new ImgModule("URL")).build();
А теперь — результат наших трудов:
ImgModifier imgModifier=component.myDI();
Благодаря библиотеке мы одной строкой создали несколько объектов и разрешили все зависимости. При долгом развитии проекта зависимости между классами будут мешать все больше. Держи в уме рассмотренный паттерн, и рефакторинг кода будет не столь болезненным.
WARNING
Следи за обновлениями: могут появиться как новые возможности, так и заплатки от серьезных багов.
ButterKnife
Возможно, поначалу тебе может показаться, что DI-паттерны — не для тебя :). Но это не так! Ты увидишь, что он может пригодиться и в более обыденных ситуациях. На очереди библиотека ButterKnife, которая поможет в работе с объектами класса View. Теперь нет нужды каждый раз вызывать метод findViewById, достаточно короткого объявления:
@Bind(R.id.imageView) ImageView image;
@Bind(R.id.textView) TextView name;
...
Для последующего использования достаточно вызвать метод ButterKnife.bind(context)
. Так же легко теперь обработать события onClick, onLongClick и подобные:
@OnClick(R.id.button)
public void submit() {...}
Здесь разрешение зависимостей тоже устроено с помощью рассмотренного паттерна, однако большую часть за нас уже сделала библиотека ButterKnife.
REST-архитектура
REST расшифровывается как передача репрезентативного состояния (Representational State Transfer). Этот термин ввел в обиход Рой Филдинг (Roy Fielding), один из основателей протокола HTTP. В широком смысле REST-приложение представляет собой клиент-серверную архитектуру, в которой один из модулей (сервер) получает запрос от другого (клиент), где уже содержится вся необходимая для ответа информация. Таким образом, серверу нет необходимости хранить у себя какие-либо данные о клиенте. Эту умную фразу можно запомнить для будущих собеседований :), а мы тем временем перейдем к сути. На сегодняшний день, говоря о REST-архитектуре, подразумевают организацию эффективного сетевого обмена. В прошлых статьях мы использовали стандартный Android SDK, но в больших приложениях есть смысл воспользоваться библиотеками.
Retrofit
Пожалуй, самая популярная библиотека для организации RESTful-запросов.
С помощью Java-аннотаций возможно легко и прозрачно формировать параметры запросов: заголовки, тело, адрес получателя и прочее. Имеется поддержка синхронных, асинхронных запросов, встроенный разборщик XML и JSON.
Посмотрим, как нам получить прогноз погоды за окном с помощью этой библиотеки. Для этого нам нужно будет осуществить GET-запрос к JSON-странице с сайта openweathermap.org и распарсить массив полученных данных. Первым делом сформируем класс, в который будут помещаться полученные данные. Прогноз погоды имеет следующую структуру:
{
...
"weather":[
{
"id":300,
...,
"description":"light intensity drizzle",
"icon":"09n"
}
...
]
}
Нам нужно получить значение элемента description JSON-объекта weather. Для этого создадим свой класс, в который Retrofit самостоятельно поместит значения.
Укажем, что все объекты weather нужно помещать в список weatherData:
public class WeatherModule {
...
@SerializedName("weather")
@Expose
private List<Weather> weatherData;
...
Теперь создадим класс, в который непосредственно и будут заноситься данные:
public class Weather{
@SerializedName("description")@Expose
String description;
...
Теперь нужно сформировать GET-запрос. Для этого создадим интерфейс WeatherApi, в котором опишем, какой именно запрос мы хотим отправить на сервер:
public interface WeatherApi {
@GET("/data/2.5/weather")
Сам запрос задается аннотацией. Чтобы получить прогноз, нужно два параметра: город и ID разработчика. Они попадут в сформированный GET-запрос позже.
void fetchData(@Query("q") String city, @Query("appid") String appid,Callback<WeatherModule> response);
И это практически все! Теперь только нужно создать RestAdapter и указать ему интерфейс с параметрами запроса.
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://api.openweathermap.org").build();
wApi=restAdapter.create(WeatherApi.class);
Теперь можно отправить запрос и обработать полученные данные. Чтобы не блокировать основной поток, сделаем загрузку данных асинхронной. Переопределим два метода, один из которых будет вызван после загрузки данных:
wApi.fetchData("City_ID", "dev_ID", new Callback<WeatherModule>() {
@Override
public void success(WeatherModule weatherModule, Response response) {
Log.v("Current weather:", " " + weatherModule.getDescription());
}
@Override
public void failure(RetrofitError error) {}
});
В несколько строчек нам удалось реализовать создание сетевого соединения, формирование HTTP-запроса, его отправку и обработку ответа сервера. Впечатляет, правда?
INFO
Ищешь альтернативу? Попробуй вбить в Гугл «название_библиотеки vs», наверняка что-то найдется.
Picasso
Еще одна популярная задача — работа с изображениями. Предположим, нам нужно получить (из сети или файловой системы) какое-то изображение, уменьшить его в размерах, повернуть и отобразить на экране. Без использования подручных средств это можно считать хорошей задачей для целого рабочего дня... Посмотрим, за сколько мы справимся вместе с библиотекой Picasso.
Picasso.with(this)
.load("URL").rotate(90).resize(50,50)
.into(image);
Загружать ли каждый раз изображение заново? Как адаптировать его размер? Для всего этого достаточно подключить Picasso, она аккумулирует в себе весь процесс: формирование запроса, кеширование, изменение размера — и все это с минимальными затратами ресурсов ОС.
AndroidViewAnimations
Иногда интерфейс пользователя не удается сделать на сто процентов интуитивным. В такой ситуации всегда есть риск потерять клиента — зачем долго разбираться, если в Google Play полно аналогов? Тогда следует обратить внимание пользователя на какой-нибудь элемент программы с помощью анимации. Библиотека AndroidViewAnimations «оживляет» элементы класса View. Для примера заставим поле TextView мигать в течение 700 мс.
YoYo.with(Techniques.Flash).duration(700).playOn(findViewById(R.id.textView));
Класс Techniques содержит множество вариантов анимации на все случаи жизни, главное — знать меру и вовремя остановиться.
ActiveAndroid
Теперь облегчим себе работу с базами данных. ActiveAndroid реализует так называемое объектно-реляционное отображение — способ, позволяющий связать объектную модель данных (ООП, используемое в Java) с реляционной (базой данных). Каждая таблица тут представляется как класс, а поле — как элементы класса. Таким образом, мы можем работать с содержимым базы данных как с объектами ООП.
Создадим таблицу Artists с полями Name и Bio. В поле Bio будет содержаться информация о названии фильмов (Name) и годе выпуска:
(DateRelease).
@Table(name = "Artist")
public class Artist extends Model {
@Column(name = "Name") public String name;
@Column(name = "Bio",..)
public MovieData movieData;
public Artist()
{super();}
Поскольку информация о фильме может быть подробной, сохраним ее в объекте MovieData.
@Table(name = "MovieData")
public class MovieData extends Model {
@Column(name = "name")
public String name;
@Column(name="date" )
public int date;
public MovieData()
{super();}
}
Все, наша база данных готова. Теперь заполним ее:
MovieData terminator = new MovieData();
terminator.date=1984;
terminator.name="The Terminator";
terminator.save();
Похожим образом добавим информацию об артисте:
Artist arnold = new Artist();
arnold.name="Arnold Schwarzenegger";
arnold.movieData=terminator;
arnold.save();
Вот так будет выглядеть выборка всех артистов, участвовавших в фильме, если известно только его название. Для начала получим сам объект класса MovieData, о котором идет речь:
public static List<Artist> getArtists(String movieName){
MovieData movieData = new Select()
.from(MovieData.class)
.where("name = ?", movieName)
.executeSingle();
Дальше воспользуемся встроенной командой getId, которая вернет уникальный автоматически созданный идентификатор для каждого элемента таблицы. По этому идентификатору уже получим всех артистов фильма.
List<Artist> lArtist = new Select()
.from(Artist.class)
.where("Bio = ?", movieData.getId())
.execute();
return lArtist;
Да, при сложных конструкциях все равно придется опускаться до реляционной модели, но для быстрой организации хранения данных библиотека серьезно тебя выручит.
Заключение
Теперь в твоем арсенале есть полезный набор инструментов, который поможет в сжатые сроки создать хороший продукт. Уверен, проведя пятнадцать минут за чтением этой статьи, ты сэкономишь часы при последующей разработке. В мире вообще мало уникального, поэтому зачастую лучше использовать готовое решение, а не изобретать велосипед.
В рамках одной статьи не раскроешь весь потенциал библиотек, но я уверен, что интерес у тебя появился и с остальными возможностями ты ознакомишься самостоятельно. Если останутся какие-то вопросы, как всегда — готов на них ответить через почту. Удачи!