Продолжаем разбирать библиотеки от Square. Сегодня мы познакомимся поближе с замечательной библиотекой Retrofit.
Внимание: данная статья немного устарела в связи с выходом библиотеки Retrofit 2, но все же мы советуем ознакомиться с содержанием данного поста, для понимания базовых принципов работы библиотеки. Новый обзор можно почитать здесь.
Замечательна она тем, что:
1) Больше не нужно выводить запросы к API в отдельный поток в коде — Retrofit это сделает за нас.
2) Значительно сокращает длину кода и, соответсвенно, ускоряет разработку
3) Динамически строит запросы
4) Автоматически конвертирует JSON в объекты (используется библиотека Gson)
5) Обрабатывает ошибки
6) Умеет передавать файлы
Вся логика работы библиотека завязана на аннотациях. Благодаря ним можно создавать динамические запросы на сервер.
Описание API
Описание запросов к серверу происходит в интерфейсе. Над каждым методом должна стоять аннотация, с помощью которой Retrofit «узнает», какого типа запрос.
Также с помощью аннотаций можно указывать параметры запроса.
Вот так, например, выглядит описание GET-запроса:
import retrofit.client.Response;
import retrofit.http.GET;
public interface API {
@GET("/v1/users")
Response getUsers();
}
Как видите, нам не нужно указывать адрес сайта, который отправляется запрос, нужно лишь указать расположение самого PHP файла на сервере (позже объясню почему). В классе Response содержится информация о статусе запроса и ответ от сервера.
А вот так выглядит POST-запрос:
import retrofit.client.Response;
import retrofit.http.POST;
public interface API {
@POST("/v1/registration")
Response registerUser();
}
Можно изменять путь к файлу динамически:
@GET("/{version}/users")
Response getUsers(@Path("version") String version);
Retrofit заменит слово «{version}» на, то которое вы передали методу. Сам аргумент должен быть аннотирован словом Path и в скобках нужно указать ключевое слово.
Параметры запроса
Тут тоже нет ничего сложного:
@GET("/v1/users")
Response getUsers(@Query("gender") String gender);
Для того, чтобы задать запросу параметры используется аннотация @Query. Слово указанное в скобках рядом с аннотацией будет ключом, а аннотированый аргумент значением.
Синхронные и асинхронные запросы
У вас есть два способа отправки запроса: синхронный и асинхронный. Для синхронной отправки запроса нужно вынести его в отдельный поток. Пример синхронной отправки запроса:
@GET("/users/{id}")
Response getUserInfo(@Path("id") int id);
В ассинхронном запросе метод ничего не возвращает (void), но обязательно должен принимать на вход объект интерфейса Callback<T>. В качестве параметра интерфейса нужно передать тип возвращаемого объекта. Вот пример ассинхронного запроса:
@GET("/users/{id}")
void getUserInfo(@Path("id") int id, Callback<Response> cb);
Превращаем интерфейс в API
После того, как мы описали все запросы к серверу в интерфейсе нужно передать этот интерфейс специальному классу, который обработает этот интерфейс и сгенерирует код запросов.
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.site.com")//В метод setEndpoint передаем адрес нашего сайта
.build();
API service = restAdapter.create(API.class);
Выше я писал, что при описании запроса адрес сайта указывать не нужно, достаточно указать его в методе setEndpoint и Retrofit сам подставит его в начале адреса запроса
Используем API
Пример асинхронного GET-запроса:
Описываем запросы к API
public interface API {
@GET("/users/{user}")
void getUserInfo(@Path("user") String userName, Callback<Response> cb);
}
Создаем интерфейс для работы с API
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://api.mysite.com")
.build();
API service = restAdapter.create(API.class);
Посылаем запрос
service.getUserInfo("Admin", new Callback<Response>(){
public void success(Response arg0, retrofit.client.Response arg1) {
}
public void failure(RetrofitError arg0) {
}
});
Пример синхронного POST-запроса:
Описываем запросы к API
@FormUrlEncoded
@POST("/reg.php")
Response saveUserInfo(@Field("login") String userName, @Field("password") String userPass);
Создаем интерфейс для работы с API
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://api.mysite.com")
.build();
API api = restAdapter.create(API.class);
Посылаем запрос
Thread t = new Thread(new Runnable(){
@Override
public void run(){
Response r = service.saveUserInfo("Admin", "123456789");
}
});
t.start();
Запрос с большим количеством полей
Если вам нужно послать запрос с большим количеством параметров, то будет лучше использовать Map<String, String> и добавлять параметры туда.
Описываем API:
@GET("/retrofit/vardump.php")
Response searchUser(@QueryMap Map<String, String> parameters);
Посылаем запрос:
Map<String, String> parameters = new HashMap<String, String>();
parameters.put("name", "Alexander");
parameters.put("surname", "Ivanov");
parameters.put("age", "35");
parameters.put("city", "Moscow");
Response response = service.searchUser(parameters);
Получаем строку из Response
Данные полученные от сервера класс Response выдает в виде InputStream, чтобы пользователь сам преобразовал ответ в нужный ему тип данных. Здесь мы рассмотрим пример перевода InputStream в строку:
String stringFromResponse(Response response){
BufferedReader reader = null;
StringBuilder sb = new StringBuilder();
try {
reader = new BufferedReader(new InputStreamReader(
response.getBody().in()));
String line;
try {
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
String result = sb.toString();
return result;
}
Multipart-запросы
Интерфейс API:
@Multipart
@POST("/upload.php")
Response saveUserFile(@Part("userfile") TypedFile file);
Описание Multipart-запроса похоже на описание POST-запроса, только вместо @FormUrlEncoded мы пишем @Multipart. На вход подается объект класса TypedFile с аннотацией @Part, указывающей на имя поля.
Thread t = new Thread(new Runnable(){
@Override
public void run(){
File file = new File("D:\\myfile.mp4");
TypedFile typedFile = new TypedFile("multipart/mixed", file);
Response response = service.saveUserFile(typedFile);
}
});
t.start();
TypedFile — тот же класс File, только с указанием MIME-типа.
Конвертируем ответ в объекты
Класс книги:
public class Book {
String name, author;
int year;
public Book(String name, String author, int year) {
super();
this.year = year;
this.name = name;
this.author = author;
}
public int getYear() {
return year;
}
public String getName() {
return name;
}
public String getAuthor() {
return author;
}
}
В интерфейсе в качестве возвращаемого класса ставим класс Book:
@GET("/books/getbook.php")
Book getBook(@Query("name") String bookName, @Query("author") String bookAuthor);
Возвращаемый текст:
{"name": "Java. The Complete Reference","author": "Herbert Shildt","year": 2015}
Вызываем метод getBook:
Book book = service.getBook("Java. The Complete Reference", "Herbert Shildt");
Для конвертирования JSON в объекты Retrofit использует библиотеку Gson. Она автоматически найдет поля в классе и присвоит им соотвествующий текст. Подробнее про Gson вы можете почитать здесь.
Работа с заголовками
Управление заголовками происходит с помощью аннотаций @Header и @Headers.
Простейший пример использования аннотации @Headers:
@Headers("Cache-Control: max-age=640000")
@GET("/users/userslist/")
List<User> widgetList();
Также можно указывать несколько заголовков:
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("/users/{username}")
User getUser(@Path("username") String username);
Также заголовок можно менять динамически используя аннотацию @Header.
@GET("/user")
void getUser(@Header("Authorization") String authorization, Callback callback)
Слово «Authorization» будет использовано в качестве имени заголовка, а переменная authorization как значение. Если переменная authorization равна null, то заголовок не добавится.
Если какой-нибудь заголовок нужно добавлять в каждый запрос (например User-Agent), то это можно сделать с помощью интерфейса RequestInterceptor. В методе intercept(RequestFacade) мы добавляем нужные нам заголовки.
RequestInterceptor requestInterceptor = new RequestInterceptor() {
@Override
public void intercept(RequestFacade request) {
request.addHeader("User-Agent", "Retrofit-Sample-App");
}
};
Далее вызываем метод setRequestInterceptor у RestAdapter’а и указываем в качестве аргумента созданный нами RequestInterceptor.
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.setRequestInterceptor(requestInterceptor)
.build();
Внимание!
Если у вас возникает ошибка
java.lang.VerifyError: retrofit/converter/GsonConverter
то, скорее всего, вы не подключили библиотеку GSON от Google или подключили ее неправильно.
