項目介紹
官網對retrofit介紹是這是一個"類型安全(type-safe)"的Android/Java http客戶端. 目前retrofit的最新正式版本為1.9.0. 2.0版本預計2015年底發布, 相較于之前版本, 2.0版本在架構上做了很大改變, 本文代碼相關的內容都是基于retrofit2.0-beta2.
注: 在編程語言的語法中, type-safe通常指編譯器在編譯時檢查變量的類型, 如果試圖向?變量分配一個錯誤的類型,編譯器就會報錯.
在項目中使用retrofit
retrofit庫可以在maven.org?找到. 可以直接添加到maven或gradle工程中.Maven #+BEGIN_SRC xml
com.squareup.retrofitretrofit2.0.0-beta2 #+END_EXAMPLEGradle. 如果與服務端的請求和結果都是json的話,需要gson converter進行轉化, 因為要把該庫也添加上. #+BEGIN_SRC xml compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' #+END_EXAMPLE
混淆配置 如果項目中使用混淆的話, 需要在混淆文件中假如如下配置-dontwarn?retrofit.**-keep?class?retrofit.**?{?*;?}-keepattributes?Signature-keepattributes?Exceptions
程序示例
一個簡單的retrofit程序
場景:通過GET請求向服務器返回用戶信息, 服務器通過Json格式返回一個或多個用戶的信息. 基于這個例子介紹一下retrofit的使用步驟:用戶類. 這段代碼定義了用戶類User, 每個用戶包含三個基本信息:id, name, age; 通過retrofit請求用戶信息時, 客戶端返回用戶的json信息, retrofit可以直接將json信息轉化為用戶類.public?class?User?{
private?int?id;
private?String?name;
private?int?age;}
定義Client和GET請求接口public?interface?Client?{
@GET("users")
Call>??getUsers();}
這段代碼定義了一個接口Client, 并定義了一個GET函數getUsers(), 該函數用戶向服務器發送get請求獲取所有的 用戶信息. 定義GET請求需要用GET注解來修飾函數, 注解的參數為uri的相對路徑, 下一部分會定義URL的地址, 在 發送GET請求時, retrofit會將GET的參數和服務器拼接. 后面會在該接口中實現其他的POST和GET函數.
注:?在retrofit2.0中,要注意GET和POST注解的參數,如果參數以"/"開頭,那么在跟base地址拼接時,會將base地址中 的相對地址全部覆蓋掉. 舉例: base地址為"http://a/b", GET參數為"/c/d", 那么最后的請求地址為"http://a/c/d", 因此,如果base地址本身已經是相對地址, 那么GET/POST的參數不能以"/"開頭.
public?class?MainActivity?{
....
public?static?final?String?SERVER_URL?=?"http://10.10.10.10/account";
private?OkHttpClient?okHttpClient?=?new?OkHttpClient();
private?Retrofit.Builder?builder?=?new?Retrofit.Builder()
.base_url(SERVER_URL)
.client(okHttpClient)
.addConvertFactory(GsonConvertFactory.create());
Retrofit?retrofit?=?builder.build();
Client?client?=?retrofit.create(Client.class);
Call>?call?=?client.getUsers();
List?result?=?call.execute().body();
....}
上述代碼用來做實際的請求動作, 首先通過Retrofit Builder來基于各種參數(服務器地址, httpclient, converter) 生成一個builder對象, 讓后調用builder的build()函數生成一個retrofit對象.
接著,調用retrofit的create()函數,傳入上一步中定義的接口作為參數來實例化一個具體的接口對象, 然后調用 該對象的具體http請求函數(這里為getUsers())來實現http請求. 請求的結果是Json數據,會通過GsonConverter轉化為具體的 對象(即User). 由于是多個對象,所以需要放到一個List中.
上述三步即為retrofit的基本使用方法.
創建一個Service generator類
如果項目中?針對同一個server地址?需要創建多個Retrofit Interface service,那么可以創建一個通用的ServiceGenerator類 來生成service實例.public?class?ServiceGenerator?{
public?static?final?String?BASE_URL?=?"";
private?static?OkHttpClient?httpClient?=?new?OkHttpClient();
private?static?Retrofit.Builder?builder?=
new?Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
public?static??T?createService(Class?serviceClass){
//把設置client放到這里是因為后續有對client進行配置的需求
Retrofit?retrofit?=?builder.client(httpClient).build();
return?retrofit.create(serviceClass);
}
}
這樣在上一節的MainActivity中,可以直接使用ServiceGenerator來創建Client實例Client?client?=?ServiceGenerator.create(Client.class);Client?call?=?client.getUsers();List?result?=?call.execute().body();
帳號密碼認證的ServiceGenerator類
帳號密碼是一種常見的認證方式, 通常將其加密后以放入到http頭部的Authorization中 進行請求認證.通過對OkHttpClient進行配置可以在retrofit中實現該方式.public?static??T?createService(Class?serviceClass){
createService(serviceClass,?null,?null);}pubic?static??T?createService(Class?serviceCls,?String?userName,?String?passWord)??{
if?(userName?!=?null?&&?passWord?!=?null)?{
//對用戶名和密碼進行加密(不同的需求加密方式不一樣,?這里只提供參考)
String?credentials?=?userName?+?":"?+?passWord;
final?String?base64Str?=?Base64.encodeToString(credentials.getBytes(),?Base64.NO_WRAP);
httpClient.interceptors().clear();
httpClient.interceptors().add(new?Interceptor()?{
@Override
public?Response?intercept(Interceptor.Chain?chain)?throws?IOException?{
Request?original?=?chain.request();
Request.Builder?requestBuilder?=?original.newBuilder()
.header("Authorization",?basic);
.header("Accept",?"applicaton/json");
.method(original.method(),?original.body());
Request?request?=?requestBuilder.build();
return?chain.proceed(request);
}
});
}
Retrofit?retrofit?=?builder.client(httpClient).build();
return?retrofit.create(serverClass);}
上述代碼通過修改OkHttpClient的相關參數來修改API請求的頭部, 講加密后的帳號和密碼放入到 Authorization中實現驗證.
注: Interceptors是屬于OkHttp的相關內容, 這部分在后面學習OkHttp時會介紹.
OAuth認證接口的ServiceGenerator類
整合過第三方API的同學肯定對OAuth接口不陌生, 大部分情況下你都需要去第三方開發者 平臺注冊你的app去獲取一個id和secret, 這樣才可以訪問第三方的接口.
注: 關于oauth的介紹可以參考阮一峰老師的文章?理解OAuth2.0.
基于前面的代碼, 重新寫一個OAuth相關的createService()函數.public?static??T?createService(Class?serviceClass,?AccessToken?token)?{
if?(token?!=?null)?{
httpClient.interceptors().clear();
httpClient.interceptors().add(new?Interceptor()?{
@Override
public?Response?intercept(Interceptor.Chain?chain)?throws?IOException?{
Request?original?=?chain.request();
Request.Builder?builder2?=?original.newBuilder()
.header("Accept",?"application/json")
.header("Authorization",?token.getTokenType()+?"?"?+?token.getAccessToken())
.method(original.method(),?original.body());
Request?request?=?builder2.build();
return?chain.proceed(request);
}
});
Retrofit?retrofit?=?builder.client(httpClient).build();
return?retrofit.create(serverClass);
}}
上面的代碼通過創建一個定制的?RequestInterceptor?對象來配置httpClient, 在定制的對象中將token信息 添加到Http表頭的Authorization域. 不過一般情況下, Access Token并不是直接可以從服務器獲取的, 下面就會講解一下獲取Access Token的常用方法.
場景: 假設你已經在第三方網站注冊了你的app, 獲取了一個clientId 和 secret, 你使用這個帳號來想注冊服務器獲取 授權碼(一般是跳轉到一個網頁, 點擊允許操作), 然后再通過授權碼獲取Access Token, 下面是主要流程.獲取授權碼 授權碼的獲取一般需要跳轉到第三方api的一個相關的網頁,網頁中會詢問用戶是否允許用戶 app獲取其在該網站的信息.如果用戶點擊允許, 第三方服務器就會生成一個授權碼返回給用戶. 第一步先創建程序主界面:public?class?LoginActivity?extends?Activity?{
//在第三方平臺注冊應用獲取的clientId和secret
private?final?String?clientId?=?"your-client-id";
private?final?String?clientSecret?=?"your-client-secret";
//獲取跳轉碼后的跳轉url,?在申請授權碼時需要一并傳給第三方服務器
private?final?String?redirectUri?=?"your://redirecturi";
@Override
protected?void?onCreate(Bundle?savedInstanceState)?{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
Button?loginButton?(Button)?findViewById(R.id.loginbutton);
loginButton.setOnClickListener(new?View.OnClickListener()?{
@Override
public?void?onClick(View?v)?{
Intent?intent?=?new?Intent(
Intent.ACTION_VIEW,
Uri.parse(ServiceGenerator.API_BASE_URL?+?"/login"?+?"?client_id="?+?clientId?+?"&redirect_uri="?+?redirectUri));
startActivity(intent);
}
});
}}
上述代碼定義了一個基本的Android界面, 界面只有一個按鈕, 點擊按鈕會請求授權碼(一般會跳轉到一個授權界面). 在請求中傳入一個了回調地址, 如果用戶授權一般第三方服務器帶著授權碼會跳到這個地址, 所以必須在請求授權碼 時傳入回調地址. 這在Android中會表現發送回調Uri的廣播,并將授權碼通過intent傳遞出去. 所以app中需要在注冊一個可以接受該intent的界面,這里還是使用主界面. 在AndroidMainfest.xml中設置intent-filter #+BEGIN_SRC xml
#+END_EXAMPLE
在onResume處理接受到的Intent. 這里假設授權碼在intent中傳遞并且key值為code(第三方平臺的回調方式需要參考他們的文檔).@Overrideprotected?void?onResume()?{
super.onResume();
Uri?uri?=?getIntent().getData();
if?(uri?!=?null?&&?uri.toString().startsWith(redirectUri))?{
String?code?=?uri.getQueryParameter("code");
if?(code?!=?null)?{
//處理授權碼
}?else?if?(uri.getQueryParameter("error")?!=?null)?{
//處理錯誤
}
}}
好, 到此為止,我們就已經獲取到了授權碼,下一步就是通過授權碼獲取Access Token.獲取Access Token 上一步獲取到授權碼后, 就可以向第三方的Access Token服務器發送請求獲取token. 我們可以寫一個retrofit服務 來實現這個功能.public?interface?LoginService?{
@POST("/token")
Call?getAccessToken(
@Query("code")?String?code,
@Query("grant_type")?String?grantType);}
這里的code就是上一步獲取的授權碼, grantType是授權類型. 然后用下面的代碼加入到onResume獲取成功的代碼段中if?(code?!=?null)?{
//?get?access?token
LoginService?loginService?=
ServiceGenerator.createService(LoginService.class,?clientId,?clientSecret);
Call?call?=?loginService.getAccessToken(code,?"authorization_code");
AccessToken?accessToken?=?call.execute().body();}
以上都是示例, 代碼具體寫法請參考相關第三方文檔.
同步請求 vs 異步請求
Retrofit支持同步和異步請求, 不過Retrofit2的同步/異步架構功能與1有 很大不同, 具體請參考相關文檔.同步請求 直接調用execute()函數, 本文中的實例就是同步請求的例子.
注意事項:不要在Android的主線程中調用execute(),有可能報錯或導致ANR.
異步請求 異步請求的話調用enque()函數, 并向enque()傳入一個Callback的參數. 并需要要實現Callback的onSuccess和onFailure函數.
請求結果Response類
當調用execute()或enqueue()函數時, 會返回一個Reponse對象表示請求結果. 該請求結果包含了以下信息:結果碼: 調用code()函數獲得
結果對象: 調用body()函數獲得, 如示例所示.
頭部: 調用headers
原始返回結果: 調用rawResponse()函數, 返回一個OkHttp的Response對象.
Tips請求失敗, body()返回值為null