原文:https://code.tutsplus.com/zh-...
原作:Chike Mgbemena
翻译:Shen Yun Court
什么是Retrofit?
Retrofit 是一个用于Android 和Java 的类型安全的HTTP 客户端。它通过将REST web service 的API转换成Java的接口, 以简化HTTP 连接的处理。在这里, 我将告诉你怎么使用这个在Android 中最常用并且最被推荐的HTTP 库。
这个强大的库可以很容易地处理JSON 或者XML 数据, 然后转换成POJO。GET
, POST
, PUT
, PATCH
, 和DELETE
这些请求都可以执行。
和大多数开源软件一样, Retrofit 也是构建在其他强大的库之上。在底层, Retrofit 使用OKHttp (来自同一个开发者) 来处理网络请求。同样, Retrofit 也并没有自己构建JSON 转换器来转换JSON 数据, 相反, 它通过支持下面一些JSON 转换库来将JSON 数据转换成Java 对象:
Gson:
com.squareup.retrofit:converter-gson
Jackson:
com.squareup.retrofit:converter-jackson
Moshi:
com.squareup.retrofit:converter-moshi
对于Protocol buffers (Google链接), 则可以使用:
Protobuf:
com.squareup.retrofit2:converter-protobuf
Wire:
com.squareup.retrofit2:converter-wire
而对于XML, 可以使用:
Simple Framework:
com.squareup.retrofit2:converter-simpleframework
所以, 为什么使用Retrofit?
要自己开发一个类型安全的HTTP 库来映射REST API 是一个痛点: 你必须处理好很多方面, 比如创建连接, 缓存, 失败请求重试, 多线程, 处理响应, 处理错误, 等等。而从另一个方面来说, Retrofit 是一个经过良好规划, 拥有优质文档, 经过全面测试的库, 它将节省你大量的宝贵的时间。
在这里, 我将通过创建一个简单的APP 来讲解该怎么使用Retrofit 2 来处理网络请求。 这个APP 会执行POST
, PUT
(为了更新实体), 和DELETE
请求。我也会告诉你如何与 RxJava 集成以及如何取消请求。我们将使用由JSONPlaceholder 提供的REST API, 这是一个用于测试和原型设计的在线模拟 API。
1. 创建一个Android 项目
启动Android Studio, 然后创建一个带有一个空的MainActivity 的项目。
2. 声明依赖
创建了项目之后, 在build.gradle
里面添加下面的依赖. 这些依赖包括Retrofit 库, 以及用来转换JSON 的Google 的Gson 库, 另外还包含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. 添加网络权限
要访问网络, 必须要在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. 自动生成Model
我们将使用一个非常有用的工具: jsonschema2pojo, 来将响应的JSON 格式自动创建成相应的Model. 我们会进行一个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 的annotation, 去掉Allow additional properties 的复选框, 然后将类名从Example 改为Post。
然后点击Preview 按钮来生成Java 对象。
你可能会好奇这里面的@SerializedName
和@Expose
是做什么用的! 别着急, 且听我道来!
@SerializedName
是Gson 用于将JSON 的key 映射到Java 对象的字段的。
@SerializedName("userId")
@Expose
private Integer userId;
比如, 上面这一段, JSON 的key userId
将被映射到类字段userId
. 想必你也注意到了, 它们是一样的, 所以这里其实可以不用@SerializedName
, Gson 会自动为我们做这样的映射。
而另一方面, @Expose
则是用来声明类成员是否需要进行JSON 的序列化或反序列化。
导入数据Model 到Android Studio
现在让我们回到Android Studio。在main
包下创建data
包. 在这新建的包下面, 再创建一个model
包. 然后在这个包下面创建Post
类. 然后将jsonschema2pojo 生成的代码复制到你创建的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 实例
要使用Retrofit 去请求RESTful 的API, 我们需要先使用Retrofit Builder 来创建一个实例, 并使用一个根URL 来配置它。
在data
包下面创建remote
包. 然后, 在新建的包下面, 创建一个RetrofitClient
的Java 类。这个类将通过getClient(String baseUrl)
方法创建一个Retrofit 的单例, 并返回给调用者。
正如我之前提到的, Retrofit 需要一个根URL 来创建实例, 所以我在调用RetrofitClient.getClient(String baseUrl)
的时候传递给它。这个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 接口
在remote 包下面, 创建一个APIService
的接口。这个接口包含了将要用到的发送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
的annotation, 这是用来标识当这个方法执行的时候要发送POST
请求出去。@Post
annotation 的参数值, 是请求的地址, 这里是/posts
。所以, 请求的全路径将会是http://jsonplaceholder.typico...。
OK, 那么@FormUrlEncoded
是用来做什么的呢? 它是用来标识这个请求的MIME 类型(一个用来标识HTTP 请求或响应的内容格式的HTTP 头) 需要设置成application/x-www-form-urlencoded
, 并且请求的字段和字段值需要在进行URL 编码之前先进行UTF-8 编码处理. @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
Annotation
我们也可以在请求方法的参数使用@Body
Annotation, 而不是像上面那样每个字段都单独指定。这个对象将使用在创建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. 创建布局
MainActivity
对应的布局文件是activity_main.xml。 这个布局, 包含了两个文本框, 一个用于输入标题, 另一个则是用于输入内容. 另外还有个用于提交数据的按钮。
<?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()
方法。
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 service
接口的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);
}
对象mAPIService
(APIService
接口的实例) 的savePost(String, String)
方法会返回一个Call
对象, 这个对象有个enqueue(Callback callback)
方法。
理解enqueue()
enqueue()
异步地发送请求, 然后当响应回来的时候, 使用回调的方式通知你的APP。因为这个请求是异步的, Retrofit 使用一个另外的线程去执行它, 这样UI 线程就不会被阻塞了。
要使用enqueue()
方法, 你需要实现两个回调方法: onResponse()
和onFailure()
。对于一个请求, 当响应回来的时候, 只有一个方法会被执行。
onResponse()
: 在收到HTTP 响应的时候被调用。这个方法在服务器可以处理请求的情况下调用, 即使服务器返回的是一个错误的信息。例如你获取到的响应状态是404 或500。你可以使用response.code() 来获取状态码, 以便进行不同的处理. 当然你也可以直接用isSuccessful() 方法来判断响应的状态码是不是在200-300 之间(在这个范围内标识是成功的)。onFailure()
: 当和服务器通信出现网络异常时, 或者在处理请求出现不可预测的错误时, 会调用这个方法。
同步请求
要执行同步的请求, 你可以直接使用Call
对象的execute()
方法。但是要注意, 如果在UI 线程执行同步的请求, 将会阻塞用户的操作。所以, 不要在Android 的UI 线程去执行同步的方法。而是应该将它们放在后台线程去运行.
使用RxJava
在Retrofit 1 中, RxJava 是默认集成进去了的, 但是在Retrofit 2 中, 我们还需要添加一些额外的依赖。Retrofit 在执行Call
对象的方法时, 带有一个默认的Adapter. 所以你能够通过引入RxJava (带有RxJava 的CallAdapter
), 改变Retrofit 的运行机制。按照下面的步骤:
步骤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
在创建Retrofit 实例的时候添加新的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;
}
步骤3
更新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
当进行请求时, 添加一个匿名的subscriber 去监听observable 的流. 当subscriber 接收到事件时, 将调用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. 测试APP
到这里, 你就可以允许你的APP, 输入标题和内容, 然后点击提交按钮。从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);
要更新一个post, 我们使用/posts/{id}
这样的请求地址, 在这里{id}
是用来填充我们要更新的post 的id 的占位符。而参数中的@Path 注解是用来填充URL 路径中的{id}
片段的值的。要注意的是这些值都将使用[String.valueOf(Object)][15]
转换成字符串, 然后进行URL 编码。如果这个值已经是编码过了的, 你也可以用这样的方式禁止再进行URL 编码: @Path(value="name", encoded=true)
。
12. 执行DELETE 请求
让我们再来看看怎么执行DELETE
请求。要使用JSONPlaceholder API 去删除一个post 资源, 要请求的地址和更新是一样的/posts/{id}
, 所不同的只是使用的HTTP 方法是DELETE
。回到APIService
接口, 只需要添加下面的deletePost()
方法。我们传入方法的参数是要删除的post id, 这个id 一样是用来替换URL 中的{id}
片段。
@DELETE("/posts/{id}")
Call<Post> deletePost(@Path("id") long id);
13. 取消请求
如果你想让你的用户能够取消或放弃一个请求。在Retrofit 可以很容易做到这一点。Retrofit 的Call
类有个cancel()
方法就是用来做这个的(下面的第30 行)。这个方法将触发onFailure()
回调方法。
如果在没有网络连接, 或者在创建请求或处理响应时出现意外异常, 可以调用这个方法来取消请求。如果你想要知道你的请求是否被取消了, 可以使用Call
类里面的isCanceled()
方法(第18 行)。
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
请求, 怎么取消请求. 你还了解到该怎么让Retrofit 和RxJava 进行集成. 在下一篇文章中, 我将讲讲该怎么上传文件。
想要了解更多Retrofit 的内容, 请务必去看看官方的文档。然后看看Envato Tuts+ 里面关于Android 开发的其他教程和课程。
关于Envato艺云台
Envato艺云台是数据资产和创造性人才汇聚的全球领先市场平台。全球数百万人都选择通过我们的市场平台、工作室和课程来购买文件、选聘自由职业者,或者学习创建网站、制作视频、应用、制图等所需的技能。我们的子网站包括Envato艺云台Tuts+ 网络,全球最大的H5、PS、插图、代码和摄影教程资源库,以及Envato艺云台市场,其中的900多万类数字资产均通过以下七大平台进行销售 - CodeCanyon、ThemeForest、GraphicRiver、VideoHive、PhotoDune、AudioJungle和3DOcean。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。