什么是翻新?
Retrofit是适用于Android和Java的类型安全的HTTP客户端。 通过将API转换为Java接口,翻新可以轻松连接到REST Web服务。 在本教程中,我将向您展示如何使用可用于Android的最受欢迎的HTTP库之一。
这个功能强大的库使您可以轻松使用JSON或XML数据,然后将其解析为原始Java对象(POJO)。 GET
, POST
, PUT
, PATCH
和DELETE
请求都可以执行。
像大多数开源软件一样,Retrofit建立在其他一些强大的库和工具之上。 在幕后,Retrofit利用OkHttp (来自同一开发人员)来处理网络请求。 此外,Retrofit没有内置的JSON转换器可将JSON解析为Java对象。 相反,它提供了对以下JSON转换器库的支持以处理该问题:
- Gson:
com.squareup.retrofit:converter-gson
- 杰克逊:
com.squareup.retrofit:converter-jackson
- Moshi:
com.squareup.retrofit:converter-moshi
对于协议缓冲区 ,翻新支持:
- 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集成以及如何取消请求。 我们将使用JSONPlaceholder提供的API-这是用于测试和原型制作的虚假在线REST API。
1.创建一个Android Studio项目
启动Android Studio并创建一个名为MainActivity
的空活动的新项目。
2.声明依赖关系
创建新项目后,在build.gradle
声明以下依赖build.gradle
。 依赖项包括Retrofit库以及Google的Gson库,以将JSON转换为POJO(普通的旧Java对象),以及Retrofit的Gson集成。
// 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'
添加依赖项后,确保同步项目。
3.添加Internet权限
为了执行网络操作,我们需要在应用程序清单AndroidManifest.xml中包含INTERNET
权限。
<?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>
4.自动生成模型
我们将利用一个非常有用的工具jsonschema2pojo从JSON响应数据自动创建模型。 我们想在API上发出POST
请求(创建新资源)。 但是在执行此请求之前,我们需要知道成功执行后应该期望的JSON响应,以便Retrofit可以解析JSON响应并将其反序列化为Java对象。 根据API ,如果我们在POST
请求中发送以下数据:
data: {
title: 'foo',
body: 'bar',
userId: 1
}
我们应该得到以下回应:
{
"title": "foo",
"body": "bar",
"userId": 1,
"id": 101
}
将JSON数据映射到Java
复制上一部分中的样本响应数据。 现在访问jsonschema2pojo并将JSON响应粘贴到输入框中。 选择JSON的源类型, Gson的注释样式,取消选中“ 允许其他属性” ,并将类名称从Example更改为Post 。
然后单击“ 预览”按钮以生成Java对象。
您可能想知道@SerializedName
和@Expose
批注在此生成的代码中的作用! 不用担心,我会解释一切!
Gson需要@SerializedName
批注才能将JSON键映射到Java对象字段。
@SerializedName("userId")
@Expose
private Integer userId;
在这种情况下,JSON密钥userId
映射到类字段userId
。 但请注意,由于它们相同,因此无需在字段上包含@SerializedName
批注,因为Gson会自动为我们映射它。
@Expose
批注指示应公开该类成员以进行JSON序列化或反序列化。
将数据模型导入Android Studio
现在让我们回到Android Studio。 在main
包内创建一个新的子包,并将其命名为data
。 在新创建的包中,创建另一个包并将其命名为model
。 在此包中,创建一个新的Java类,并将其命名为Post
。 现在复制由jsonschema2pojo生成的Post
类,并将其粘贴到您创建的Post
类中。
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 +
'}';
}
}
除了getter和setter之外,我还包括toString()
方法。 (在Intellij中,可以使用Generate命令简化此操作:在Windows上为Alt-Insert ,在macOS上为Command-N 。)
5.创建改造实例
要使用Retrofit向RESTful API发出网络请求,我们需要使用Retrofit Builder类创建一个实例,并使用基本URL对其进行配置。
在data
包内创建一个新的子包,并将其命名为remote
。 现在,在此包中,创建一个Java类并将其命名为RetrofitClient
。 此类将在getClient(String baseUrl)
方法中创建Retrofit的单例并将其返回给调用方。
如前所述,Retrofit需要一个基本URL来构建其实例,因此在调用RetrofitClient.getClient(String baseUrl)
时,我们将为其传递一个URL。 然后,该URL将在第12行中用于构建实例。我们还在第13行中指定所需的JSON转换器(Gson)。
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;
}
}
6.创建API接口
在远程包中,创建一个接口,并将其APIService
。 该接口包含我们将用于执行HTTP请求的方法,例如POST
, PUT
和DELETE
。 让我们从POST
请求开始。
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")
注释应与API期望的名称匹配。 Retrofit使用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
注释
我们还可以在服务方法参数上使用@Body
批注,而不用指定具有多个单独字段的表单样式请求主体。 该对象将使用创建期间指定的Retrofit
实例Converter
进行序列化。 仅在执行POST
或PUT
操作时使用。
@POST("/posts")
@FormUrlEncoded
Call<Post> savePost(@Body Post post);
7.创建API实用程序
我们将创建一个实用程序类。 因此,在data.remote
创建一个类,并将其命名为ApiUtils
。 此类将基URL作为静态变量,并且还将通过getAPIService()
静态方法为应用程序的其余部分提供APIService
接口。
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 。
8.创建布局
文件activity_main.xml是MainActivity
的布局。 此布局将具有一个用于文本标题的文本编辑字段,以及一个用于文本正文的文本编辑字段。 它还包括一个按钮,用于将帖子提交到API。
<?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>
9.执行POST请求
在MainActivity
的onCreate()
方法中,我们初始化APIService
接口的实例(第14行)。 我们还初始化了EditText
字段和一个提交按钮,单击该按钮时将调用sendPost()
方法(第22行)。
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);
}
}
});
}
在MainActivity
类的sendPost(String, String)
方法中,我们将帖子的标题和正文传递给了此方法。 该方法将执行我们的API服务接口方法savePost(String, String)
该方法的工作是执行POST
请求,以将标题和正文发送给API。 showResponse(String response)
方法将在屏幕上显示响应。
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);
}
我们的APIService
实例mAPIService
方法savePost(String, String)
将返回一个Call
实例,该实例具有一个名为enqueue(Callback<T> callback)
。
了解enqueue()
enqueue()
异步发送请求,并在响应返回时通过回调通知您的应用。 由于此请求是异步的,因此Retrofit在后台线程上处理执行,以便不会阻塞或干扰主UI线程。
要使用enqueue()
方法,必须实现两个回调方法: onResponse()
和onFailure()
。 这些方法中只有一种会响应给定的请求而被调用。
-
onResponse()
:为收到的HTTP响应调用。 调用此方法是为了即使服务器返回错误消息也可以正确处理响应。 因此,如果状态代码为404或500,则仍将调用此方法。 为了获取状态代码以便您根据情况进行处理,可以使用response.code()
方法。 您还可以使用isSuccessful()
方法找出状态码是否在200-300范围内,表示成功。 -
onFailure()
:在发生与服务器通信的网络异常时,或者在处理请求或处理响应时发生意外异常时调用。
同步请求
要执行同步请求,可以在Call
实例中使用execute()
方法。 但是请注意,主/ UI线程上的同步方法将阻止任何用户操作。 因此,请勿在Android的main / UI线程上执行同步方法! 而是在后台线程上运行它们。
使用RxJava
RxJava默认情况下已集成到Retrofit 1中,但是在Retrofit 2中,您需要包括一些额外的依赖项。 Retrofit附带有用于执行Call
实例的默认适配器。 因此,您可以通过包含RxJava CallAdapter
来将Retrofit的执行机制更改为包含CallAdapter
。 这些步骤是:
第1步
添加依赖项。
compile 'io.reactivex:rxjava:1.1.6'
compile 'io.reactivex:rxandroid:1.2.1'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
第2步
构建改造实例时,添加新的CallAdapter RxJavaCallAdapterFactory.create()
(第5行)。
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。
@POST("/posts")
@FormUrlEncoded
Observable<Post> savePost(@Field("title") String title,
@Field("body") String body,
@Field("userId") long userId);
第4步
在发出请求时,我们的匿名订阅者响应可观察对象的流,该流发出事件,在我们的案例中为Post
。 当订户收到任何事件时,将调用onNext
方法,然后将其传递给我们的showResponse(String response)
方法。
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());
}
});
}
10.测试应用
此时,您可以运行该应用程序,并在输入标题和正文后单击“提交”按钮。 API的响应将显示在“提交”按钮下方。
11.执行一个PUT请求
现在我们知道如何执行POST
请求,让我们看看如何执行更新实体的PUT
请求。 将以下新方法添加到APIService
类。
@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}
是要更新的帖子{id}
的占位符。 @Path
注释是URL路径段{id}
的命名替换。 请注意,使用String.valueOf(Object)
和URL编码将值转换为字符串。 如果该值已被编码,则可以禁用如下URL编码: @Path(value="name", encoded=true)
。
12.执行DELETE请求
我们还要看看如何执行DELETE
请求。 使用JSONPlaceholder API删除帖子资源,所需端点是HTTP方法DELETE
/posts/{id}
。 回到我们的APIService
接口,我们只需要包含将执行该方法的deletePost()
方法deletePost()
。 我们将帖子的ID传递给方法,然后在URL路径段{id}
中将其替换。
@DELETE("/posts/{id}")
Call<Post> deletePost(@Path("id") long id);
13.取消请求
假设您想让用户能够取消或中止请求。 在翻新中这很容易做到。 Retrofit Call
类具有一个名为cancel()
的方法,该方法可以做到这一点(下面的第32行)。 此方法将在回调中触发onFailure()
方法。
例如,如果没有互联网连接,或者在创建请求或处理响应时发生意外异常,则可以调用此方法。 因此,要知道请求是否已取消,请使用Call
类中的isCanceled()
方法(第20行)。
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:为什么要使用Retrofit,以及如何将其集成到项目中以执行POST
, PUT
, DELETE
和取消请求。 您还学习了如何将RxJava与之集成。 在有关使用Retrofit的下一篇文章中,我将向您展示如何上传文件。
翻译自: https://code.tutsplus.com/tutorials/sending-data-with-retrofit-2-http-client-for-android--cms-27845