Retrofit的使用

介绍

Retrofit是一个网络加载框架,Retrofit主要负责网络接口的封装,实质的网络请求是依靠OkHttp实现的。Retrofit可以配置不同的反序列化工具解析数据,如jsonxml等等

视频链接:[Retrofit Tutorial for Beginners - Android Programming

使用

  1. 添加依赖及网络权限

    当前选用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" />
    
  1. 创建返回数据的类

    测试地址为: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 + '\'' +
                '}';
    }
}
  1. 定义请求的接口
    public interface JsonPlaceHolderApi {
    
        @GET("posts")
        Call<List<Post>> getPosts();
    }
    
  1. 具体实现
    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、请求参数等等

Logging Interceptor

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。