Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Многие сайты имеют собственные API для удобного доступа к своим данным. На данный момент самый распространённый вариант - это JSON. Также могут встречаться данные в виде XML и других форматов.
Библиотека Retrofit упрощает взаимодействие с REST API сайта, беря на себя часть рутинной работы.
Авторами библиотеки Retrofit являются разработчики из компании "Square", которые написали множество полезных библиотек, например, Picasso, Okhttp, Otto.
Домашняя страница - http://square.github.io/retrofit/
Библиотекой удобно пользоваться для запроса к различным веб-сервисам с командами GET, POST, PUT, DELETE. Может работать в асинхронном режиме, что избавляет от лишнего кода.
В основном вам придётся работать с методами GET и POST. Если вы будет создавать собственный API, то будете использовать и другие команды.
Подключается стандартно.
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
В Retrofit 2.x автоматически подключается библиотека OkHttp и её не нужно прописывать отдельно.
Библиотека может работать с GSON и XML, используя специальные конвертеры, которые следует указать отдельно.
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
Затем в коде конвертер добавляется с помощью метода addConverterFactory().
addConverterFactory(GsonConverterFactory.create()
Список готовых конвертеров:
Также вы можете создать свой собственный конвертер, реализовав интерфейс на основе абстрактного класса Converter.Factory.
Можно подключить несколько конвертеров (порядок важен).
Retrofit retrofit = Retrofit.Builder()
.baseUrl("https://your.api.url/v2/");
.addConverterFactory(ProtoConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
Если вы хотите изменить формат какого-нибудь JSON-объекта, то это можно сделать с помощью GsonConverterFactory.create():
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/base/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
service = retrofit.create(APIService.class);
Базовый URL всегда заканчивается слешем /. Задаётся в методе baseUrl().
Можно указать полный URL в запросе, тогда базовый URL будет проигнорирован:
public interface APIService {
@GET("https://api.sample.com/users/user/list")
Call<Users> getUsers();
}
Для работы с Retrofit понадобятся три класса.
Работу с Retrofit можно разбить на отдельные задачи.
Задача первая - посмотреть на структуру ответа сайта в виде JSON (или других форматов) и создать на его основе Java-класс в виде POJO.
POJO удобнее создавать с помощью готовых веб-сервисов в автоматическом режиме. Либо можете самостоятельно создать класс, если структура не слишком сложная.
В классе часто используются аннотации. Иногда они необходимы, иногда их можно пропустить. В некоторых случаях аннотации помогают избежать ошибок. Список аннотаций зависит от типа используемого конвертера, их список можно посмотреть в соответствующей документации.
Задача вторая - создать интерфейс и указать имя метода. Добавить необходимые параметры, если они требуются.
В интерфейсе задаются команды-запросы для сервера. Команда комбинируется с базовым адресом сайта (baseUrl()) и получается полный путь к странице. Код может быть простым и сложным. Можно посмотреть примеры в документации.
Запросы размещаются в обобщённом классе Call с указанием желаемого типа.
import retrofit2.Call;
public interface APIService {
@POST("list")
Call<Repo> loadRepo();
}
В большинстве случаев вы будете возвращать объект Call<T> с нужным типом, например, Call<User>. Если вас не интересует тип ответа, то можете указать Call<Response>.
Здесь также используются аннотации, но уже от самой библиотеки.
С помощью аннотации указываются веб-команды, а затем Java-метод. Для динамических параметров используются фигурные скобки (users/{user}/repos), в которые подставляются нужные значения.
В самой аннотации используется метод, используемый на сервере, а ниже вы можете указать свой вариант (полезно для соответствия стилю вашего кода.
@GET("get_all_cats") // команда на сервере
List<Cat> getAllCats(); // ваш код
Аннотация | Описание |
---|---|
@GET() | GET-запрос для базового адреса. Также можно указать параметры в скобках |
@POST() | POST-запрос для базового адреса. Также можно указать параметры в скобках |
@Path | Переменная для замещения конечной точки, например, username подставится в {username} в адресе конечной точки |
@Query | Задаёт имя ключа запроса со значением параметра |
@Body | Используется в POST-вызовах (из Java-объекта в JSON-строку) |
@Header | Задаёт заголовок со значением параметра |
@Headers | Задаёт все заголовки вместе |
@Multipart | Используется при загрузке файлов или изображений |
@FormUrlEncoded | Используется при использовании пары "имя/значение" в POST-запросах |
@FieldMap | Используется при использовании пары "имя/значение" в POST-запросах |
@Url | Для поддержки динамических адресов |
Аннотация @Query полезна при запросах с параметрами. Допустим, у сайте есть дополнительный параметр к запросу, который выводит список элементов в отсортированном виде: http://example.com/api/v1/products/cats?sort=desc. Это несложный пример и мы можем поместить запрос с параметром в интерфейс без изменений.
@GET("products/cats?category=5&sort=desc")
Call<Cats> getAllCats();
Если не требуется управлять сортировкой, то её можно оставить в коде и она будет применяться по умолчанию. Но в нашем запросе есть ещё один параметр, который отвечает за категорию котов (домашние, уличные, породистые), которая может меняться в зависимости от логики приложения. Этот параметр можно снабдить аннотацией и программно управлять в коде.
@GET("products/cats?sort=desc")
Call<Cats> getAllCats(@Query("category") int categoryId);
Сортировку мы оставляем как есть, а категорию перенесли в параметры метода под именем categoryId, снабдив аннотацией, с которой параметр будет обращаться на сервер в составе запроса.
Call<Cats> getAllCats() = catAPIService.getAllCats(5);
Запрос получится в виде http://example.com/api/v1/products/cats?sort=desc&category=5.
В одном методе можно указать несколько Query-параметров.
Запрос может иметь изменяемые части пути. Посмотрите на один из примеров запроса для GitHub: /users/:username. Вместо :username следует подставлять конкретные имена пользователей (https://api.github.com/users/alexanderklimov). В таких случаях используют фигурные скобки в запросе, в самоме методе через аннотацию @Path указывается имя, которое будет подставляться в путь.
@GET("/users/{username}")
Call getUser(
@Path("username") String userName
);
Пример аннотации @Headers, которая позволяет указать все заголовки вместе.
@Headers({"Cache-Control: max-age=640000", "User-Agent: My-App-Name"})
@GET("some/endpoint")
Пример аннотации @Multipart при загрузке файлов или картинок:
@Multipart
@POST("some/endpoint")
Call<Response> uploadImage(@Part("description") String description, @Part("image") RequestBody image)
Пример использования аннотации @FormUrlEncoded:
@FormUrlEncoded
@POST("/some/endpoint")
Call<SomeResponse> someEndpoint(@FieldMap Map<String, String> names);
Пример аннотации @Url:
public interface UserService {
@GET
public Call<File> getZipFile(@Url String url);
}
Для синхронного запроса используйте метод Call.execute(), для асинхронного - метод Call.enqueue().
Объект для запроса к серверу создаётся в простейшем случае следующим образом
public static final String BASE_URL = "http://api.example.com/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
В итоге мы получили объект Retrofit, содержащий базовый URL и способность преобразовывать JSON-данные с помощью указанного конвертера Gson.
Далее в его методе create() указываем наш класс интерфейса с запросами к сайту.
UserService userService = retrofit.create(UserService.class);
После этого мы получаем объект Call и вызываем метод enqueue() (для асинхронного вызова) и создаём для него Callback. Запрос будет выполнен в отдельном потоке, а результат придет в Callback в main-потоке.
В результате библиотека Retrofit сделает запрос, получит ответ и производёт разбор ответа, раскладывая по полочкам данные. Вам остаётся только вызывать нужные методы класса-модели для извлечения данных.
Основная часть работы происходит в onResponse(), ошибки выводятся в onFailure() (неправильный адрес сервера, некорректные формат данных, неправильный формат класса-модели и т.п). HTTP-коды сервера (например, 404) не относятся к ошибкам.
Метод onResponse() вызывается всегда, даже если запрос был неуспешным. Класс Response имеет удобный метод isSuccessful() для успешной обработки запроса (коды 200хх). В ошибочных ситуациях вы можете обработать ошибку в методе errorBody() класса ResponseBody.
Другие полезные методы Response.
Можно написать такую конструкцию.
// код 200
if (response.isSuccessful()) {
... // код для успешного случая
} else {
switch(response.code()) {
case 404:
// страница не найдена. можно использовать ResponseBody, см. ниже
break;
case 500:
// ошибка на сервере. можно использовать ResponseBody, см. ниже
break;
}
// или
// Также можете использовать ResponseBody для получения текста ошибки
ResponseBody errorBody = response.errorBody();
try {
mTextView.setText(errorBody.string());
} catch (IOException e) {
e.printStackTrace();
}
}
Для отмены запроса используется метод Call.cancel().
В библиотеку можно внедрить перехватчики для изменения заголовков при помощи класса Interceptor из OkHttp. Сначала следует создать объект перехватчика и передать его в OkHttp, который в свою очередь следует явно подключить в Retrofit.Builder через метод client().
Поддержка перехватчиков/interceptors для обработки заголовков запросов, например, для работы с токенами авторизации в заголовке Authorization.
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Настраиваем запросы
Request request = original.newBuilder()
.header("Accept", "application/json")
.header("Authorization", "auth-token")
.method(original.method(), original.body())
.build();
Response response = chain.proceed(request);
return response;
}
});
Retrofit retrofit = Retrofit.Builder()
.baseUrl("https://your.api.url/v2/");
.client(client)
.build();
Библиотека HttpLoggingInterceptor является частью OkHttp, но поставляется отдельно от неё. Перехватчик следует использовать в том случае, когда вам действительно нужно изучать логи ответов сервера. По сути библиотека является сетевым аналогом привычного LogCat.
Подключаем библиотеку.
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
Подключаем перехватчик к веб-клиенту. Добавляйте его после других перехватчиков, чтобы ловить все сообщения. Существует несколько уровней перехвата данных: NONE, BASIC, HEADERS, BODY. Последний вариант самый информативный, пользуйтесь им осторожно. При больших потоках данных информация забьёт весь экран. Используйте промежуточные варианты.
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
// Только в режиме отладки
if(BuildConfig.DEBUG){
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY );
}
OkHttpClient okClient = new OkHttpClient.Builder()
.addInterceptor(new ResponseInterceptor())
.addInterceptor(loggingInterceptor)
.build();
Сами разработчики библиотеки очень любят реактивное программирование и приложили многие усилия для интеграции с библиотекой RxJava.
// build.gradle
implementation 'com.squareup.retrofit2:adapter-rxjava:2.5.0'
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl);
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
Retrofit 2.x. Примеры для GitHub
Retrofit 2.x. POST, PUT, DELETE
Retrofit 2.x. Конвертер ScalarsConverterFactory и метод POST
Курс валют Центрального Банка России. Retrofit, XML
Retrofit на Kotlin с применением корутины. JSONPlaceholder
TheCatAPI - Cats as a Service, Everyday is Caturday (Retrofit, Kotlin)