Retrofit2.1.0 学习笔记

16 篇文章 0 订阅
14 篇文章 0 订阅

本片文章是Retrofit 2.1.0的学习笔记,以下简称Retrofit 2,Retrofit 2 的源码只有37个文件,其中注解文件的个数是22个。所以我们平时使用的过程中重点使用其中的15个类。22个注解我们会使用即可(如果有朋友想详细的学习注解的东西建议先看一些注解相关的资料,再回头看里面的设计思想)。下面我们开始学习Retrofit 2.

1、Retrofit 2 入门:

创建实例和Retrofit1.1的区别:

Retrofit 1.1 创建实例:

Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8848")   //  url的结尾可以不是/(斜线)
.build();

Retrofit 2 创建实例:

 Retrofit retrofit = new Retrofit.Builder()
 .baseUrl("http://localhost:8848/")   //  url 的结尾必须以/(斜线)结束, 不然会抛出IllegalArgumentException,所以需要注意下。
 .build();

2、定义接口

官方文档:http://square.github.io/retrofit/

引用 :官方的一句话:Retrofit turns your HTTP API into a Java interface.  

定义接口示例:

public interface GitHubService{
      @GET("users/{user}/repos")
      Call<List<Repo>> listRepos(@Path("user") String user);
}

2.1 示例中的注解 

@GET("users/{user}/repos")  代表请求方式是get请求。请求参数是listRepos()方法中传的参数。这里我们暂时先简单了解一下,除了@GET 方式,另外还有@POST、@PUT、@DELETE、@PATCH、@HEAD、@OPTIONS以及@HTTP 这几种方式(这里简单了解一下,HTTP方式可以替换前面的7种方式)。

2.2 接口中的方法

示例代码中方法的返回类型是一个Call类型,这种类型一般用于简单的逻辑实现,实现Call类型的回调监听即可。除Call类型之外,实际项目开发中使用Observable类型的情况居多,毕竟使用Retrofit也是为了使代码更加优雅,逻辑更加清晰(订阅者/观察者模式,当然说观察者模式有些不太合乎定义),Observable也就是可订阅者(相当于被观察者),用于响应式编程(这个之后再说)。

2.3 接口方法的参数

@Path注解作用于方法参数,用于URL。除Path之外还有:

@Headers(用于添加请求头)、

@Header(用于添加不固定值的Header,请求头)、

@Body(用于非表单请求体,之后再详细说)、

@Field(用于表单字段,和@FiledMap配合@FormUrlEncoded注解配合)、

@FieldMap(和@FieldMap类似,接收的参数类型是Map<String, String>,如果传的参数类型不是String类型,会调用参数的toString()方法)、

@Part和@PartMap(配合@Multipart注解使用,适合有文件上传的情况。@PartMap的默认接受的参数类型是Map<String, RequestBody>类型,非RequestBody类型会通过Converter转换,Converter转换之后会细说)、

@Query(用于URL,@Query和@QueryMap 类似 @Field和@FieldMap功能,不同的是@Query和@QueryMap中的数据体现在Url上,而@Field和@FieldMap的数据是请求体,但是生成的数据形式是一样的)、

Url(用于URL)


注:{占位符}和Path尽量只用在URL的path部分,url中的参数使用Query和QueryMap代替保证接口定义的简洁

注:Query、Field和Part这三者都是支持数组和实现了Iterable接口的类型,比如List、Set等,方便向后台传递数组


3、使用接口(这里说的接口是interface,不是后台接口)

这里我们以示例接口为例。

// 3.1 Retrofit中调用接口方法需要我们先创建一个代理对象。
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://localhost:8848").build();
GitHubService gitHubService = retrofit.create(GitHubService.class);

// 3.2 得到代理对象之后,就可以调用定义的接口方法了
// 3.2.1 接口方法的返回类型是Call
Call<ResponseBody> call = gitHubService.listRepos("uana_777");
// 这一步和OkHttp的Call类似,不同的是如果是Android系统回调方法执行在主线程
call.enqueue(new Callback<ResponseBody>(){
	@Override
	public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response){
		try{
			System.out.println(response.body().toString());
		} catch(Exception e){
			// 处理异常
		}
	}

    	@Override
    	public void onFilure(Call<ResponseBody> call, Throwable t){
    		// 失败后的处理
		t.printStackTrace();
    	}
});

打印出来的结果:

{“code”:200, "msg":"success", "data":{"id":2,"date":"2016-11-04","userName":"uana_777"}, "count":1, "page":0}

注:上面说到了Retrofit 的注解:@XX。所以下面我们详细介绍一下这方面。

4、Retrofit 的注解

开始我们提到Retrofit中的注解文件个数是22个。下面我们就详细介绍这22个注解。为了帮助大家更好的理解和记忆,我们把这22个注解分为3个大类。

第一类:Http请求注解  8个

第二类:标志类   3个

第三类:参数类   11个

下面开始详细介绍:

4.1 Http 请求注解

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@GET   对应Http请求方法,get请求,接受字符串格式的参数,与baseUrl组成一个完整的Url,例如:

public interface GitHubService{					
	@GET("users/uana_777/repos") // 和创建Retrofit 时baseUrl("http://localhost:8848/")中的参数拼接为一个完整的 url
      	Call<List<Repo>> listRepos();
}

也可以像上面的接口中写的:和@PATH注解的参数一起使用,定义一个灵活的请求地址。例如:

public interface GitHubService{					
	@GET("users/{user_name}/repos")  // 对应@PATH("XXX") 参数名称,和baseUrl("http://localhost:8848/")中的参数拼接为一个完整的 url
      	Call<List<Repo>> listRepos(@PATH("user_name") String userName);
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@POST 对应Http请求方法,post请求,和GET注解类似,接受的参数类型是String类型,

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@PUT 对应Http请求方法,put请求

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@DELETE 对应Http请求方法,delete请求

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@PATCH 对应Http请求方法,patch请求

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@HEAD 对应Http请求方法,head请求

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@OPTIONS 对应Http请求方法,options请求

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@HTTP 注:这个可以代替上面7种注解中的任意一种注解,有三个属性:method = “Http请求方式”、path = "请求地址"、hasBody = true Or false

public interface GitHubService{
	/**
 	 * method 表示请求方法,不区分大小写
	 * path 表示请求地址(路径)
 	 * hasBody 表示是否有请求体
 	 */
	@HTTP(method = "get", path = "{user_name}/repo", hasBody = false)
	Call<ResponseBody> getUserInfo(@Path("user_name") String userName);

	@HTTP(method = "get", path = "{user_id}/repo", hasBody = false)
	Call<ResponseBody> getUserInfo(@Path("user_id") int uerId);
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------


4.2 标志类                                                                          

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@FormUrlEncoded 用于表单请求,表示请求体是一个Form表单,比如平时网页登录的时候使用的就是表单请求,Content-Type:multipart/form-data

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@MultiPart 用于表单请求,表示请求体是一个支持文件上传的Form表单,Content-Tpye:multipart/form-data

-------------------------------------------------------------------------------------------------------------------------------------------------------------

@Streaming 标记,表示响应提的数据用流的形式返回,如果使用该注解,默认会把返回的数据全部载入内存,之后你通过流获取数据,其实也就是

读取内存中的数据。所以如果你返回的数据比较大,你就需要使用这个注解

-------------------------------------------------------------------------------------------------------------------------------------------------------------

注:示例代码

package com.github.uana;

import java.util.HashMap;
import java.util.Map;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Field;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import retrofit2.http.PartMap;

/**
 * Retrofit 2 标志类注解 
 */
public class Example{

	public interface GitHubService{

		/**
 		 * @FormUrlEncoded 表示是一个表单格式的请求
 		 * (Content-Type:application/x-www-form-urlencoded)
 		 * Field("user_name") 表示将后面的 String name 参数值作为user_name的值,同样的Field("age") 表示 将后面的 int age 参数值作为age的值
 		 */
 		@POST("/form")   // url地址
		@FormUrlEncoded
 		Call<ResponseBody> testFormUrlEncoded(@Field("user_name") String name, @Field("age") int age);

		/**
 		 * Map的key作为表单的键
		 */ 
		@POST("/form")
		@FormUrlEncoded
		Call<ResponseBody> testFormUrlEncoded(@FieldMap Map<String, Object> map);

		/**
		 *{@link Part}注解,后面的参数类型支持三种:RequestBody、okhttp3.MultipartBody.Part、除以上两种类型之外其他类型的必须带上表单字段(okhttp3.MultipartBody.Part 中已经包含了表单字段的信息)
		 *
		 */
		@POST("/form")
		@Multipart
		Call<ResponseBody> testFileUpLoad(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);

		/**
		 * PartMap 注解支持一个Map作为参数,也支持RequestBody类型,如果有其他的类型,会被retrofit2.Converter 转换。
		 *  后面会介绍如何转换,使用com.google.gson.Gson 的retrofit2.Converter。文件只能用 @Part MultipartBody.Part
		 */
		@POST("/form")
		@Multipart
		Call<ResponseBody> testFileUpload(@PartMap<String, RequestBody> args, @Part MultipartBody.Part file);

	}

	/**
 	 * 调用
	 */
	public void test(){

		// 获取Retrofit 对象
		Retrofit retrofit = new Retrofit.Builder()
			.baseUrl("http://localhost:8848/")
			.addConverterFactory(GsonConverterFactory.create())
			.build();

		// 获取接口实例
		GitHubService service = retrofit.create(GitHubService.class);

		// 演示 @FormUrlEncoded 和 @Field
		Call<ResponseBody> call_one = service.testFormUrlEncoded("uana_777", 25);

		// 打印响应结果
		// ResponseBodyPrinter.printRespinseBody(call_one);
		// 自定义处理
		call_one.enqueue(new CallBack<ResponseBody>(){

			@Override
			public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response){

				try{
					//  自己处理响应结果
				}catch(IOException e){
					// 异常处理
				}
			}

			@Override
			public void onFailure(Call<ResponseBody> call, Throwable t){
				// 请求失败的处理
			}
		
		});

		// =========================//

		// 演示@FormUrlEncoded 和 @FieldMap
		Map<String, Object> params = new HashMap<>();
		params.put("user_name", "uana_777");
		params.put("age", 25);
		Call<ResponseBody> call_two = service.testFormUrlEncoded(params);
		// 打印响应结果
		// ResponseBodyPrintResponseBody(call_two);

		//============================//
		MediaType textType = MediaType.parse("text/plain");
		RequestBody userName = RequestBody.create(textType, "uana_777");
		RequestBody age = RequestBody.create(textType, "25");
		RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");

		// 演示@Multipart 和 @Part
		MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
		Call<ResponseBody> call_three = service.testFileUpload(userName, age, filePart);
		// 打印响应结果
		// ResponseBodyPrinter.printResponseBody(call_three);

		//===========================//

		// 演示@Multipart 和 @Part
		MediaType textType = MediaType.parse("text/plain");
		RequestBody uName = RequestBody.create(textType, "uana_777");
		RequesBody uAge = RequestBody.create(textType, "25");
		RequestBody uFile = RequestBody.create(MediaType.parse("application/octet-stream"), "这是测试数据");
		Map<String, RequestBody> fileUploadParams = new HashMap();

		fileUploadParams.put("user_name", uName);
		fileUploadParams.put("age", uAge);
		// 这里并不会被当成文件处理, 因为没有文件名
		// fileUploadParams.put("file", uFile);
		// 通过part转换一下
		MultipartBody.Part uFilePart = MultipartBody.Part.createFormData("file", "test.txt", uFile);

		Call<ResponseBody> call_four = service.testFileUpload(fileUploadParams, uFilePart);
		// 打印响应的结果
		ResponseBodyPrintResponseBody(call_four);
	}

}

4.3 参数类 (作用于方法参数)

-------------------------------------------------------------------------------------------------------------------------------------------------------------
@Headers 用于添加请求头
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@Header 用于添加不固定值的Header
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@Body 用于非表单请求体
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@Filed  用于表单字段 和FieldMap注解配合FormUrlEncoded注解使用
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@FiledMap 用于表单字段,接受的类型是Map<String, String>, 如果参数不是String类型,会调用其toString方法
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@Part  和@PartMap注解配合@MultiPart注解使用,适合有文件上传的情况
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@PartMap 默认接受的类型是Map<String, RequestBody>, 如果参数不是RequestBody类型,会通过Converter转换
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@Path 用于URL
-------------------------------------------------------------------------------------------------------------------------------------------------------------
@Query 用于URL,@Query和@QueryMap 与@Field和@FieldMap功能一样,不同的是@Query和@QueryMap中的数据体现在Url,
而@Field和@FieldMap的数据是请求体,但是生成的数据形式是一样的。
-------------------------------------------------------------------------------------------------------------------------------------------------------------
Url 用于Url
-------------------------------------------------------------------------------------------------------------------------------------------------------------

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.Query;

/**
 * Retrofit注解详解 Headers & Header
 */
public class Example {

    	public interface GitHubService {
        	@GET("/headers?showAll=true")
		@Headers({"CustomHeader1: customHeaderValue1", "CustomHeader2: customHeaderValue2"})
        	Call<ResponseBody> testHeader(@Header("CustomHeader3") String customHeaderValue3);

    	}

    	public void test() {
        	Retrofit retrofit = new Retrofit.Builder()
                	.baseUrl("http://localhost:8848/")
                	.build();

        	GitHubService service = retrofit.create(<span style="font-family: Arial, Helvetica, sans-serif;">GitHubService.class</span><span style="font-family: Arial, Helvetica, sans-serif;">);</span>

        	// 演示 @Headers 和 @Header
        	Call<ResponseBody> call = service.testHeader("uana_777");
      		// 打印响应结果
		ResponseBodyPrinter.printResponseBody(call);
	}
}





import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.http.GET;
import retrofit2.http.Query;
import retrofit2.http.Url;

/**
 * [Retrofit注解详解 之 Query & QueryMap & Url 注解
 */
public class Example {
    public interface GitHubService{
        	/**
        	 * 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供
        	 * 对于Query和QueryMap,如果不是String(或Map的第个泛型参数不是String)时
        	 * 会被调用toString
        	 * Url支持的类型有 okhttp3.HttpUrl, String, java.net.URI, android.net.Uri
        	 * {@link retrofit2.http.QueryMap} 用法和{@link retrofit2.http.FieldMap} 用法一样,不再说明
         	 */
        	@GET //当有URL注解时,这里的URL就省略了
        	Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);

		@GET //当有URL注解时,这里的URL就省略了
        	Call<ResponseBody> testUrlAndQueryMap(@Url String url, @QueryMap("params") Map<String, Object> params);

		@GET("headers")
       	 	Call<ResponseBody> testQueryMap(@QueryMap("params") Map<String, Object> params);
		@GET("headers")
        	Call<ResponseBody> testQuery(@Query("showall") boolean showAll);
	}
	public void test() { 

		Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:8848/") .build();
		GitHubService service = retrofit.create(GitHubService.class);

		// 调用
Call<ResponseBody> call1 = service.testUrlAndQuery("headers",false);ResponseBodyPrinter.printResponseBody(call1);
	}
}















// ... 另外三个方法的测试就不在这里示例了,大家可以自己测试一下 }}
 
   
上面示例代码中,@Headers({"键 : 值", ...}) 参数数量不固定,也就是说可以添加多个。  @Header("键名") ,所对应的键值是以形参的方式传递过来的,只能对应一个形参。

注:{占位符}和@PATH尽量只在URL的path部分,url中的参数使用@Query和@QueryMap代替,这样做能保证接口定义的简洁。

注:@Query、@Field  和 @Part 这三者都支持数组和实现了 Iterable  接口类型的参数,比如:List、Set 等。

比如:Call<ResponseBody> test(@Query("ids[]") List<Integer> ids);

5、Gson与Converter

在默认情况下Retrofit只支持将HTTP 的响应体转换为ResponseBody,这也是为什么前面的例子中接口方法的返回值都是Call<ResponseBody>,泛型写的一直是ResponseBody,但是如果响应体只支持ResponseBody 的话为什么设计的时候要设计为泛型的方式呢??(这句话可能有些童鞋不理解,详细说一下,上面这句话意思就是说:如果Retrofit的请求响应体只支持ResponseBody的话,为什么设计Call<T> 为这种格式)。 既然这样设置,就说明Retrofit提供给我们的有类似转换器一类的东西,而这个东西就是Converter,Converter就是Retrofit为我们提供的用于将ResponseBody转换为我们需要的类型的工具。

下面我们写一个示例,看看Converter是如何使用的:

public interface GitHubService{
	@GET("user_info/{user_id}")  // 使用GET注解
	Call<Result<User>> getUserById(@Path("user_id") int uerId); // 设置返回值类型是Result<User> 类型

	@POST("create_user")
	Call<Result<User>> createUser(@Body User user);  // 被@Body注解的User会被Gson转换成RequesBody发送到服务器
}
注:只改变泛型的类型是不行的,我们在创建Retrofit 的时候要明确的告知用于将ResponseBody转换为我们想要的类型的时候需要使用到的Converter.

上面的这句话有点拗口,不过不妨事,下面我们通过示例代码看一下:

5.1 如果是AndroidStudio开发的话:在model 的 gradle文件中引入 : (其他的开发工具请自己搜索,这个很简单就不详细说了)

compile 'com.squareup.retrofit2:converter-gson:2.0.2'

5.2 通过GsonConverterFactory为Retrofit添加Gson支持:

// Gson 如果能做到全局只用一个最好(这个只是建议) 一般gson的创建会放在Application类中创建。
Gson gson = new GsonBuilder()
		// 配置Gson
		.setDataFormat("yyyy-MM-dd hh:mm:ss")
		.create();

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("http://localhost:8848/")
		// 在Retrofit 1 的时候可以设置这一步,1的时候默认有gson,2的时候就必须设置了。 这一步其实就是提供给Retrofit 使用这个gson对象将
		// ResponseBody转换为我们想要的类型
		.addConverterFactory(GsonConverterFactory.create(gson))
		.build();

5.3  调用

GitHubService service = retrofit.create(GitHubService.class);
User user = new User();
user.setName("uana_888");
user.setAge(25);
Call<Result<User>> call = service.createUser(user);

注:Gson的使用:http://www.jianshu.com/p/e740196225a4 这个链接内容写的真乃一绝,大家如果对Gson不了解,你就看看这个吧!会有用的


6、RxJava 与 CallAdapter

在实际开发中,Retrofit使用的过程中一定会用到RxJava库,网上已经有不少的文章介绍Retrofit 与 RxJava的结合。下面我们通过示例来看一下它们结合后的效果。

在上面我们介绍了Converter其实是对于Call<T> 中泛型T的转换,而CallaAdapter则是对Call的转换。其实意思就是对于接口的返回类型可以被替换(Call<T>中的Call和T都能被替换)。比如:开发中我们一般使用Observable类型代替Call。


6.1 引入RxJava 库


compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'

6.2 使用RxJavaCallAdapterFactory为Retrofit添加RxJava支持


Retrofit retrofit = new Retrofit.Builder()
	.baseUrl("http://localhost:8848/")
	.addConverterFactory(GsonConverterFactory.create())
	.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加RxJava支持, 这一行一定要放在最后添加
	.build();

6.3 接口设计


public interface GitHubService{
	@POST("/user")
	Observable<Result<List<User>>> getUsers();   // 返回值类型为 Observable 泛型为 List<User>类型
}

6.4 使用示例

GitHubService service = retrofit.create(GitHubService.class);
service.getUsers()
	.subscribeOn(Schedulers.io())
	.subscribe(new Subscriber<Result<List<User>>>(){
		@Override
		public void onCompleted(){
			// 完成
		}

		@Override
		public void onError(Throwable e){
			// 失败
		}

		@Override
		public void onNext(Result<List<User>> responseStr){
			// 成功
		}
});
注:上面的这种方式,我们是拿不到后台返回的Header和响应码
如果我们想使用响应的Header和响应码。可以使用下面的两种解决方案

01. 使用Observable<Response<T>>,这里的Response指的是retrofit2.Response。

02.使用Observable<Result<T>> 代替Observable<T>,这里的Result是retrofit2.adapter.rxjava.Result,这个Result中包含的是Response的实例。


7、自定义Converter

7.1 Converter接口及其作用:

public interface Converter<F, T>{
	// 实现从F类型到T类型的转换,F(from) T(To)
	T convert(F value) throws IOException;
	
	// 用于向Retrofit提供相应的Converter的工厂
	abstract class Factory {
		// 这里创建从ResponseBody其他类型的Converter,如果不能处理,就返回null
		// 主要用于响应体的处理
		public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit){
			return null;
		}

		// 在这里创建 从自定义类型到ResponseBody 的 Converter, 不能处理就返回null,
		// 主要用于对Part、 PartMap、Body注解的处理
		public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit){
			return null;
		}

		// 这里用于对Field、FieldMap、 Header、Path、Query、QueryMap注解的处理
		public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit){
			return null;
		}
	}

}
注:Retrofit 默认返回的类型是Call<T>类型。上面的接口,如果我们想从Call<ResponseBody>转换为Call<String> 那么我们需要将对应的F和T,分别写为ResponseBody和String,下面我们自定义一个StringConverter 。

7.2 自定义StringConverter

public class StringConverter implements Converter<ResponseBody, String> {
	
	// 创建单一实例
	public static final StringConverter instance = new StringConverter();

	// 重写转换的方法
	@Override
	public String convert(ResponseBody resBody) throws IOException{
		// resBody 非空
		return resBody.string();
	}

}

7.3 使用自定义的转换器

首先,我们需要一个 StringConverterFactory 来向Retrofit注册 StringConverter。我们先创建一个StringConverterFactory继承我们写的Converter接口中的抽象类Factory
public class StringConverterFactory extends Converter.Factory {
	
	public static final StringConverterFactory INSTANCE = new StringConverterFactory();

	public static StringConverterFactory create(){
		return INSTANCE;
	}

	//  我们只关心实现从ResponseBody到String类型 的转换,所以其他的方法可以不用重写
	// 这个方法 可以把ResponseBody类型的转换到其他类型
	@Override
	public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit){
		if(type == String.class){
			return StringConverter.INSTANCE;  // 将StringConverter注册进来
		}
		
		// 其他的不处理,我们直接返回null
		return null;
	}
}

然后,我们使用Retrofit.Builder.addConverterFactory 向 Retrofit 注册我们创建的StringConverterFactory:
Retrofit retrofit = new Retrofit.Builder()
	.baseUrl("http://localhost:8848/")
	.addConverterFactory(StringConverterFactory.create()) // 这行代码一定要放在 添加Gson类型相关的Converter之前
	.addConverterFactory(GsonConverterFactory.create())
	.build();
注:addConverterFactory(**) 是有先后顺序的,如果有多个ConverterFactory都支持同一个类型,那么就只有第一个有效(也就是说添加多个同一类型的转换器,只有第一个有效、会被使用)。 而 GsonConverterFactory是不判断是否支持的,所以上面的代码中的顺序,如果颠倒的话会有一个类型不匹配的异常。

只要返回值类型的泛型是我们StringConverter处理的类型,不管是Call<String> 还是Observable<String> 类型,我们自定义的StringConverterFactory都能处理。

最后我们看一下完整的使用案例:
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Converter;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Path;

/**
 * 自定义 Converter
 */
public class ExampleTest{

	// 我们还使用GitHubService,GitHubService中的方法的返回值假设是Call<String>
	private GitHubService  service;

	public void test(){
		Retrofit retrofit = new Retrofit.Builder()
			.baseUrl("http://localhost:8848/")
			.addConverterFactory(StringConverterFactory.create())
			.addConverterFactory(GsonConverterFactory.create())
			.build();

		service = retrofit.create(GitHubService.class);
		Call<String> call = service.getUsers();
		call.enqueue(new Callback<String>(){
			@Override
			public void onResponse(Call<String> call, Response<String> response){
				// 成功   
				response.body();
}@Overridepublic void onFailure(Call<String> call, Throwable t){// 失败}});}}





8、自定义CallAdapter

下面我们介绍一下CallAdapter,也验证一下上面写的,不论是Call<String> 或 Observable<String> 都能通过StringConverterFactory处理。
首先,我们先看一下CallAdapter接口的模样,顺便看一下接口定义的各方法的作用。
public interface CallAdapter<T> {

	//  真正的数据类型是 我们实现Adapter的时候 设置的泛型
	// 这个T会作为Converter.Factory.responseBodyConverter 的第一个参数
	// 可以参照上面定义的Converter
	Type responseType();

	<R>  T adapt(Call<R> call);

	/**
	 * 用于向Retrofit提供CallAdapter的工厂类
	 */
	abstract class Factory {
		// 在这个方法中判断是否是我们支持的类型,returnType 即Call<RequestBody> 和 Observable<RequestBody>中的ResponseBody
		// RxJavaCallAdapterFactory 就是判断returnType 是不是 Observable<?> 类型
		// 不支持返回null
		public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);

		// 用于获取泛型的参数, 比如:Call<RequestBody> 中的RequestBody
		protected static Type getParameterUpperBound(int index, ParameterizedType type) {
			return Utils.getParameterUpperBound(index, type);
		}

		// 获取泛型的原始类型, 比如:Call<RequestBody> 中的Call
		protected static Class<?> getRawType(Type type) {
			return Utils.getRawType(type);
		}
	
	}

}

然后,我们定义一个CustomCall,不过这里的CustomCall作为演示只是对Call类的包装,并没有实际的用途。
public class CustomCall<R> {
	
	public final Call<R> call;

	public CustomCall(Call<R> call){
		this.call = call;
	}

	public R get () throws IOException {
		return call.execute().body();
	}
}

然后,我们来实现一下自定义的CustomCallAdapter,实现Call<T> 到 CustomCall<T> 的转换,这里需要注意的是最后的泛型,也就是我们要返回的类型。
public class CustomCallAdapter implements CallAdapter<CustomCall<?>> {

	private final type responseType;
	
	// 下面的 responseType 方法需要数据的类型
	CustomCallAdapter(Type responseType){
		this.responseType = responseType;
	}

	/*
  	 * 重写responseType() 方法
	 */
	@Override
	public Type responseType(){
		return responseType;
	}

	/**
	 * 重写adapt()方法
	 */
	@Override
	public <R> CustomCall<R> adapt(Call<R> call) {
		// 由 CustomCall 决定如何使用
		return new CustomCall<>(call);
	}
}

adapter创建好了之后,使用之前我们需要创建一个CustomCallAdapterFactory 用于向Retrofit提供CustomCallAdapter:
public class CustomCallAdapterFactory extends CallAdapter.Factory {

	public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory();

	@Override
	public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit){
		
		// 获取原始类型
		Class<?> rawType = getRawType(returnType);
		
		// 返回值必须是CustomCall并且带有泛型
		if(rawType == CustomCall.class && returnType instanceof ParameterizedType){
			Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);
			return new CustomCallAdapter(callReturnType);
		}
		
		return null;
	}
}

最后我们将CustomCallAdapterFactory注册到Retrofit中。
Retrofit retrofit = new Retrofit.Builder()
	.baseUrl("http://localhost:8848/")
	.addConverterFactory(StringConverterFactory.create())
	.addConverterFactory(GsonConverterFactory.create())
	.addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)  // 注册 CustomCallAdapter
	.build();

注:addCallAdapterFactory 和 addConverterFactory同理,也有先后顺序。

使用CustomCallAdapter 完整示例:
public class ExampleCallAdapteTest{
	
	private GitHubService service; // 别忘记在接口中新增一个返回值类型是CustomCall<String> 类型的方法getUsers

	
	public void test () {
		Retrofit retrofit = new Retrofit.Builder()
			.baseUrl("http://localhost:8848/")
			.addConverterFactory(StringConverterFactory.create())
			.addConverterFactory(GsonConverterFactory.create())
			.addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)
			.build();

		service = retrofit .create(GitHubService.class);
		
		// Call<String> call = service.getUsers();
		CustomCall<String> call = service.getUsers();
		
		try{
			// String result = call.execute().body();
			String result = call.get();
		} catch (Exception e) {

			e.printStackTrace();
		}
	}

}

以上所有就是我们平时使用Retrofit 2 的时候需要掌握的内容。祝大家工作顺利!!!(注:以上所有是通过官网和网上的一些资源文件整理的)



下面我们在补充点东西:

我们先介绍一下:Retrofit 

Retrofit  是Square 公司开发的一款针对 Android 网络请求的框架,Retrofit 2 底层基于OkHttp实现的。OkHttp现在已经得到了Google官方的认可。有关OkHttp的源码,请看这里 --- OkHttp 源码

使用的时候我们一般引入三个库:
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'

使用Call实例完成同步和异步请求

同步请求:
Call<String> call = service.getUser();
    String resultStr = call.execute().body();
注:需要注意的是,网络请求一定要在子线程中完成,不能直接在UI线程中执行,不然会Crash

异步请求:
call.enqueue(new Callback<BookSearchResponse>(){

	@Override
	public void onResponse(Call<String> call, Response<String> response){
		// 获取请求结果
		String resultStr = response.body();
	}

		@Override
public void onFailure(Call<BookSearchResponse> call, Throwable t){
}
});

取消请求:调用取消方法的前提条件是该请求还没有执行。
call.cancel();


添加请求参数的方式:

第一种:上文中已经提过,就是直接在interface 中添加@Query 、@QueryMap等注解添加请求参数;

第二种:通过Interceptor 实现,下面我们直接上代码:

  
  
public class CustomInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl httpUrl = request.url()
.newBuilder()
.addQueryParameter("token", "tokenValue")
.build();
request = request.newBuilder()
.url(httpUrl)
.build();
return chain.proceed(request);
}
}


创建完自定义的Interceptor,还需要在创建 OkHttpClient 实例的时候,通过addInterceptor(new CustomInterceptor()) 将自定义的拦截器(Interceptor)添加进来。
		private static OkHttpClient getNewClient(){
    			HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); // 网络请求日志
    			logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    			return new OkHttpClient.Builder()
           			.addInterceptor(new CustomInterceptor())
           			.addInterceptor(logging)
           			.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
           			.build();
		}
注:上面的网络请求日志,调试网络请求的时候经常需要关注一下请求参数和返回值,以便判断和定位问题出在哪里,Retrofit官方提供了一个很方便查看日志的Interceptor,你可以控制你需要的打印信息类型,使用方法也很简单。

网络请求日志库文件地址:
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'

添加请求头的方式:

第一种:通过@Header 或 @Headers 注解设置请求头

	@Header("Content-type : audio/wav")
	@Headers("Content-type : audio/wav", "Authorization : token")
	Observable<Result<List<User>>> getUser();		  
第二种:通过Interceptor来定义请求头
	public class RequestInterceptor implements Interceptor {
	
		@Override
		public Response intercept(Chain chain) throws IOException {
			Request original = chain.request();
			Request request = original.newBuilder()
				.header("User_Agent", "app_name")
				.header("Accept", "application/vnd.yourapi.v1.full+json")
				.method(original.method(), original.body())
				.build();
			return chain.proceed(request);
		}

	}
注:添加header参数Request提供了两个方法,一个是header(key, value),另一个是.addHeader(key, value),两者的区别是,header()如果有重名的将会覆盖,而addHeader()允许相同key值的header存在

然后在创建OkHttp创建实例的时候,添加RequestInterceptor即可
private static OkHttpClient getNewClient(){
  return new OkHttpClient.Builder()
    .addInterceptor(new RequestInterceptor())
    .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
    .build();
}

上传操作:

1、定义接口


  
  
public interface FileUploadService {
// 上传单个文件
@Multipart @POST("upload") Call<ResponseBody> uploadFile(@Part("description") RequestBody description, @Part MultipartBody.Part file); // 上传多个文件 @Multipart @POST("upload") Call<ResponseBody> uploadMultipleFiles(@Part("description") RequestBody description, @Part MultipartBody.Part file1, @Part MultipartBody.Part file2); 
}
2、定义工具类
public class MultiplePartUtil {

	private static final String MULTIPART_FORM_DATA = "mutilpart/form-data";
	
	private Context context;

	public MultiplePartUtil(Context context){
		this.context = context;
	}

	@NonNull
	private RequestBody createPartFormString(String descriptionString){
		return RequestBody.create(MediaType.parse(MULTIPART_FORM_DATA), descriptionString);
	}

	@NonNull
	private MultipartBody.Part prepareFilePart(String partName, Uri fileUri) {

		File file = FileUtils.getFile(context, fileUri);
		// 为file建立RequestBody实例
		RequestBody requestFile = RequestBody.create(MediaType.parse(MULTIPART_FORM_DATA), file);

		// MultipartBody.Part借助文件名完成最终的上传
		return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);
	}
}
3、最后我们,上传一下文件:
public class TestActivity extends BaseActivity {
	
	private Uri file01Url = ...; // 从文件选择器或者摄像头中获取
	private Uri file02Url = ...; // 
	private MultiplePartUtil partUtil = new MultiplePartUtil(this); 

	// 创建上传的service实例
	private FileUploadService service = ServiceGenerator.createService(FileUploadService.class);

	// 创建文件 Part (可以是photo、video、doc、amr等)
	MultipartBody.Part body1, body2; 

	//
	private RequestBody requestBody;
	
	@Override
    	protected void onCreate(Bundle savedInstanceState) {
        	super.onCreate(savedInstanceState);
        	body1 = partUtil.prepareFilePart("video", file01Uri);
		body2 = partUtil.prepareFilePart("thumbnail", file02Uri);
		
		requestBody = partUtil.createPartFromString("hello, test");

		Call<ResponseBody> call = service.uploadMultipleFiles(description, body1, body2);
		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());
    			}
		});
    }
}

Retrofit.Builder的其他方法:

上面在创建Retrofit 实例的时候已经提到了 baseUrl()、 addCallAdapterFactory、addConverterFactory()、build()等方法。除了前面说的这些方法,剩下的还有四个方法没有用到。这里介绍一下:
=========================================================================================
方法 用途
-------------------------------------------------------------------------------------------------------------------------------------------------------------
callbackExecutor(Executor)                 用于指定Call.enqueue()时使用的Executor,所以该方法的设置只对返回值为Call<T>类型的方法有效
-------------------------------------------------------------------------------------------------------------------------------------------------------------
callFactory(Factory)   用于设置一个自定义的okhttp3.Call.Factory,这个方法执行后OkHttpClient就实现了okhttp3.Call.Factory接口,
下面的client(OkHttpClient)   方法最终也是调用了该方法,所以此方法和client(OkHttpClient)方法不能共用。
-------------------------------------------------------------------------------------------------------------------------------------------------------------
client(OkHttpClient)    设置自定义的OKHttpClient,Retrofit 1的实例对象共用同一个OkHttpClient,在Retrofit 2的实例对象各自
持有不同的OkHttpClient实例, 所以当你需要共用OkhttpClient或需要自定义时则可以使用该方法,比如处理Cookie等
-------------------------------------------------------------------------------------------------------------------------------------------------------------
validateEagerly(boolean)       是否在调用create(Class)的时候检测接口定义是否正确,而不是在调用该方法的时候才检测,适合在开发、测试时使用。
=========================================================================================


Retrofit的Url组合规则:








另:附上学习资源链接:


Retrofit 官方链接:






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值