Статьи

Отправка данных с помощью HTTP-клиента Retrofit 2 для Android

Конечный продукт
Что вы будете создавать

Retrofit — это безопасный для типов HTTP-клиент для Android и Java. Модернизация позволяет легко подключаться к веб-службе REST путем перевода API в интерфейсы Java. В этом уроке я покажу вам, как использовать одну из самых популярных и часто рекомендуемых библиотек HTTP, доступных для Android.

Эта мощная библиотека позволяет легко использовать данные JSON или XML, которые затем анализируются в простые старые объекты Java (POJO). Запросы GET , POST , PUT , PATCH и DELETE могут быть выполнены.

Как и большинство программного обеспечения с открытым исходным кодом, Retrofit был построен поверх некоторых других мощных библиотек и инструментов. За кулисами Retrofit использует OkHttp (от того же разработчика) для обработки сетевых запросов. Кроме того, Retrofit не имеет встроенного конвертера JSON для анализа объектов JSON в Java. Вместо этого он поддерживает следующие библиотеки JSON Converter для этого:

  • Gson: com.squareup.retrofit:converter-gson
  • Джексон: com.squareup.retrofit:converter-jackson
  • Моши: com.squareup.retrofit:converter-moshi

Для буферов протокола Retrofit поддерживает:

  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Провод: com.squareup.retrofit2:converter-wire

А для XML Retrofit поддерживает:

  • Простая структура: com.squareup.retrofit2:converter-simpleframework

Разработка собственной безопасной HTTP-библиотеки для взаимодействия с REST API может быть очень сложной задачей: вам приходится иметь дело со многими аспектами, такими как создание соединений, кэширование, повторение неудачных запросов, многопоточность, анализ ответов, обработка ошибок и многое другое. С другой стороны, дооснащение — это хорошо спланированная, документированная и протестированная библиотека, которая сэкономит вам много драгоценного времени и головной боли.

В этом руководстве я объясню, как использовать Retrofit 2 для обработки сетевых запросов, создав простое приложение, которое будет выполнять запросы POST , PUT (для обновления сущностей) и DELETE . Я также покажу вам, как интегрироваться с RxJava и как отменять запросы. Мы будем использовать API, предоставляемый JSONPlaceholder — это поддельный онлайн REST API для тестирования и создания прототипов.

Прочтите мою предыдущую статью «Начало работы с HTTP- клиентом Retrofit 2» , чтобы узнать, как выполнять запросы GET и как интегрировать Retrofit с RxJava.

  • Android SDK
    Начало работы с HTTP-клиентом Retrofit 2

MainActivity Android Studio и создайте новый проект с пустым действием MainActivity .

создать новую пустую активность

После создания нового проекта объявите следующие зависимости в вашем build.gradle . Зависимости включают библиотеку Retrofit, а также библиотеку Google Gson для преобразования JSON в POJO (простые старые объекты Java), а также интеграцию Retrofit Gson.

1
2
3
4
5
6
// Retrofit
compile ‘com.squareup.retrofit2:retrofit:2.1.0’
 
// JSON Parsing
compile ‘com.google.code.gson:gson:2.6.1’
compile ‘com.squareup.retrofit2:converter-gson:2.1.0’

Убедитесь, что вы синхронизируете свой проект после добавления зависимостей.

Для выполнения сетевых операций нам необходимо включить разрешение INTERNET в манифест приложения: AndroidManifest.xml .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version=»1.0″ encoding=»utf-8″?>
<manifest xmlns:android=»http://schemas.android.com/apk/res/android»
          package=»com.chikeandroid.retrofittutorial2″>
 
    <uses-permission android:name=»android.permission.INTERNET» />
     
    <application
            android:allowBackup=»true»
            android:icon=»@mipmap/ic_launcher»
            android:label=»@string/app_name»
            android:supportsRtl=»true»
            android:theme=»@style/AppTheme»>
        <activity android:name=».PostActivity»>
            <intent-filter>
                <action android:name=»android.intent.action.MAIN»/>
 
                <category android:name=»android.intent.category.LAUNCHER»/>
            </intent-filter>
        </activity>
    </application>
     
</manifest>

Мы собираемся создавать модели автоматически из данных ответов JSON, используя очень полезный инструмент: jsonschema2pojo . Мы хотели бы сделать запрос POST (создать новый ресурс) на API. Но прежде чем выполнить этот запрос, нам нужно знать ответ JSON, который мы должны ожидать, когда он будет выполнен успешно, чтобы Retrofit мог проанализировать ответ JSON и десериализовать его для объектов Java. Согласно API , если мы отправим следующие данные в запросе POST :

1
2
3
4
5
data: {
    title: ‘foo’,
    body: ‘bar’,
    userId: 1
}

Мы должны получить следующий ответ:

1
2
3
4
5
6
{
  «title»: «foo»,
  «body»: «bar»,
  «userId»: 1,
  «id»: 101
}

Сопоставьте данные JSON с Java

Скопируйте пример ответа из предыдущего раздела. Теперь посетите jsonschema2pojo и вставьте ответ JSON в поле ввода. Выберите тип источника JSON , стиль аннотации Gson , снимите флажок Разрешить дополнительные свойства и измените имя класса с Пример на Пост .

вход jsonschema2pojo

Затем нажмите кнопку « Просмотр» , чтобы сгенерировать объекты Java.

вывод jsonschema2pojo

Вам может быть интересно, что @SerializedName аннотации @SerializedName и @Expose в этом сгенерированном коде! Не волнуйся, я все объясню!

Аннотация @SerializedName необходима Gson для сопоставления ключей JSON с полями объектов Java.

1
2
3
@SerializedName(«userId»)
@Expose
private Integer userId;

В этом случае ключ userId JSON сопоставляется с полем класса userId . Но обратите внимание, что, поскольку они одинаковы, нет необходимости включать аннотацию @SerializedName в поле, потому что Gson отобразит ее автоматически для нас.

Аннотация @Expose указывает, что член класса должен быть @Expose для сериализации или десериализации JSON.

Теперь вернемся к Android Studio. Создайте новый подпакет внутри main пакета и назовите его data . Внутри нового пакета создайте другой пакет и назовите его model . Внутри этого пакета создайте новый класс Java и назовите его Post . Теперь скопируйте класс Post , созданный jsonschema2pojo, и вставьте его в класс Post вы создали.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.chikeandroid.retrofittutorial2.data.model;
 
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
 
public class Post {
 
    @SerializedName(«title»)
    @Expose
    private String title;
    @SerializedName(«body»)
    @Expose
    private String body;
    @SerializedName(«userId»)
    @Expose
    private Integer userId;
    @SerializedName(«id»)
    @Expose
    private Integer id;
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
    
    public String getBody() {
        return body;
    }
 
    public void setBody(String body) {
        this.body = body;
    }
 
    public Integer getUserId() {
        return userId;
    }
 
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
 
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
     
    @Override
    public String toString() {
        return «Post{» +
                «title=’» + title + ‘\» +
                «, body=’» + body + ‘\» +
                «, userId=» + userId +
                «, id=» + id +
                ‘}’;
    }
}

Помимо методов получения и установки, я также включил метод toString() . (В Intellij вы можете использовать команду Generate, чтобы упростить это: Alt-Insert в Windows или Command-N в macOS.)

Чтобы отправлять сетевые запросы к RESTful API с Retrofit, нам нужно создать экземпляр с помощью класса Retrofit Builder и настроить его с помощью базового URL.

Создайте новый подпакет внутри пакета data и назовите его remote . Теперь внутри этого пакета создайте класс Java и назовите его RetrofitClient . Этот класс создаст синглтон Retrofit в методе getClient(String baseUrl) и вернет его вызывающей стороне.

Как я упоминал ранее, Retrofit нужен базовый URL для создания своего экземпляра, поэтому мы передадим ему URL при вызове RetrofitClient.getClient(String baseUrl) . Затем этот URL будет использоваться для построения экземпляра в строке 12. Мы также указываем нужный нам JSON-конвертер (Gson) в строке 13.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package com.chikeandroid.retrofittutorial2.data.remote;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
 
public class RetrofitClient {
 
    private static Retrofit retrofit = null;
 
    public static Retrofit getClient(String baseUrl) {
        if (retrofit==null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

Внутри удаленного пакета создайте интерфейс и назовите его APIService . Этот интерфейс содержит методы, которые мы собираемся использовать для выполнения HTTP-запросов, таких как POST , PUT и DELETE . Начнем с запроса POST .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package com.chikeandroid.retrofittutorial2.data.remote;
import com.chikeandroid.retrofittutorial2.data.model.Post;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
 
public interface APIService {
 
    @POST(«/posts»)
    @FormUrlEncoded
    Call<Post> savePost(@Field(«title») String title,
                        @Field(«body») String body,
                        @Field(«userId») long userId);
}

Глядя на класс APIService , у нас есть метод с именем savePost() . Сверху метода находится аннотация @POST , которая указывает, что мы хотим выполнить запрос POST при вызове этого метода. Значением аргумента для аннотации @POST является конечная точка, то есть /posts . Таким образом, полный URL будет http://jsonplaceholder.typicode.com/posts .

Хорошо, а как насчет @FormUrlEncoded ? Это будет указывать на то, что у запроса будет свой тип MIME (поле заголовка, которое определяет формат тела HTTP-запроса или ответа), установленный на application/x-www-form-urlencoded а также что его имена и значения будут UTF-8 кодируется перед URI-кодированием. @Field("key") с именем параметра должна соответствовать имени, @Field("key") API. Модернизация неявно преобразует значения в строки, используя String.valueOf(Object) , и строки затем формируются в URL-кодированном виде. null значения игнорируются.

Например, вызов APIService.savePost("My Visit To Lagos", "I visited...", 2) выдает тело запроса с title=My+Visit+To+Lagos&body=I+visited...&userId=2 .

Мы также можем использовать аннотацию @Body для параметра метода службы вместо указания тела запроса в форме формы с несколькими отдельными полями. Объект будет сериализован с использованием Converter экземпляра Retrofit указанного при создании. Это используется только при выполнении операции POST или PUT .

1
2
3
@POST(«/posts»)
@FormUrlEncoded
Call<Post> savePost(@Body Post post);

Мы собираемся создать служебный класс. Поэтому создайте класс в data.remote и назовите его ApiUtils . Этот класс будет иметь базовый URL-адрес в качестве статической переменной, а также предоставит интерфейс APIService с помощью статического метода getAPIService() для остальной части нашего приложения.

01
02
03
04
05
06
07
08
09
10
11
12
13
package com.chikeandroid.retrofittutorial2.data.remote;
 
public class ApiUtils {
 
    private ApiUtils() {}
 
    public static final String BASE_URL = «http://jsonplaceholder.typicode.com/»;
 
    public static APIService getAPIService() {
 
        return RetrofitClient.getClient(BASE_URL).create(APIService.class);
    }
}

Убедитесь, что в конце базового URL-адреса указан символ / .

Файл activity_main.xml — это макет нашей MainActivity . Этот макет будет иметь одно поле для редактирования текста для заголовка сообщения и другое для тела сообщения. Также имеется кнопка для отправки сообщения в API.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version=»1.0″ encoding=»utf-8″?>
<LinearLayout
        xmlns:android=»http://schemas.android.com/apk/res/android»
        xmlns:tools=»http://schemas.android.com/tools»
        android:id=»@+id/activity_post»
        android:orientation=»vertical»
        android:layout_width=»match_parent»
        android:layout_height=»match_parent»
        android:paddingLeft=»@dimen/activity_horizontal_margin»
        android:paddingRight=»@dimen/activity_horizontal_margin»
        android:paddingTop=»@dimen/activity_vertical_margin»
        android:paddingBottom=»@dimen/activity_vertical_margin»
        tools:context=»com.chikeandroid.retrofittutorial2.AddEditPostActivity»>
 
    <TextView
            android:layout_width=»match_parent»
            android:layout_height=»wrap_content»
            android:gravity=»center_horizontal»
            android:textAppearance=»@style/TextAppearance.AppCompat.Title»
            android:text=»@string/title_enter_post»/>
    <EditText
            android:id=»@+id/et_title»
            android:layout_marginTop=»18dp»
            android:layout_width=»match_parent»
            android:layout_height=»wrap_content»
            android:hint=»@string/hint_title»/>
 
    <EditText
            android:id=»@+id/et_body»
            android:lines=»4″
            android:layout_width=»match_parent»
            android:layout_height=»wrap_content»
            android:hint=»@string/hint_body»/>
 
    <Button
            android:id=»@+id/btn_submit»
            android:layout_marginTop=»18dp»
            android:layout_width=»match_parent»
            android:layout_height=»wrap_content»
            android:background=»@color/colorAccent»
            android:textColor=»@android:color/white»
            android:text=»@string/action_submit»/>
 
    <TextView
            android:id=»@+id/tv_response»
            android:layout_marginTop=»35dp»
            android:visibility=»gone»
            android:layout_width=»match_parent»
            android:layout_height=»wrap_content»/>
     
</LinearLayout>

В onCreate() в MainActivity мы инициализируем экземпляр интерфейса APIService (строка 14). Мы также инициализируем поля EditText и кнопку отправки, которая при нажатии sendPost() метод sendPost() (строка 22).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private TextView mResponseTv;
private APIService mAPIService;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
 
    final EditText titleEt = (EditText) findViewById(R.id.et_title);
    final EditText bodyEt = (EditText) findViewById(R.id.et_body);
    Button submitBtn = (Button) findViewById(R.id.btn_submit);
    mResponseTv = (TextView) findViewById(R.id.tv_response);
 
    mAPIService = ApiUtils.getAPIService();
 
    submitBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            String title = titleEt.getText().toString().trim();
            String body = bodyEt.getText().toString().trim();
            if(!TextUtils.isEmpty(title) && !TextUtils.isEmpty(body)) {
                sendPost(title, body);
            }
        }
    });
}

В sendPost(String, String) в классе MainActivity мы передали заголовок и тело сообщения этому методу. Этот метод будет вызывать наш метод интерфейса службы API savePost(String, String) , задачей которого является выполнение запроса POST отправляющего заголовок и тело в API. Метод showResponse(String response) отобразит ответ на экране.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void sendPost(String title, String body) {
mAPIService.savePost(title, body, 1).enqueue(new Callback<Post>() {
    @Override
    public void onResponse(Call<Post> call, Response<Post> response) {
 
        if(response.isSuccessful()) {
            showResponse(response.body().toString());
            Log.i(TAG, «post submitted to API.» + response.body().toString());
        }
    }
 
    @Override
    public void onFailure(Call<Post> call, Throwable t) {
        Log.e(TAG, «Unable to submit post to API.»);
    }
});
}
 
public void showResponse(String response) {
    if(mResponseTv.getVisibility() == View.GONE) {
        mResponseTv.setVisibility(View.VISIBLE);
    }
    mResponseTv.setText(response);
}

Наш метод savePost(String, String) mAPIService savePost(String, String) вернет экземпляр Call которого есть метод с именем enqueue(Callback<T> callback) .

enqueue() асинхронно отправляет запрос и уведомляет ваше приложение обратным вызовом, когда возвращается ответ. Поскольку этот запрос асинхронный, Retrofit обрабатывает выполнение в фоновом потоке, чтобы основной поток пользовательского интерфейса не блокировался и не мешал.

Чтобы использовать метод enqueue() , вы должны реализовать два метода обратного вызова: onResponse() и onFailure() . Только один из этих методов будет вызван в ответ на данный запрос.

  • onResponse() : вызывается для полученного ответа HTTP. Этот метод вызывается для ответа, который можно правильно обработать, даже если сервер возвращает сообщение об ошибке. Поэтому, если вы получите код состояния 404 или 500, этот метод все равно будет вызываться. Чтобы получить код состояния для обработки ситуаций на их основе, вы можете использовать метод response.code() . Вы также можете использовать метод isSuccessful() чтобы выяснить, находится ли код состояния в диапазоне 200-300, что указывает на успех.
  • onFailure() : вызывается, когда при подключении к серверу возникло сетевое исключение или произошло непредвиденное исключение при обработке запроса или обработке ответа.

Для выполнения синхронного запроса вы можете использовать метод execute() в экземпляре Call . Но имейте в виду, что синхронные методы в основном / пользовательском интерфейсе будут блокировать любые действия пользователя. Поэтому не выполняйте синхронные методы в основном потоке Android / пользовательском интерфейсе! Вместо этого запустите их в фоновом потоке.

RxJava был интегрирован в Retrofit 1 по умолчанию, но в Retrofit 2 вам необходимо включить некоторые дополнительные зависимости. Модификация поставляется с адаптером по умолчанию для выполнения экземпляров Call . Таким образом, вы можете изменить механизм выполнения Retrofit, включив в него RxJava, включив RxJava CallAdapter . Вот эти шаги:

Добавьте зависимости.

1
2
3
compile ‘io.reactivex:rxjava:1.1.6’
compile ‘io.reactivex:rxandroid:1.2.1’
compile ‘com.squareup.retrofit2:adapter-rxjava:2.1.0’

Добавьте новый CallAdapter RxJavaCallAdapterFactory.create() при создании экземпляра Retrofit (строка 5).

01
02
03
04
05
06
07
08
09
10
public static Retrofit getClient(String baseUrl) {
    if (retrofit==null) {
        retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
    return retrofit;
}

Обновите метод APIService savePost(String title, String body, String userId) чтобы он стал Observable.

1
2
3
4
5
@POST(«/posts»)
@FormUrlEncoded
Observable<Post> savePost(@Field(«title») String title,
                          @Field(«body») String body,
                          @Field(«userId») long userId);

При выполнении запросов наш анонимный подписчик отвечает на поток наблюдаемой, который генерирует события, в нашем случае Post . Затем метод onNext вызывается, когда наш подписчик получает какое-либо событие, которое затем передается в наш showResponse(String response) .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public void sendPost(String title, String body) {
 
    // RxJava
    mAPIService.savePost(title, body, 1).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<Post>() {
                @Override
                public void onCompleted() {
 
                }
 
                @Override
                public void onError(Throwable e) {
 
                }
 
                @Override
                public void onNext(Post post) {
                    showResponse(post.toString());
                }
            });
}

Чтобы узнать больше о RxJava и RxAndroid, ознакомьтесь с разделом Начало работы с ReactiveX на Android от Ашрафа Хатибелагала.

  • Android
    Начало работы с ReactiveX на Android
    Ашраф Хатхибелагал

На этом этапе вы можете запустить приложение и нажать кнопку отправки, когда вы ввели заголовок и текст. Ответ от API будет показан ниже кнопки отправки.

окончательный скриншот приложения

Теперь, когда мы знаем, как выполнить запрос POST , давайте посмотрим, как мы можем выполнить запрос PUT который обновляет сущности. Добавьте следующий новый метод в класс APIService .

1
2
3
4
5
6
@PUT(«/posts/{id}»)
@FormUrlEncoded
Call<Post> updatePost(@Path(«id») long id,
                      @Field(«title») String title,
                      @Field(«body») String body,
                      @Field(«userId») long userId);

Чтобы обновить сообщение из API, у нас есть конечная точка /posts/{id} где {id} является заполнителем для идентификатора сообщения, которое мы хотим обновить. Аннотация @Path — это именованная замена в сегменте пути URL {id} . Помните, что значения преобразуются в строку с использованием String.valueOf(Object) и закодированного URL-адреса. Если значение уже закодировано, вы можете отключить кодировку URL следующим образом: @Path(value="name", encoded=true) .

Давайте также посмотрим, как выполнить запрос DELETE . Используя API-интерфейс JSONPlaceholder , для удаления ресурса публикации требуется конечная точка /posts/{id} с методом HTTP DELETE . Возвращаясь к нашему интерфейсу APIService , нам просто нужно включить метод deletePost() который будет его выполнять. Мы передаем id сообщения методу, и он заменяется в сегменте пути URL {id} .

1
2
@DELETE(«/posts/{id}»)
Call<Post> deletePost(@Path(«id») long id);

Допустим, вы хотите дать своим пользователям возможность отменить или отменить запрос. Это очень легко сделать в Retrofit. В классе Retrofit Call есть метод cancel() который будет делать именно это (строка 32 ниже). Этот метод вызовет метод onFailure() в onFailure() .

Этот метод может быть вызван, например, если нет подключения к Интернету или когда произошло непредвиденное исключение при создании запроса или обработке ответа. Чтобы узнать, был ли запрос отменен, используйте метод isCanceled() в классе Call (строка 20).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private Call<Post> mCall;
 
 
public sendPost(String title, String body) {
    mCall = mAPIService.savePost(title, body, 1);
    mCall.enqueue(new Callback<Post>() {
        @Override
        public void onResponse(Call<Post> call, Response<Post> response) {
 
            if(response.isSuccessful()) {
                showResponse(response.body().toString());
                Log.i(TAG, «post submitted to API.» + response.body().toString());
            }
        }
 
        @Override
        public void onFailure(Call<Post> call, Throwable t) {
 
            if(call.isCanceled()) {
                Log.e(TAG, «request was aborted»);
            }else {
                Log.e(TAG, «Unable to submit post to API.»);
            }
            showErrorMessage();
 
        }
    });
}
 
public void cancelRequest() {
    mCall.cancel();
}

В этом уроке вы узнали о Retrofit: почему вы должны его использовать и как интегрировать его в ваш проект для выполнения запросов POST , PUT , DELETE и отмены. Вы также узнали, как интегрировать RxJava с ним. В моем следующем сообщении об использовании Retrofit я покажу вам, как загружать файлы.

Чтобы узнать больше о модернизации, обратитесь к официальной документации . И ознакомьтесь с некоторыми другими нашими учебными пособиями и курсами по разработке Android здесь на Envato Tuts +!

  • Анимируйте ваше приложение для Android

  • Практический параллелизм на Android с HaMeR

  • Создание функциональных приложений для Android в Kotlin: начало работы

  • Перенос приложения Android на дизайн материалов