1. 程式人生 > >淺談Android網路封裝框架Retrofit

淺談Android網路封裝框架Retrofit




在對Android 開發中,我們都是從原生的 HttpUrlConnection到經典的 Apache公司的HttpClient,再到對前面這些網路基礎框架的封裝(比如VolleyAsyncHttpClient等)。Http請求相關開源框架還是很多的,今天我們講解 Square 公司開源的Retrofit。Square 公司的框架總是一如既往的簡潔優雅!Retrofit更是以其簡易的介面配置、強大的擴充套件支援、優雅的程式碼結構受到大家的追捧。 
Retrofit是一個 RESTful 的 HTTP 網路請求框架的封裝。注意這裡並沒有說它是網路請求框架,主要原因在於網路請求的工作並不是Retrofit
來完成的。Retrofit2.0 開始內建OkHttp,前者專注於介面的封裝,後者專注於網路請求的高效,二者分工協作!
我們的應用程式通過 Retrofit請求網路,實際上是使用Retrofit介面層封裝請求引數、Header、Url 等資訊,之後由OkHttp完成後續的請求操作,在服務端返回資料之後,OkHttp將原始的結果交給Retrofit,後者根據使用者的需求對結果進行解析的過程。
Retrofit的使用就像它的編碼風格一樣,非常簡單,首先你需要在你的 build.gradle 中新增依賴:
compile 'com.squareup.retrofit2:retrofit:2.0.2'
加入我們想要訪問 GitHub 的 api 對,那麼我們就定義一個介面:
介面當中的listRepos方法,就是我們想要訪問的api了,在發起請求時,{user}會被替換為方法的第一個引數user。
Retrofit支援的協議包括GET/POST/PUT/DELETE/HEAD/PATCH,當然你也可以直接用HTTP來自定義請求。這些協議均以註解的形式進行配置例如下面GET:
public interface GitHubService { 
	@GET("users/{user}/repos") 
	Call<List<Repo>> listRepos(@Path("user") String user); 
}
這些註解都有一個引數 value,用來配置其路徑,比如示例中的 users/{user}/repos,我們還注意到在構造Retrofit之時我們還傳入了一個baseUrl("https://api.github.com/"),請求的完整 Url 就是通過baseUrl與註解的value(下面稱 “path“ ) 整合起來的,具體整合的規則如下:
path是絕對路徑的形式:
path = "/apath",baseUrl = "http://host:port/a/b"
Url = "http://host:port/apath"


path是相對路徑,baseUrl是目錄形式:
path = "apath",baseUrl = "http://host:port/a/b/"
Url = "http://host:port/a/b/apath"


path是相對路徑,baseUrl是檔案形式:
path = "apath",baseUrl = "http://host:port/a/b"
Url = "http://host:port/a/apath"


path是完整的 Url:
path = "http://host:port/aa/apath",baseUrl = "http://host:port/a/b"
Url = "http://host:port/aa/apath"

建議採用第二種方式來配置,並儘量使用同一種路徑形式。

構造 Retrofit:

Retrofit retrofit = new Retrofit.Builder.baseUrl("https://api.github.com/").build;
GitHubService service = retrofit.create(GitHubService.class);
//service發起請求
Call<List<Repo>> repos = service.listRepos("octocat");

返回的repos其實並不是真正的資料結果,它更像一條指令,你可以在合適的時機去執行它

Lisg<Repo> data = repos.execute();//同步呼叫
repos.enqueue(new Callback<List<repo>>(){
	@Override
	public void onResponse(Call<List<rpo>> call, Response<List<Repo>> response){
		List<Repo> data = response.body();
	}	
	@Override
	public void onFailure(Call<List<Repo>> call,Throwable t){
		t.printStackTrace();
	}
});


認識Query & QueryMap

@GET("/list") Call<ResponseBody> list(@Query("page") int page);
Query其實就是 Url 中 ‘?’ 後面的 key-value,比如:這裡的 cate=android就是一個Query,而我們在配置它的時候只需要在介面方法中增加一個引數,即可:
interface PrintlnServer{
	@GET("/") 
	Call<String> cate(@Query("cate") String cate); 
}
當面臨引數很多的情況,我們就採用QueryMap!

認識Field & FieldMap

@FormUrlEncoded 
@POST("/") 
Call<ResponseBody> example( @Field("name") String name, @Field("occupation") String occupation);
我們用 Field聲明瞭表單的項,這樣提交表單就跟普通的函式呼叫一樣簡單直接了,表單項不確定個數就試用FieldMap。

認識Part & PartMap

public interface FileUploadService { 
	@Multipart @POST("upload") 
	Call<ResponseBody> upload(@Part("description") RequestBody description, @Part MultipartBody.Part file); 
}

如果你需要上傳檔案,和我們前面的做法類似,定義一個介面方法,需要注意的是,這個方法不再有 @FormUrlEncoded這個註解,而換成了@Multipart,後面只需要在引數中增加Part就可以了。也許你會問,這裡的Part和Field究竟有什麼區別,其實從功能上講,無非就是客戶端向服務端發起請求攜帶引數的方式不同,並且前者可以攜帶的引數型別更加豐富,包括資料流。也正是因為這一點,我們可以通過這種方式來上傳檔案,下面我們就給出這個介面的使用方法:

//先建立Service
FileUploadService service = retrofit.create(FileUploadService.class);
//構建要上傳的檔案
File file = new File(filename);
RequestBody requestFile = RequestBody.crate(MediaType.parse("application/otcet-stream"),file);
MultipartBody.Part body = MultipartBody.Part.crateFormData("aFile",file.getNmae(),requestFile);
String description = RequestBody.create(MediaType.parse("multipart/form-data"),descriptionString);
Call<ResponseBody> call = service.upload(description,body);
call.enqueue(new Callback<ResponseBody>(){
	@Override
	public void onResponse(Call<ResponseBody call,Response<ResponseBody> response){
		Log.d("success","success");
	}
	@Override
	public void onFailure(Call<ResponseBody> call,Throwable t){
		t.printStackTrace();
	}
});
如果你需要上傳多個檔案,就宣告多個Part引數,或者試試PartMap!
上面提供的上傳檔案的方式前後構造了三個物件:File-->RquestBody-->MultipartBody.part看起來其實是非常複雜的。
實際上Retrofit允許我們自己定義入參和返回的型別,不過,如果這些型別比較特別,我們還需要準備相應的 Converter,也正是因為 Converter 的存在,Retrofit在入參和返回型別上表現得非常靈活。該如何做呢,請看下面:

public interface FileUploadService { 
	@Multipart @POST("upload") 
	Call<ResponseBody> upload(@Part("description") RequestBody description, //注意這裡的引數 "aFile" 之前是在建立 MultipartBody.Part 的時候傳入的 @Part("aFile")
	File file); 	
}
static class FileRequestBodyConverterFactory extends Converter.Factory { 
	@Override
	public Converter<File, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 
		return new FileRequestBodyConverter; 
	} 
} 
static class FileRequestBodyConverter implements Converter<File, RequestBody> { 
	@Override 
	public RequestBody convert(File file) throws IOException { 
		return RequestBody.create(MediaType.parse("application/otcet-stream"), file); 
	} 
}
在建立 Retrofit的時候記得配置上它,這樣,我們的檔案內容就能上傳了:
addConverterFactory(new FileRequestBodyConverterFactory)
注意:Retrofit在選擇合適的 Converter 時,主要依賴於需要轉換的物件型別,在新增 Converter 時,注意 Converter 支援的型別的包含關係以及其順序。

總結上面的技術知識,我們來看完整的請求:
前面我們已經看到 Retrofit為我們構造了一個OkHttpCall,實際上每一個OkHttpCall都對應於一個請求,它主要完成最基礎的網路請求,而我們在介面的返回中看到的 Call 預設情況下就是OkHttpCall了,如果我們添加了自定義的callAdapter,那麼它就會將OkHttp適配成我們需要的返回值,並返回給我們。
先看下call介面程式碼:

public interface Call<T> extends Cloneable { 
	//同步發起請求 
	Response<T> execute throws IOException; 
	//非同步發起請求,結果通過回撥返回 
	void enqueue(Callback<T> callback); 
	boolean isExecuted; 
	void cancel; 
	boolean isCanceled; 
	Call<T> clone; 
	//返回原始請求 
	Request request;
}
接下來執行repos其實就是一個OkHttpCall例項,execute就是要發起網路請求:
Call<List<Repo>> repos = service.listRepos("octocat");
List<Repo> data = repos.execute;
parseResponse主要完成了由okhttp3.Response向retrofit.Response的轉換,同時也處理了對原始返回的解析:
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException { 
	ResponseBody rawBody = rawResponse.body; //略掉一些程式碼 
	try { 
		//在這裡完成了原始 Response 的解析,T 就是我們想要的結果,比如 GitHubService.listRepos 的 List<Repo> T body = serviceMethod.toResponse(catchingBody); 
		return Response.success(body, rawResponse); 
	} catch (RuntimeException e) { 
		// If the underlying source threw an exception, propagate that rather than indicating it was // a runtime exception. catchingBody.throwIfCaught; 
		throw e; 		
	} 
}
處理結果時我想要接入 RxJava,讓介面的返回結果改為 Observable:
public interface GitHub { 
	@GET("/repos/{owner}/{repo}/contributors") 
	Observable<List<Contributor>> contributors( @Path("owner") String owner, @Path("repo") String repo); 
}
只需要提供一個 Adapter,將 OkHttpCall轉換為Observable即可。Retrofit提供了相應的 Adapter(RxJavaCallAdapterFactory)我們只需要在構造 Retrofit時,新增它:
addCallAdapterFactory(RxJavaCallAdapterFactory.create)
我們來看看RxJavaCallAdapterFactory是如何工作的:
我們只需要實現 CallAdapter類來提供具體的適配邏輯,並實現相應的Factory,用來將當前的CallAdapter註冊到Retrofit當中,並在Factory.get方法中根據型別來返回當前的CallAdapter即可。知道了這些,我們再來看RxJavaCallAdapterFactory:

/**
*  只給大家列出來比較重要的程式碼段
*/
public final class RxJavaCallAdapterFactory extends CallAdapter.Factory{
		@Override
		public CallAdapger<?> get(Type returnType,Annotation[] annotations,Retrofit retrofit){
			//判斷returnType是否為RxJava支援的型別
			Class<?> rawType = getRawType(returnType);
			String CanonicalName = RawType.getCanonicalName();
			boolean isSingle = "rx.Single".equals(CanonicalName);
			boolean isCompletable = "rx.Completable".equals(CanonicalName);
			if(rawType != Observable.class && !isSingle && !isCompletable){
				return null;
			}
			return Adapter;//"獲取你需要的Adapter 返回"
		}

		static final class SimpleCallAdapter implements CallAdapter<Ovservable<?>>{
			private final Type responseType;
			private final Scheduler scheduler;

			SimpleCallAdpter(Type responseType ,Scheduler scheduler){
				this.responseType = responseType;
				this.scheduler = scheduler;
			}

			@Override
			public Type responseType(){
				return responseType;
			}
			
			@Override
			public <R> Observable<R> adapt(Call<R> call){
				//在這裡建立需要作為返回值的Observable例項,並持有call例項,所以在Observable.subscribe觸發時,call.execute將會被呼叫
				Observable<R> observable = Observable.create(new CallOnSubscribe<>(call)).lift(OperatorMapResponseToBodyOrError.<R>instance());
				if(scheduler != null){
					return observable.subscribeOn(scheduler);
				}
				return observable;
			}
		}
}

總結:Retrofit是非常強大的,部落格中通過一些示例向大家展示了Retrofit自身強大的功能以及擴充套件性,就算它本身功能不能滿足你的需求,你也可以很容易的進行改造。這就是好的程式碼設計,能夠讓其拓展性強大到只有你想不到,沒有它做不到的境界!