注意
Retrofit 2.0+和Retrofit 2.0之前的版本語法上有差別,本文基于Retrofit2.1.0
什么是Retrofit?
retrofit是一款針對Android網絡請求的開源框架,它與okhttp一樣出自Square公司。Rotrofit2.0的網絡框架全部交給了okhttp來實現,Android N之后Apache的httpclient已經被Google從SDK中移除,Okhttp則成功上位。Retrofit的網絡請求實現風格與URLconnection和httpClient有較大的差別。創建請求的時候需要先創建基于注解的服務接口(不了解的可以先了解一下注解),進行網絡請求的時候再通過retrofit.creat()
方法創建請求。
Retrofit中http POST/GET請求
Retrofit中的網絡請求都是通過注解方式的接口方法來表示的,此處只對常用的post和get請求進行說明,Retrofit還提供有put,delete等請求方式可自己研究文檔使用
post請求
- Body對象作為post參數
@POST("user/login")
Call<User> login(@Body LoginInfo loginInfo);
- Field方式
@FormUrlEncoded
@POST("user/login")
Call<User> login(@Field("username") String username,
@Field(password) String password);
- FieldMap方式
@FormUrlEncoded
@POST("user/login")
Call<User> login(@FieldMap Map<String,String> map);
參數較多時建議用Body方式和FieldMap方式
get請求
- 直接請求某一地址獲取列表
//接口我瞎寫的
@GET("news/toplist")
Call<ArrayList<News> news> getNewsList();
- url拼接固定查詢條件
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList();
- url中拼接地址信息
@GEt("news/{city}/newslist")
Call<ArrayList<News> news> getCityNewsList(@Path("city") String city);
- 通過Query注解添加其他查詢條件
@GET("news/{city}/newslist")
Call<ArrayList<News> news> getCityNewsList(@Path("city") String city
@Query("date") String date
@Query("newsType") String newsType);
- 查詢條件較多時同樣有QueryMap注解方法供使用
@GET("news/{city}/newslist")
Call<ArrayList<News> news> getCityNewsList(@Path("city") String city
@QueryMap<String, String> options);
通過上面的API方法會發現都是在進行請求條件的配置,假如我要給請求加請求頭怎么辦?放心,retrofit也有相應的注解。除了注解之外還有一個萬用的處理方法。
Header請求頭設置
- 為請求添加固定請求頭
//添加單個固定請求頭
@Header("Cache-Control: max-aget-640000")
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList();
//多個請求頭以數組的形式提交
@Header(
{"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList();
- 動態添加請求頭
//添加動態請求頭,比如獲取的認證信息等
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList(@Header(Authorization) String authorization);
上面的兩種添加請求頭的方法作用范圍只是添加注解的單個方法,如果想為每個請求都添加請求頭還按這種方式來做的話就很不程序猿了。Retrofit的網絡請求全部交給okhttp來處理,因此我們可以通過OkHttpClient
來做文章,自己重寫okhttp的攔截器在攔截器內再進行需要的操作
Okhttp interceptor
攔截器顧名思義,所有通過okhttp進行的請求都會過一遍okhttpClient的攔截器,發出去的請求,收到的響應都會經過他,就像一個雙向的安檢通道。
okhttp攔截器的原理如下:

如圖所示攔截器分為Application Interceptors和NetWork Interceptors。Application攔截器工作區域為應用發出請求到okhttp核心之間,遠端響應經過okhttp核心后到達應用處理之前。而NetWork攔截器的作用域為okhtt核心到遠端服務器之間的部分。明顯區別就是當一次請求中會有一個重定向的時候Application攔截器只會響應一次,因為對于應用來說就進行了一次請求。而NetWork攔截器會在重定向時也響應即響應兩次,也不難理解,畢竟重定向也會經過一次okhttp核心嘛。

上圖是okhttp攔截器工作原理簡圖,重點在右邊部分。當多個攔截器配合使用時,不用擔心請求攔截和響應攔截順序會錯亂,okhttp已經給你排好了。

上傳個需要壓縮和編碼的東東的時候,你可以選擇先寫個攔截器請求時壓縮響應時解壓,再寫個攔截器請求時編碼響應時解碼。加起來就是
壓縮
->編碼
->okhttClient與服務器的Py交易
->解碼
->解壓
跟棧先進后出類似。
原理扯了一大堆,代碼才是干貨,看了代碼才知道怎么用。
//官方的栗子
class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
//拿到request實例在此對請求做需要的設置
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
//發送request請求
Response response = chain.proceed(request);
//得到請求后的response實例,做相應操作
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
通過Request request = chain.request();
拿到請求實例,想怎么裝扮就怎么裝扮,什么加請求頭,設置編碼格式soeasy。前面說到的為每個請求設置請求頭就是在這完成設置工作的。但是真正要加到請求里跟retrofit的ApiService接口一起用還需要將Okhttp注冊攔截器后與Retrofit綁定才行。
//注冊應用攔截器
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();··
Response response = client.newCall(request).execute();
response.body().close();
//注冊網絡攔截器
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
該如何使用Retrofit?
基本也說得差不多了,那么怎么使用Retrofit進行一次完整的網絡請求呢
需要注意一下Retrofit的Url拼接規則



個人建議以第一幅圖的方式,baseUrl總是以
/
結尾,接口rul總是不以/
開頭
- 1、當然是引入retrofit的庫啦
//build.gradle的依賴中加入,其中第二條不一定要使用gson,其他方式在官方的github上也有
// retrofit
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'
- 2、創建一個ServiceApi接口
//方便后面RxAndroid我把RxAndroid方式的接口也貼上來。只有返回類型不同而已
public interface RetrofitService {
//單純使用retrofit接口定義
@GET("news/latest")
Call<ZhiHuDaily> getZhihuDailyRetrofitOnly();
//使用retrofit+RxAndroid的接口定義
@GET("news/latest")
Observable<ZhiHuDaily> getZhihuDaily();
}
- 3、我建議是維護一個統一的api管理類。當然你要直接拿接口用也行,但可維護性會降低很多
public class ApiManager {
private RetrofitService mDailyApi;
private static ApiManager sApiManager;
//獲取ApiManager的單例
public static ApiManager getInstence() {
if (sApiManager == null) {
synchronized (ApiManager.class) {
if (sApiManager == null) {
sApiManager = new ApiManager();
}
}
}
return sApiManager;
}
/**
* 封裝配置知乎API
*/
public RetrofitService getDailyService() {
//不需要使用攔截器就不創建直接從if開始
OkHttpClient client = new OkHttpClient.Builder()
//添加應用攔截器
.addInterceptor(new MyOkhttpInterceptor())
//添加網絡攔截器
// .addNetworkInterceptor(new MyOkhttpInterceptor())
.build();
if (mDailyApi == null) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(GlobalConfig.baseUrl)
//將client與retrofit關聯
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
//到這一步創建完成
mDailyApi = retrofit.create(RetrofitService.class);
}
return mDailyApi;
}
}
- 4、調用接口方法進行網絡請求
public void getStoryDataByRetrofit(final OnEventLister<ArrayList<ZhihuStory>> eventLister) {
ApiManager apiManager = ApiManager.getInstence();
Call<ZhiHuDaily> call = apiManager.getDailyService().getZhihuDailyRetrofitOnly();
//發送異步請求
call.enqueue(new Callback<ZhiHuDaily>() {
@Override
public void onResponse(Call<ZhiHuDaily> call, Response<ZhiHuDaily> response) {
eventLister.onSuccess(response.body().getStories());
}
@Override
public void onFailure(Call<ZhiHuDaily> call, Throwable t) {
eventLister.onFail(t.getMessage(), "");
}
});
}
使用Retrofit的好處
- 可以少寫不少的代碼
- 接口方便維護需要改什么直接到
ApiService
中進行配置即可 - 異步請求不再需要自己來newThread再handler,也不需要自己再來寫請求結果回調。異步請求只需要使用
call.enqueue()
即可。 - 支持RxAndroid,這個我覺得很重要
- 降低工程的耦合度,網絡請求跟邏輯代碼完全剝離開。需要的僅僅是傳遞參數有的請求甚至參數都不需要傳遞。直接在接口中配置就好。
Retrofit的好基友——RxAndroid
RxAndroid是RxJava在Android上的變種。那么RxJava到底是什么呢?
"a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一個在 Java VM 上使用可觀測的序列來組成異步的、基于事件的程序的庫)。這是github項目主頁的自我概括,我覺得其實就兩個關鍵詞,異步
、基于事件
。這里我只說一下RxAndroid怎么跟Retrofit搭配使用,要進一步了解可以異步扔物線大神的文章給 Android 開發者的 RxJava 詳解。講的肯定比我好,我就是看這個入門的。
Retrofit+RxAndroid使用
因為用到Retrofit所以定義接口,創建ApiManager這些跟上面單純用Retrofit是一毛一樣的,唯一的不同是接口的返回類型從Retrofit的Call
對象變成了Observable
對象,即被觀察者對象。然后就是調用進行網絡請求部分變成如下形式
//使用rxandroid+retrofit進行請求
public void loadDataByRxandroidRetrofit() {
mIMainActivity.showProgressBar();
Subscription subscription = ApiManager.getInstence().getDailyService()
.getZhihuDaily()
.map(new Func1<ZhiHuDaily, ArrayList<ZhihuStory>>() {
@Override
public ArrayList<ZhihuStory> call(ZhiHuDaily zhiHuDaily) {
return zhiHuDaily.getStories();
}
})
//設置事件觸發在非主線程
.subscribeOn(Schedulers.io())
//設置事件接受在UI線程以達到UI顯示的目的
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ArrayList<ZhihuStory>>() {
@Override
public void onCompleted() {
mIMainActivity.hidProgressBar();
}
@Override
public void onError(Throwable e) {
mIMainActivity.getDataFail("", e.getMessage());
}
@Override
public void onNext(ArrayList<ZhihuStory> stories) {
mIMainActivity.getDataSuccess(stories);
}
});
//綁定觀察對象,注意在界面的ondestory或者onpouse方法中調用presenter.unsubcription();進行解綁避免內存泄漏
addSubscription(subscription);
}
網絡請求得到的是一個Obserable
對象,該對象再通過subscrible()
綁定一個觀察者對象,觀察者對象中有onCompleted()
,onError()
,onNext()
三個回調方法。事件過程中出錯onError()觸發并停止后續事件,一個Obserable對象一次發出多個事件每次都會觸發onNext(),當不再有事件發出的時候onCompleted()方法觸發并結束。異步在RxAndroid中變得很簡單subscribeOn
指定事件發生線程,如上面的網絡請求被指定在io線程中,observeOn
指定事件的消費線程,如上面的知乎故事數據結果被交給主線程顯示。
RxAndroid的優點
看完上面單獨使用retrofit和使用retrofit+rxandroid兩種方式之后你也許會吐槽,尼瑪腦子有坑?明明代碼變多了。
但是你也會發現代碼中都是.XX()的形式如果需求變化更多一些會更明顯。比如說就上面的例子我要在每一條信息中修改某一個值。并且又要對結果進行一些篩選。只用retrofit的話是不是應該向這樣:
call.enqueue(new Callback<ZhiHuDaily>() {
@Override
public void onResponse(Call<ZhiHuDaily> call, Response<ZhiHuDaily> response) {
ArrayList<ZhihuStory> stories = response.body().getStories();
for(ZhihuStory story : stories){
//修改每一條story中的某一值,這里用XXX代替
story.setXXX(XXX);
//篩選出id<100的
if(story.getId()>100){
stories.remove(story);
}
}
eventLister.onSuccess(response.body().getStories());
}
@Override
public void onFailure(Call<ZhiHuDaily> call, Throwable t) {
eventLister.onFail(t.getMessage(), "");
}
});
如果需要設置和條件賽選層次越多會發現for套if,if再if會越嵌套越多。隔一段時間之后就真的成了“當初寫下這段代碼的時候只有我跟上帝知道他是干嘛的,現在只有上帝知道他是干嘛的”。而使用RxAndroid的話整個變換過程都是線性的哪一步做了什么都會很清楚不會出現各種蜜汁縮進:
Subscription subscription = ApiManager.getInstence().getDailyService()
.getZhihuDaily()
//從ZhihuDaily中獲取Stories列表
.map(new Func1<ZhiHuDaily, ArrayList<ZhihuStory>>() {
@Override
public ArrayList<ZhihuStory> call(ZhiHuDaily zhiHuDaily) {
return zhiHuDaily.getStories();
}
})
//將列表拆開成事件發送
.flatMap(new Func1<ArrayList<ZhihuStory>, Observable<ZhihuStory>>() {
@Override
public Observable<ZhihuStory> call(ArrayList<ZhihuStory> stories) {
return Observable.from(stories);
}
})
//將story中的XXX設置為xxx
.map(new Func1<ZhihuStory, ZhihuStory>() {
@Override
public ZhihuStory call(ZhihuStory zhihuStory) {
zhihuStory.setXXX(xxx);
return zhihuStory;
}
})
//過濾掉Id>10的story
.filter(new Func1<ZhihuStory, Boolean>() {
@Override
public Boolean call(ZhihuStory zhihuStory) {
return zhihuStory.getId()<10;
}
})
//將結果重新整理成List
.toList()
//設置事件觸發在非主線程
.subscribeOn(Schedulers.io())
//設置事件接受在UI線程以達到UI顯示的目的
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<ZhihuStory>>() {
@Override
public void onCompleted() {
mIMainActivity.hidProgressBar();
}
@Override
public void onError(Throwable e) {
mIMainActivity.getDataFail("", e.getMessage());
}
@Override
public void onNext(List<ZhihuStory> stories) {
mIMainActivity.getDataSuccess((ArrayList<ZhihuStory>) stories);
}
});
越復雜的邏輯,Rx的優勢也就越明顯。Rx的操作符各種組合起來幾乎能夠滿足所有的變換需求。開始寫可能會覺得很不適應,但熟練使用之后會默念Rx大法好的。
對于RxJava操作符鼠標懸停都會有文字和示意圖的,另外發現一個不錯的博客里面也有較詳細的解析RxJava/RxAndroid操作符
Demo地址
在之前的那個MVPDemo的基礎上寫的,okhttp請求方式,單純retrofit方式,retrofit+rxAndroid方式的請求都有保留,可以對比著感受一下。
demo倉庫地址