В процессе работы над первой и второй частью статьи, я неоднократно натыкался на упоминание разнообразных библиотек компании Square, которые народ активно использует при реализации REST-клиентов. Приглядимся к этим библиотекам поближе.
Следующая часть статьи будет посвящена реализации тестового проекта на базе RoboSpice + Retrofit + OkHttp.
OkHttp: продвинутый HTTP-клиент
Как известно, под Android имеется два стандартных HTTP-клиента: Apache HTTP Client и HttpURLConnection. Первый оптимален на старых версиях Android (Eclair and Froyo), второй - на более поздних версиях. Библиотека OkHttp - это альтернативный HTTP-клиент, основанный на исходных кодах HttpURLConnection, и реализующий множество дополнительных полезных функций. В частности, в OkHttp:- добавлена поддержка протоколов HTTP 2 (draft), SPDY 3 (draft);
- реализовано автоматическое восстановление соединения, при возникновении распространенных сетевых проблем (например, проблем c прокси-сервером и TLS рукопожатием);
- реализован пул соединений, обеспечивающий повторное использование HTTP и SPDY соединений, за счет чего увеличивается пропускная способность и снижается время ожидания.
Retrofit: безопасный REST-клиент
В общем случае, при выполнении запроса к REST-серверу, требуется выполнить ряд операций:- сформировать URL;
- задать HTTP-заголовки;
- выбрать тип HTTP-запроса;
- сформировать тело HTTP-запроса, т.е. преобразовать Java объект в JSON;
- выполнить запрос, воспользовавшись HTTP-клиентом;
- распарсить результаты запроса - преобразовать полученный JSON в Java объект.
GET /books/1234Этот запрос возвращает JSON вида
{ "id": "1236", "title": "Yellow mist", "author": "Alexander Volkov", "released": "1970" }С помощью Retrofit мы можем описать этот запрос следующим образом:
import retrofit.http.GET; import retrofit.http.Path; import retrofit.http.Query; public class Book { public int id; public String title; public String author; public int released; } public interface IBookSetRestAPI { @GET("/v1/books/{book_id}") Book getBook(@Path("book_id") int book_id); }Выполнить запрос к серверу можно так:
RestAdapter restAdapter = new RestAdapter.Builder() .setServer("http://samplebookssetrestapi.apiary.io") .build(); IBookSetRestAPI rest_api = restAdapter.create(IBookSetRestAPI.class); Book book = rest_api.getBook(136);Каждый запрос к REST-серверу описывается отдельной функцией в интерфейсе
IBookSetRestAPI
. Тип HTTP запроса задается с помощью аннотаций @GET, @POST, @PUT, @DELETE, @HEAD, @PATCH
, параметры URI-шаблона задаются через параметры функции и описываются аннотацией @Path
.
В случае, когда в URL присутствуют переменные, они задаются через аннотацию @Query
, например запросы:
GET /v1/books?limit=10&offset=20 GET /v1/books?limit=10описываются следующим образом:
public interface IBookSetRestAPI { @GET("/v1/books/{book_id}") Book getBook(@Path("book_id") int book_id); @GET("/v1/books") ListBooks getBooks(@Query("limit") int limit); @GET("/v1/books") ListBooks getBooks(@Query("limit") int limit, @Query("offset") int offset); }Разумеется, есть и другие аннотации - для задания заголовков HTTP-запросов, для формирования составных запросов и т.д. Несмотря на то, что аннотации Retrofit по структуре похожи на аннотации JAX-RS, эти наборы аннотаций не совместимы. Причина: аннотации JAX-RS ориентированы на серверную часть, а аннотации Retrofit - на клиентскую.
Парсинг результатов
ФункцияBook getBook(int book_id)
возвращает объект, распарсивание JSON производится автоматически. По умолчанию, для распарсивания используется GSON, т.е. в проект необходимо добавить gson-2.2.4.jar. Библиотека поддерживает кастомизацию конвертации объектов, причем конвертеры XML и Protobuf доступны вместе с библиотекой.
Можно получать результаты в "сыром", нераспарсенном виде. Достаточно определить интерфейс IBookSetRestAPI следующим способом:
import retrofit.client.Response; import retrofit.http.GET; import retrofit.http.Path; import retrofit.http.Query; public interface IBookSetRestAPI { @GET("/v1/books/{book_id}") Response getBook(@Path("book_id") int book_id); @GET("/v1/books") Response getBooks(@Query("limit") int limit); @GET("/v1/books") Response getBooks(@Query("limit") int limit, @Query("offset") int offset); }Объект
Response
дает прямой доступ к содержимому ответа сервера - можно парсить его вручную.
OkHttp и Retrofit
Retrofit выбирает HTTP-клиента следующим образом:- Если OkHttp задействована в проекте, то используется OkHttpClient;
- В противном случае: если приложение работает под Android 2.2 или ниже, используется HttpClient, иначе - HttpURLConnection.
GZip и Retrofit
OkHttp использует Gzip автоматически при условии, что заголовок Accept-Encoding не задан явно. Таким образом, если Accept-Encoding не задан, то:- OkHttp автоматически добавляет "Accept-Encoding: gzip" и отправляет запрос серверу;
- далее, проверяет ответ от сервера: если в заголовках ответа есть "Content-Encoding: gzip", значит данные запакованы;
- запакованные данные OkHttp автоматически распаковывает. В результате, клиент получает Response с распакованным содержимым; если же используется конвертер, то в функцию fromBody конвертера опять же приходят уже распакованные данные.
@Headers("Accept-Encoding: identity") @GET("/v1/books") ListBooks getBooks(); @Headers("Accept-Encoding: gzip") @GET("/v1/books") ListBooks getBooks2();Запрос getBooks будет работать без gzip. Запрос getBooks2 будет поддерживать gzip, однако на выходе пользователь получит запакованные данные (как в Response, так и в функции fromBody конвертера), которые необходимо будет распаковывать вручную.
Синхронное и асинхронное выполнение запросов
Retrofit поддерживает оба варианта. Асинхронное выполнение запросов требует подключения библиотеки RxJava.Зависимости Retrofit
Все зависимости опциональны:- OkHttp - если требуется OkHttpClient;
- GSon - если планируется использовать стандартный GSon конвертер;
- RxJava - если требуется асинхронное выполнение запросов.
Примеры использования Retrofit
Неплохая коллекция примеров.MimeCraft: формирование HTTP-запросов
Еще одна библиотека, MimeCraft, предназначена для формирования тела составных HTTP-запросов и данных веб-формы. Все это умеет делать Retrofit, однако синтаксис немного другой. Пример:- Retrofit:
@FormUrlEncoded @POST("/user/edit") User updateUser(@Field("first_name") String first, @Field("last_name") String last); ... User u = rest_api.updateUser("First", "Last");
- MimeCraft:
FormEncoding fe = new FormEncoding.Builder() .add("first_name", "First") .add("last_name", "Last") .build(); fe.writeBodyTo(outstream);
Везде пишут, что для того, чтобы Retrofit стал использовать gzip, достаточно добавить соответствующий заголовок (так, как описано у вас в статье, либо же с помощью RequestInterceptor), но нигде не описано, как добиться нормального распаковывания ответа сервера.
ОтветитьУдалитьИ я не могу понять, что я делаю не так: без этого заголовка все работает, с ним - парсер получает на вход гзипованую строку, и, естественно, ничего не работает.
В логе это выглядит вот так:
02-04 10:54:01.692: D/retrofit(8656): Transfer-Encoding: chunked
02-04 10:54:01.692: D/retrofit(8656): � ������������ =�An�0 E�byoaǎ!���Ċ Dhխ�L�"�+;Ab� �2]��Dz�&
�f�?�I��<� g ��; [���B�sW� �ƀm'�i
!`T���?�gH1 �F$�L F�1_�ɟ� �:
F��q�p/�' �'� �
9� � �R3-����ŤԂ !��\GuUs)�
02-04 10:54:01.692: D/retrofit(8656): ����~vv�O�Rr�X��S�����������~L$3��q [�U3
���� �հ ����
02-04 10:54:01.692: D/retrofit(8656): <--- END HTTP (226-byte body)
А в случае отсутствия заголовка про gzip, вместо этой абракадабры - нормальный текст.
Вы правы, у меня была ошибка в статье. Спасибо, поправил и написал подробно. Пришлось под отладчиком посмотреть, как именно работает OkHttp. Transparent gzip - прозрачная распаковка запакованного ответа, - используется во всех запросах, у которых нет заголовка Accept-Encoding. В этом случае Accept-Encoding:gzip добавляется OkHttp автоматически. Если же Accept-Encoding указан явно, то transparent gzip отключается и OkHttp выдает наружу запакованные данные, как в вашем случае.
УдалитьСпасибо большое! Я не сообразил подебажить okhttp, и думал, что gzip вообще не используется.
УдалитьСтатья интересная, спасибо. Будете ли вы рассматривать другие библиотеки для работы с сетью? http://android-arsenal.com (Networking)
ОтветитьУдалитьСпасибо большое за статью. Также мого полезной информации можно найти здесь https://amazingcart.us/
ОтветитьУдалить