Продолжаем разбирать библиотеки от 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 или подключили ее неправильно.