Retrofit2整理
来源:互联网 发布:java字符串数组 \0 编辑:程序博客网 时间:2024/06/14 09:02
简介
Retrofit的介绍:
A type-safe REST client for Android and Java.
Retrofit使用注解来描述HTTP请求,默认支持URL参数替换和请求参数。而且还支持自定义header、multipart请求体、文件上传和下载、模拟响应等等。
导入
Retrofit2默认以OkHttp为网络层,因此不需要显式依赖OkHttp。但是Retrofit2需要同时依赖JSON转换器。例如以Gson为JSON转换器:
dependencies { // Retrofit & OkHttp compile 'com.squareup.retrofit2:retrofit:2.3.0' compile 'com.squareup.retrofit2:converter-gson:2.3.0' // 如果需要打印日志 compile 'com.squareup.okhttp3:logging-interceptor:3.8.0' }
记得要添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
使用示例
1. 定义接口
接口中定义了请求接口的方法,Retrofit会自动把请求结果解析成声明的返回值类型:
public interface GitHubClient { @GET("/users/{user}/repos") Call<List<GitHubRepo>> reposForUser( @Path("user") String user );}
GitHubRepo类:
public class GitHubRepo { private int id; private String name; public GitHubRepo() { } public int getId() { return id; } public String getName() { return name; }}
2. 创建Retrofit类
String API_BASE_URL = "https://api.github.com/";OkHttpClient.Builder httpClient = new OkHttpClient.Builder();Retrofit.Builder builder = new Retrofit.Builder() .baseUrl(API_BASE_URL) .addConverterFactory(GsonConverterFactory.create());Retrofit retrofit = builder .client(httpClient.build()) .build();GitHubClient client = retrofit.create(GitHubClient.class);
3. 发送请求
// Create a very simple REST adapter which points the GitHub API endpoint.GitHubClient client = retrofit.create(GitHubClient.class);// Fetch a list of the Github repositories.Call<List<GitHubRepo>> call = client.reposForUser("fs-opensource");// 异步执行call.enqueue(new Callback<List<GitHubRepo>>() { @Override public void onResponse(Call<List<GitHubRepo>> call, Response<List<GitHubRepo>> response) { } @Override public void onFailure(Call<List<GitHubRepo>> call, Throwable t) { }})
如果要获取完整的请求结果,可以调用response.raw()。
请求方法详解
使用@GET, @POST(发送创建资源), @PUT(替换资源), @DELETE(删除资源), @PATCH(选择性更新资源)或者@HEAD注解表示使用的HTTP请求方法。例如:
public interface FutureStudioClient { @GET Call<ResponseBody> getUserProfilePhoto( @Url String profilePhotoUrl ); @PUT("/user/info") Call<UserInfo> updateUserInfo( @Body UserInfo userInfo ); @DELETE("/user") Call<Void> deleteUser(); // 也可以指定URL全称 @GET("https://futurestud.io/tutorials/rss/") Call<FutureStudioRssFeed> getRssFeed(); @GET("/users/{user}/repos") Call<List<GitHubRepo>> reposForUser( // 使用这个参数来替换@GET注解中user这个占位符 @Path("user") String user ); @GET("/tutorials") Call<List<Tutorial>> getTutorials( @Query("page") Integer page );}
请求方法的注解需要传入URL地址参数,可以是相对地址也可以是地址全称。
Retrofit会自动把请求结果解析成请求方法的返回值,如果想要获取原始请求结果,可以把返回值类型声明为Call<Response>
,这样可以节省解析成特定对象的事件;如果不需要返回请求结果,可以声明Call<Void>
为返回值类型,这样可以节省加载response body的时间。
可以对请求方法的参数进行以下的注解:
- @Body:发送Java对象作为请求体,Java对象会通过converter转为JSON发送
- @Url:使用指定的Url
- @Field:作为表单数据发送
- @Path:替换方法注解中的路径占位符
- @Query:指定查询资源的更详细条件,以?key=value的形式添加在Url后面。
- @Field:后续详解。
全局配置Retrofit
可以自定义一个ServiceGenerator来封装Retrofit的初始化过程,在项目中使用同一个Retrofit,只使用一个socket连接来处理所有的请求。
public class ServiceGenerator { private static final String BASE_URL = "https://api.github.com/"; private static Retrofit.Builder builder = new Retrofit.Builder().baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()); private static Retrofit retrofit = builder.build(); private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); public static <S> S createService(Class<S> serviceClass) { return retrofit.create(serviceClass); }}
这样封装成ServiceGenerator之后,调用就很方便了:
GitHubClient client = ServiceGenerator.createService(GitHubClient.class);
可以通过添加HttpLoggingInterceptor来打印日志,实现如下:
public class ServiceGenerator { private static final String BASE_URL = "https://api.github.com/"; private static Retrofit.Builder builder = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()); private static Retrofit retrofit = builder.build(); private static HttpLoggingInterceptor logging = new HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BODY); private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); public static <S> S createService( Class<S> serviceClass) { if (!httpClient.interceptors().contains(logging)) { httpClient.addInterceptor(logging); builder.client(httpClient.build()); retrofit = builder.build(); } return retrofit.create(serviceClass); }}
这里在添加HttpLoggingInterceptor之前先判断了是否已经有添加了,避免重复添加。
Url处理
配置基本Url:
Retrofit.Builder builder = new Retrofit.Builder() .baseUrl("https://your.base.url/api/");
注意基本Url必须以/结尾。
端点Url可以在@GET注解参数中设置,也可以设置在方法参数注解的@Url:
public interface UserService { @GET public Call<ResponseBody> profilePicture(@Url String url);}
Retrofit2使用OkHttp的HttpUrl来处理Url,遵循以下几个原则:
1.当端点Url定义了scheme和host的时候,就会整个替换掉base Url。
2.如果端点Url以/开头,则端点Url会直接拼接到基本Url的host后面。比如说下例第二个Url中,端点Url是/my/endpoint,以/开头,会直接拼接到基本Url的host后面,也就是/futurestud.io/后面:
# Good Practicebase url: https://futurestud.io/api/ endpoint: my/endpoint Result: https://futurestud.io/api/my/endpoint# Bad Practicebase url: https://futurestud.io/api endpoint: /my/endpoint Result: https://futurestud.io/my/endpoint
这样子可以用在API有版本区别的时候:
# Example 1base url: https://futurestud.io/api/v3/ endpoint: my/endpoint Result: https://futurestud.io/api/v3/my/endpoint# Example 2base url: https://futurestud.io/api/v3/ endpoint: /api/v2/another/endpoint Result: https://futurestud.io/api/v2/another/endpoint
3.用//表示沿用基本Url的scheme:
# Example 3 — completely different urlbase url: http://futurestud.io/api/ endpoint: https://api.futurestud.io/ Result: https://api.futurestud.io/# Example 4 — Keep the base url’s schemebase url: https://futurestud.io/api/ endpoint: //api.futurestud.io/ Result: https://api.futurestud.io/# Example 5 — Keep the base url’s schemebase url: http://futurestud.io/api/ endpoint: //api.github.com Result: http://api.github.com
4.方法参数注解中可以注解@Path对应@GET的Url中的{占位符}来实时替换。传入”“字符串表示省略该path,比如下面:
public interface TaskService { @GET("tasks/{taskId}") Call<List<Task>> getTasks(@Path("taskId") String taskId);}
如果传递的参数是""
,那生成的Url就是tasks/。而服务器对于Url结尾有无/,处理方式是一样:
// 效果一样https://your.api.url/tasks https://your.api.url/tasks/
这样如果服务器的处理方式是对于有声明资源id就返回某一资源,如果没有声明资源id就返回资源列表的话,这种省略的机制就可以达到一个API可以实现两种用途。
但是如果所要替换的path是在Url中间的话就不能传递""
,否则服务器一般识别不了。
注意@Path参数不能传递null。
自定义请求header
可以使用静态和动态两种方式来定义HTTP请求header。静态的含义是对于不同请求无法改变的,动态的含义是每个请求都需要指定。
1. 静态header
静态header的添加方式也有两种。一种是在请求方法上注解添加:
public interface UserService { @Headers("Cache-Control: max-age=640000") @GET("/tasks") Call<List<Task>> getTasks();}
添加多个header:
public interface UserService { @Headers({ "Accept: application/vnd.yourapi.v1.full+json", "User-Agent: Your-App-Name" }) @GET("/tasks/{task_id}") Call<Task> getTask(@Path("task_id") long taskId);}
另一种添加静态header的方式就是在RequestInterceptor中设置,这样添加的header对于所有使用这个Retrofit实例进行的请求都生效:
OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); httpClient.addInterceptor(new Interceptor() { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request original = chain.request(); Request request = original.newBuilder() .header("User-Agent", "Your-App-Name") .header("Accept", "application/vnd.yourapi.v1.full+json") .method(original.method(), original.body()) .build(); return chain.proceed(request); }}OkHttpClient client = httpClient.build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(API_BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .client(client) .build();
调用header()会覆盖原有的同名header,调用addHeader()是如果有同名header不会覆盖,而是添加到一起。
2. 动态header
动态header是添加在请求方法的参数当中:
public interface UserService { @GET("/tasks") Call<List<Task>> getTasks(@Header("Content-Range") String contentRange);}
可以使用Map批量添加:
public interface TaskService { @GET("/tasks") Call<List<Task>> getTasks( @HeaderMap Map<String, String> headers );}
Get请求和表单Post请求
1. Get请求
@GET请求可以设置@Query和@QueryMap参数。
如果一个key可以对应多个value,则参数类型可以设为List<String>
来实现。如果某个参数是可选的,则调用的时候可以传递null进去,Retrofit就会跳过这个参数。注意参数类型如果是基本数据类型,则不能使用null,需要定义为包装类才能使用null达到跳过这个参数的目的。
Query参数也可以在interceptor当中全局添加:
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();httpClient.addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); HttpUrl originalHttpUrl = original.url(); HttpUrl url = originalHttpUrl.newBuilder() .addQueryParameter("apikey", "your-actual-api-key") .build(); // Request customization: add request headers Request.Builder requestBuilder = original.newBuilder() .url(url); Request request = requestBuilder.build(); return chain.proceed(request); }});
如果有多个参数可以使用@QueryMap传递:
public interface NewsService() { @GET("/news") Call<List<News>> getNews( @QueryMap Map<String, String> options );}
@QueryMap也有个encoded字段表明是否已编码。
Post请求
表单请求会把请求的MIME类型设为application/x-www-form-urlencoded
,如下:
public interface TaskService { @FormUrlEncoded @POST("tasks") Call<Task> createTask(@Field("title") String title);}
@FormUrlEncoded注解不能用在@GET上,因为表单请求是要向服务端发送数据的。
同一表单字段对应多个值可以使用List作为参数来实现:
public interface TaskService { @FormUrlEncoded @POST("tasks") Call<List<Task>> createTasks(@Field("title") List<String> titles);}
比如进行如下请求的话:
List<String> titles = new ArrayList<>(); titles.add("Research Retrofit"); titles.add("Retrofit Form Encoded")service.createTasks(titles);
实际生成的表单数据是这样子的:
title=Research+Retrofit&title=Retrofit+Form+Encoded
@Field注解可传入encoded字段表示该参数是否已经进行Url编码了。默认是false:
@Field(value = "title", encoded = true) String title
对于多个参数可以使用@FieldMap:
public interface UserService { @FormUrlEncoded @PUT("user") Call<User> update(@FieldMap Map<String, String> fields);}
@FieldMap同样有个可选字段encoded来设置参数是否已经是Url编码过的了。
如果多个Post请求都有固定的字段,则可以封装成Java对象,简化请求步骤。
比如一个feeback接口接收以下几个参数,当中只有message是会变的,其他都是固定的:
@FormUrlEncoded@POST("/feedback")Call<ResponseBody> sendFeedbackSimple( @Field("osName") String osName, @Field("osVersion") int osVersion, @Field("device") String device, @Field("message") String message, @Field("userIsATalker") Boolean userIsATalker);
那我们就可以把请求字段封装起来,不变的字段默认进行初始化:
public class UserFeedback { private String osName = "Android"; private int osVersion = android.os.Build.VERSION.SDK_INT; private String device = Build.MODEL; private String message; private boolean userIsATalker; public UserFeedback(String message) { this.message = message; this.userIsATalker = (message.length() > 200); } // getters & setters // ...}
新的请求方法:
@POST("/feedback")Call<ResponseBody> sendFeedbackConstant(@Body UserFeedback feedbackObject);
进行请求的话就很简便了:
private void sendFeedbackFormAdvanced(@NonNull String message) { FeedbackService taskService = ServiceGenerator.create(FeedbackService.class); Call<ResponseBody> call = taskService.sendFeedbackConstant(new UserFeedback(message)); call.enqueue(new Callback<ResponseBody>() { ... });}
3. @FormUrlEncoded和@Query的区别:
@FormUrlEncoded用于Post请求,发送数据到服务器,数据存放在请求体当中。
@Query用于Get请求,用于向服务器请求数据。
Call的相关说明
可以调用Call对象的cancel()方法来取消请求,cancel()方法会触发onFailure(Call<ResponseBody> call, Throwable t)
回调,可以调用call.isCanceled()
判断失败原因是否是取消。
Call对象只能执行一次execute()或者enqueue(),如果要重复请求,可以调用call.clone()来创建副本进行请求。
Call.request()可以返回这个Call对应的request,注意如果这个request还没执行的话为了获取这个request可能会阻塞住线程。
上传文件
使用Retrofit2上传文件需要把文件封装在OkHttp的RequestBody或者MultipartBody.Part当中。
接口定义:
public interface FileUploadService { @Multipart @POST("upload") Call<ResponseBody> upload( @Part("description") RequestBody description, @Part MultipartBody.Part file );}
上传文件方法需要使用@Multipart注解。@Part注解表明该参数是一个multi-part请求的一部分,需要提供@Part自带参数用来表示该part的名称。当@Part用来注解MultipartBody.Part时比较特殊,无需提供自带参数,可省略。
上传文件的执行:
private void uploadFile(Uri fileUri) { // create upload service client FileUploadService service = ServiceGenerator.createService(FileUploadService.class); // https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java // use the FileUtils to get the actual file by uri File file = FileUtils.getFile(this, fileUri); // 创建文件的RequestBody RequestBody requestFile = RequestBody.create(MediaType.parse(getContentResolver().getType(fileUri)),file); // 使用文件RequestBody创建MultipartBody.Part,指定名称 MultipartBody.Part body = MultipartBody.Part.createFormData("picture", file.getName(), requestFile); // 添加另外一个Part String descriptionString = "hello, this is description speaking"; RequestBody description = RequestBody.create( okhttp3.MultipartBody.FORM, descriptionString); // finally, execute the request Call<ResponseBody> call = service.upload(description, body); call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { Log.v("Upload", "success"); } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { Log.e("Upload error:", t.getMessage()); } });}
- Retrofit2整理
- Retrofit2.0上传图片文件件整理
- Retrofit2
- retrofit2
- Retrofit2
- Retrofit2 source
- Retrofit2 简述
- Retrofit2+RxJava
- retrofit2+rxJava
- Retrofit2介绍
- Retrofit2.0
- 学习 Retrofit2
- Retrofit2使用方法
- Retrofit2 笔记
- Retrofit2 笔记
- Retrofit2使用
- Retrofit2使用
- rxjava2 + retrofit2
- 年薪30万-50万,站在风口上的大数据面临150万的人才紧缺
- 大端模式和小端模式详解
- 把英语当成是一门语言去学习
- Google/LintCode:M-合并k个排序链表
- 合并链表
- Retrofit2整理
- Linux文件的归档与压缩
- MATLAB实现Catmull-Clark细分(CC细分)
- VMware vSphere/vCenter/ESX(i)介绍
- python u'\ ' 输出编码问题
- 面试题---二进制中1的个数
- 【面试虐菜】—— MongoDB知识整理
- java String 中的一些问题
- Java8学习记录(一)-函数式接口