注意
Retrofit 2.0+和Retrofit 2.0之前的版本語法上有差別逞敷,本文基于Retrofit2.1.0
什么是Retrofit?
retrofit是一款針對Android網(wǎng)絡(luò)請求的開源框架蝶涩,它與okhttp一樣出自Square公司琳省。Rotrofit2.0的網(wǎng)絡(luò)框架全部交給了okhttp來實現(xiàn)姐军,Android N之后Apache的httpclient已經(jīng)被Google從SDK中移除亭引,Okhttp則成功上位吧恃。Retrofit的網(wǎng)絡(luò)請求實現(xiàn)風(fēng)格與URLconnection和httpClient有較大的差別嫡良。創(chuàng)建請求的時候需要先創(chuàng)建基于注解的服務(wù)接口(不了解的可以先了解一下注解)袜炕,進(jìn)行網(wǎng)絡(luò)請求的時候再通過retrofit.creat()
方法創(chuàng)建請求本谜。
Retrofit中http POST/GET請求
Retrofit中的網(wǎng)絡(luò)請求都是通過注解方式的接口方法來表示的,此處只對常用的post和get請求進(jìn)行說明,Retrofit還提供有put偎窘,delete等請求方式可自己研究文檔使用
post請求
- Body對象作為post參數(shù)
@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);
參數(shù)較多時建議用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方法會發(fā)現(xiàn)都是在進(jìn)行請求條件的配置乌助,假如我要給請求加請求頭怎么辦?放心陌知,retrofit也有相應(yīng)的注解他托。除了注解之外還有一個萬用的處理方法。
Header請求頭設(shè)置
- 為請求添加固定請求頭
//添加單個固定請求頭
@Header("Cache-Control: max-aget-640000")
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList();
//多個請求頭以數(shù)組的形式提交
@Header(
{"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList();
- 動態(tài)添加請求頭
//添加動態(tài)請求頭仆葡,比如獲取的認(rèn)證信息等
@GET("news/toplist?date=20161030")
Call<ArrayList<News> news> getNewsList(@Header(Authorization) String authorization);
上面的兩種添加請求頭的方法作用范圍只是添加注解的單個方法赏参,如果想為每個請求都添加請求頭還按這種方式來做的話就很不程序猿了。Retrofit的網(wǎng)絡(luò)請求全部交給okhttp來處理,因此我們可以通過OkHttpClient
來做文章把篓,自己重寫okhttp的攔截器在攔截器內(nèi)再進(jìn)行需要的操作
Okhttp interceptor
攔截器顧名思義纫溃,所有通過okhttp進(jìn)行的請求都會過一遍okhttpClient的攔截器,發(fā)出去的請求韧掩,收到的響應(yīng)都會經(jīng)過他紊浩,就像一個雙向的安檢通道。
okhttp攔截器的原理如下:
如圖所示攔截器分為Application Interceptors和NetWork Interceptors。Application攔截器工作區(qū)域為應(yīng)用發(fā)出請求到okhttp核心之間窒悔,遠(yuǎn)端響應(yīng)經(jīng)過okhttp核心后到達(dá)應(yīng)用處理之前呜袁。而NetWork攔截器的作用域為okhtt核心到遠(yuǎn)端服務(wù)器之間的部分。明顯區(qū)別就是當(dāng)一次請求中會有一個重定向的時候Application攔截器只會響應(yīng)一次简珠,因為對于應(yīng)用來說就進(jìn)行了一次請求阶界。而NetWork攔截器會在重定向時也響應(yīng)即響應(yīng)兩次,也不難理解聋庵,畢竟重定向也會經(jīng)過一次okhttp核心嘛膘融。
上圖是okhttp攔截器工作原理簡圖,重點在右邊部分祭玉。當(dāng)多個攔截器配合使用時氧映,不用擔(dān)心請求攔截和響應(yīng)攔截順序會錯亂,okhttp已經(jīng)給你排好了脱货。
上傳個需要壓縮和編碼的東東的時候岛都,你可以選擇先寫個攔截器請求時壓縮響應(yīng)時解壓,再寫個攔截器請求時編碼響應(yīng)時解碼振峻。加起來就是
壓縮
->編碼
->okhttClient與服務(wù)器的Py交易
->解碼
->解壓
跟棧先進(jìn)后出類似臼疫。
原理扯了一大堆,代碼才是干貨扣孟,看了代碼才知道怎么用烫堤。
//官方的栗子
class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
//拿到request實例在此對請求做需要的設(shè)置
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()));
//發(fā)送request請求
Response response = chain.proceed(request);
//得到請求后的response實例,做相應(yīng)操作
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();
拿到請求實例凤价,想怎么裝扮就怎么裝扮鸽斟,什么加請求頭,設(shè)置編碼格式soeasy利诺。前面說到的為每個請求設(shè)置請求頭就是在這完成設(shè)置工作的富蓄。但是真正要加到請求里跟retrofit的ApiService接口一起用還需要將Okhttp注冊攔截器后與Retrofit綁定才行。
//注冊應(yīng)用攔截器
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();
//注冊網(wǎng)絡(luò)攔截器
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進(jìn)行一次完整的網(wǎng)絡(luò)請求呢
需要注意一下Retrofit的Url拼接規(guī)則
個人建議以第一幅圖的方式立倍,baseUrl總是以
/
結(jié)尾躏吊,接口rul總是不以/
開頭
- 1、當(dāng)然是引入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胜卤、創(chuàng)建一個ServiceApi接口
//方便后面RxAndroid我把RxAndroid方式的接口也貼上來疆导。只有返回類型不同而已
public interface RetrofitService {
//單純使用retrofit接口定義
@GET("news/latest")
Call<ZhiHuDaily> getZhihuDailyRetrofitOnly();
//使用retrofit+RxAndroid的接口定義
@GET("news/latest")
Observable<ZhiHuDaily> getZhihuDaily();
}
- 3、我建議是維護(hù)一個統(tǒng)一的api管理類葛躏。當(dāng)然你要直接拿接口用也行澈段,但可維護(hù)性會降低很多
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() {
//不需要使用攔截器就不創(chuàng)建直接從if開始
OkHttpClient client = new OkHttpClient.Builder()
//添加應(yīng)用攔截器
.addInterceptor(new MyOkhttpInterceptor())
//添加網(wǎng)絡(luò)攔截器
// .addNetworkInterceptor(new MyOkhttpInterceptor())
.build();
if (mDailyApi == null) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(GlobalConfig.baseUrl)
//將client與retrofit關(guān)聯(lián)
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
//到這一步創(chuàng)建完成
mDailyApi = retrofit.create(RetrofitService.class);
}
return mDailyApi;
}
}
- 4、調(diào)用接口方法進(jìn)行網(wǎng)絡(luò)請求
public void getStoryDataByRetrofit(final OnEventLister<ArrayList<ZhihuStory>> eventLister) {
ApiManager apiManager = ApiManager.getInstence();
Call<ZhiHuDaily> call = apiManager.getDailyService().getZhihuDailyRetrofitOnly();
//發(fā)送異步請求
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的好處
- 可以少寫不少的代碼
- 接口方便維護(hù)需要改什么直接到
ApiService
中進(jìn)行配置即可 - 異步請求不再需要自己來newThread再handler舰攒,也不需要自己再來寫請求結(jié)果回調(diào)败富。異步請求只需要使用
call.enqueue()
即可。 - 支持RxAndroid摩窃,這個我覺得很重要
- 降低工程的耦合度兽叮,網(wǎng)絡(luò)請求跟邏輯代碼完全剝離開。需要的僅僅是傳遞參數(shù)有的請求甚至參數(shù)都不需要傳遞猾愿。直接在接口中配置就好鹦聪。
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項目主頁的自我概括姻僧,我覺得其實就兩個關(guān)鍵詞规丽,異步
、基于事件
撇贺。這里我只說一下RxAndroid怎么跟Retrofit搭配使用赌莺,要進(jìn)一步了解可以異步扔物線大神的文章給 Android 開發(fā)者的 RxJava 詳解。講的肯定比我好显熏,我就是看這個入門的雄嚣。
Retrofit+RxAndroid使用
因為用到Retrofit所以定義接口,創(chuàng)建ApiManager這些跟上面單純用Retrofit是一毛一樣的喘蟆,唯一的不同是接口的返回類型從Retrofit的Call
對象變成了Observable
對象缓升,即被觀察者對象。然后就是調(diào)用進(jìn)行網(wǎng)絡(luò)請求部分變成如下形式
//使用rxandroid+retrofit進(jìn)行請求
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();
}
})
//設(shè)置事件觸發(fā)在非主線程
.subscribeOn(Schedulers.io())
//設(shè)置事件接受在UI線程以達(dá)到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方法中調(diào)用presenter.unsubcription();進(jìn)行解綁避免內(nèi)存泄漏
addSubscription(subscription);
}
網(wǎng)絡(luò)請求得到的是一個Obserable
對象港谊,該對象再通過subscrible()
綁定一個觀察者對象,觀察者對象中有onCompleted()
,onError()
,onNext()
三個回調(diào)方法橙弱。事件過程中出錯onError()觸發(fā)并停止后續(xù)事件歧寺,一個Obserable對象一次發(fā)出多個事件每次都會觸發(fā)onNext()燥狰,當(dāng)不再有事件發(fā)出的時候onCompleted()方法觸發(fā)并結(jié)束。異步在RxAndroid中變得很簡單subscribeOn
指定事件發(fā)生線程斜筐,如上面的網(wǎng)絡(luò)請求被指定在io線程中龙致,observeOn
指定事件的消費線程,如上面的知乎故事數(shù)據(jù)結(jié)果被交給主線程顯示顷链。
RxAndroid的優(yōu)點
看完上面單獨使用retrofit和使用retrofit+rxandroid兩種方式之后你也許會吐槽目代,尼瑪腦子有坑?明明代碼變多了嗤练。
但是你也會發(fā)現(xiàn)代碼中都是.XX()的形式如果需求變化更多一些會更明顯榛了。比如說就上面的例子我要在每一條信息中修改某一個值。并且又要對結(jié)果進(jìn)行一些篩選煞抬。只用retrofit的話是不是應(yīng)該向這樣:
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(), "");
}
});
如果需要設(shè)置和條件賽選層次越多會發(fā)現(xiàn)for套if霜大,if再if會越嵌套越多。隔一段時間之后就真的成了“當(dāng)初寫下這段代碼的時候只有我跟上帝知道他是干嘛的革答,現(xiàn)在只有上帝知道他是干嘛的”战坤。而使用RxAndroid的話整個變換過程都是線性的哪一步做了什么都會很清楚不會出現(xiàn)各種蜜汁縮進(jìn):
Subscription subscription = ApiManager.getInstence().getDailyService()
.getZhihuDaily()
//從ZhihuDaily中獲取Stories列表
.map(new Func1<ZhiHuDaily, ArrayList<ZhihuStory>>() {
@Override
public ArrayList<ZhihuStory> call(ZhiHuDaily zhiHuDaily) {
return zhiHuDaily.getStories();
}
})
//將列表拆開成事件發(fā)送
.flatMap(new Func1<ArrayList<ZhihuStory>, Observable<ZhihuStory>>() {
@Override
public Observable<ZhihuStory> call(ArrayList<ZhihuStory> stories) {
return Observable.from(stories);
}
})
//將story中的XXX設(shè)置為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;
}
})
//將結(jié)果重新整理成List
.toList()
//設(shè)置事件觸發(fā)在非主線程
.subscribeOn(Schedulers.io())
//設(shè)置事件接受在UI線程以達(dá)到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);
}
});
越復(fù)雜的邏輯,Rx的優(yōu)勢也就越明顯蝗碎。Rx的操作符各種組合起來幾乎能夠滿足所有的變換需求湖笨。開始寫可能會覺得很不適應(yīng),但熟練使用之后會默念Rx大法好的蹦骑。
對于RxJava操作符鼠標(biāo)懸停都會有文字和示意圖的慈省,另外發(fā)現(xiàn)一個不錯的博客里面也有較詳細(xì)的解析RxJava/RxAndroid操作符
Demo地址
在之前的那個MVPDemo的基礎(chǔ)上寫的,okhttp請求方式眠菇,單純retrofit方式边败,retrofit+rxAndroid方式的請求都有保留,可以對比著感受一下捎废。
demo倉庫地址