介绍
Retrofit是一个网络加载框架,Retrofit主要负责网络接口的封装,实质的网络请求是依靠OkHttp实现的。Retrofit可以配置不同的反序列化工具解析数据,如json
、xml
等等
使用
-
添加依赖及网络权限
当前选用
Gson
作为数据解析器implementation 'com.squareup.retrofit2:retrofit:2.0.2' implementation 'com.squareup.retrofit2:converter-gson:2.0.2' implementation 'com.google.code.gson:gson:2.8.6'
<uses-permission android:name="android.permission.INTERNET" />
-
创建返回数据的类
测试地址为:
http://jsonplaceholder.typicode.com/posts
(有时候访问比较慢)数据内容如下:
[
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
},
{
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
},
... ...
]
返回数据类
public class Post {
private int userId;
private int id;
private String title;
private String body;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = 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;
}
@Override
public String toString() {
return "Post{" +
"userId=" + userId +
", id=" + id +
", title='" + title + '\'' +
", body='" + body + '\'' +
'}';
}
}
- 定义请求的接口
public interface JsonPlaceHolderApi { @GET("posts") Call<List<Post>> getPosts(); }
- 具体实现
public class MainActivity extends AppCompatActivity { private JsonPlaceHolderApi jsonPlaceHolderApi; private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.textView); Retrofit retrofit = new Retrofit.Builder() // 设置网络请求的url地址 .baseUrl("http://jsonplaceholder.typicode.com/") // 设置数据解析器 .addConverterFactory(GsonConverterFactory.create()) .build(); jsonPlaceHolderApi = retrofit .create(JsonPlaceHolderApi.class); getPosts(); } private void getPosts() { Call<List<Post>> call = jsonPlaceHolderApi.getPosts(); // 此方式为异步请求 call.enqueue(new Callback<List<Post>>() { @Override public void onResponse(Call<List<Post>> call, Response<List<Post>> response) { if (!response.isSuccessful()) { textView.setText("Code:" + response.code()); return; } List<Post> list = response.body(); for (Post post : list) { textView.append(post.toString() + "\n"); } } @Override public void onFailure(Call<List<Post>> call, Throwable t) { textView.setText(t.getMessage()); } }); } }
网络请求方法和参数
以下是支持的请求方法,后面将以这些作为例子
GET /posts
GET /posts/1
GET /posts/1/comments
GET /comments?postId=1
GET /posts?userId=1
POST /posts
PUT /posts/1
PATCH /posts/1
DELETE /posts/1
GET请求方式
// 无参
@GET("posts")
Call<List<Post>> getPosts();
@GET("posts")
Call<List<Post>> getPosts(@Query("userId") int userId);
@GET("posts")
Call<List<Post>> getPosts(@Query("userId") int userId, @Query("_order") String order);
// 多个相同的参数,使用数组形式,也可使用可变参数,但得确保后面没有其他参数
@GET("posts")
Call<List<Post>> getPosts(@Query("userId") Integer[] userId);
// 也可以使用map集合传递多个不同参数
@GET("posts")
Call<List<Post>> getPosts(@QueryMap Map<String, String> params);
@GET("posts/{id}/comments")
Call<List<Comment>> getComments(@Path("id") int postId);
// 直接传入url
@GET
Call<List<Comment>> getComments(@Url String url);
// 如果想直接返回ResponseBody的内容,可这样定义
@GET("posts")
Call<ResponseBody> getPosts();
其中@Path
和@Query
的url拼接方式是不同的
@Path
url拼接格式为:http://jsonplaceholder.typicode.com/posts/1/comments
@Query
url拼接格式为:http://jsonplaceholder.typicode.com/posts?userId=1
POST请求方式
@POST("posts")
Call<Post> createPost(@Body Post post);
public void createPost() {
// 需要再Post中添加一个构造方法
Post post = new Post(11011, "Title", "Body");
Call<Post> call = jsonPlaceHolderApi.createPost(post);
call.enqueue(new Callback<Post>() {
@Override
public void onResponse(Call<Post> call, Response<Post> response) {
if (!response.isSuccessful()) {
textView.setText("Code:" + response.code());
return;
}
Post responsePost = response.body();
// 返回的状态码为201,即代表创建成功
textView.append("Code:" + response.code() + "\n");
textView.append(responsePost.toString());
}
@Override
public void onFailure(Call<Post> call, Throwable t) {
textView.setText(t.getMessage());
}
});
}
当然我们可以使用@FormUrlEncoded
表示发送form-encoded
数据,就像这样,每个参数需要使用@Field
@FormUrlEncoded
@POST("posts")
Call<Post> createPost(
@Field("userId") int userId,
@Field("title") String title,
@Field("body") String body
);
也可以使用map集合的方式
@FormUrlEncoded
@POST("posts")
Call<Post> createPost(@FieldMap Map<String, String> fields);
PUT/PATCH请求方式
@PUT("posts/{id}")
Call<Post> putPost(@Path("id") int id, @Body Post post);
@PATCH("posts/{id}")
Call<Post> patchPost(@Path("id") int id, @Body Post post);
二者的区别
PUT方式需要提交一个完整的对象,如果对象不完整,则缺失的属性会变成null
PATCH方式适用于局部更新,后端只更新接收到的属性
Gson
默认不会导出值为null
的属性,但是可以手动开启,修改Retrofit的初始化
// Gson默认不导出值为null的属性
// Gson gson = new GsonBuilder().serializeNulls().create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
DELETE请求方式
@DELETE("posts/{id}")
Call<Void> deletePost(@Path("id") int id);
日志拦截器Logging Interceptor
日志拦截器能够显示HTTP请求和响应的数据日志
加入如下依赖
implementation 'com.squareup.okhttp3:logging-interceptor:4.7.2'
修改Retrofit的初始化
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
部分运行日志如下:
2020-06-13 16:54:15.086 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: <-- 200 OK http://jsonplaceholder.typicode.com/posts/1 (1853ms)
2020-06-13 16:54:15.086 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Date: Sat, 13 Jun 2020 08:54:15 GMT
2020-06-13 16:54:15.086 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Content-Type: application/json; charset=utf-8
2020-06-13 16:54:15.086 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Transfer-Encoding: chunked
2020-06-13 16:54:15.087 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Connection: keep-alive
2020-06-13 16:54:15.087 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Set-Cookie: __cfduid=deb0375d73772aebf42a5ed936675a4f71592038454; expires=Mon, 13-Jul-20 08:54:14 GMT; path=/; domain=.typicode.com; HttpOnly; SameSite=Lax
2020-06-13 16:54:15.087 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: X-Powered-By: Express
2020-06-13 16:54:15.087 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Vary: Origin, Accept-Encoding
2020-06-13 16:54:15.088 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Access-Control-Allow-Credentials: true
2020-06-13 16:54:15.088 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Cache-Control: no-cache
2020-06-13 16:54:15.088 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Pragma: no-cache
2020-06-13 16:54:15.088 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Expires: -1
2020-06-13 16:54:15.088 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: X-Content-Type-Options: nosniff
2020-06-13 16:54:15.088 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Etag: W/"df-6jLdLZqSLI/cZFXFYIHDHqEFuLg"
2020-06-13 16:54:15.089 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Via: 1.1 vegur
2020-06-13 16:54:15.089 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: CF-Cache-Status: DYNAMIC
2020-06-13 16:54:15.089 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: cf-request-id: 034e7c0b4b0000e50e1fb84200000001
2020-06-13 16:54:15.089 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: Server: cloudflare
2020-06-13 16:54:15.089 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: CF-RAY: 5a2a95f21cb4e50e-LAX
2020-06-13 16:54:15.794 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: {
2020-06-13 16:54:15.794 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: "userId": 1,
2020-06-13 16:54:15.794 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: "id": 0,
2020-06-13 16:54:15.794 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: "title": "Title",
2020-06-13 16:54:15.794 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
2020-06-13 16:54:15.794 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: }
2020-06-13 16:54:15.796 29040-29068/com.example.retrofitdemo I/okhttp.OkHttpClient: <-- END HTTP (223-byte body)
可以方便的查看到访问的url、请求参数等等
Header
Header有以下几种设置方式
@Header
@Headers
@HeaderMap
// 可作用于方法或者是参数
@Headers({"Header:123"})
@PUT("posts/{id}")
Call<Post> putPost(@HeaderMap Map<String, String> headers,
@Path("id") int id, @Body Post post);
当然也可以在OkHttpClient
中添加一个拦截器,设置Header
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@NotNull
@Override
public okhttp3.Response intercept(@NotNull Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.header("Interceptor-Header", "456")
.build();
return chain.proceed(request);
}
})
.addInterceptor(loggingInterceptor).build();
源码:RetrofitDemo