Retrofit2.1.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 官方鏈接:






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章