前陣子看到圈子里Retrofit 2.0序臂,RxJava(Android), OkHttp3.3 ,加之支持Android和 iOS 的React Native 熱更新技術(shù), 火的不要不要的, 2015年新技術(shù)一大波來襲 ,看著自己項目還在用httpClient, asyncTask的原生開發(fā) 感覺自己已成火星人,實在頂不住內(nèi)心強烈的自卑感辩尊,加之對新技術(shù)的追求,入手移動開發(fā)新三劍客康辑,運用在目前的項目中摄欲,雖然目前關(guān)于他們的介紹資料網(wǎng)上一大把,但是自己親自實踐后疮薇,發(fā)現(xiàn)坑不少胸墙,為了能方便其他人安全順利入坑,今天就先從Retrofit說起按咒,前方高能迟隅,準(zhǔn)備躲避。
Retrofit 2.0
Retrofit是SQUARE美國一家移動支付公司最近新發(fā)布的在Android平臺上http訪問的開源項目
一 什么Retrofit
官方標(biāo)語励七;A type-safe HTTP client for Android and Java
語意很明顯一款android安全類型的http客戶端智袭, 那么怎么樣才算安全?支持https掠抬?支持本地線程安全吼野?
發(fā)現(xiàn)Rertofit其內(nèi)部都是支持lambda語法(國內(nèi)稱只鏈?zhǔn)秸Z法),內(nèi)部支持okhttp, 并且支持響應(yīng)式RxJAava两波,當(dāng)然jdk1.8 和android studio工具也支持lambda瞳步。帶著這些疑問 我開始探究一下。
在此之前準(zhǔn)備入手資料:
官方github
http://square.github.io/retrofit/
OKHttp原理請看我寫的這個系列:
OkHttp 3.x 源碼解析之Interceptor 攔截器
二 Retrofit怎么使用
下文之前先給大家看下傳統(tǒng)的httpclient(urlConnection) + AsyncTask實現(xiàn)的登錄功能腰奋,這樣我們才能發(fā)現(xiàn)Retrofit的優(yōu)雅之處.
傳統(tǒng)方式:
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
private final String mEmail;
private final String mPassword;
UserLoginTask(String email, String password) {
mEmail = email;
mPassword = password;
}
@Override
protected Boolean doInBackground(Void... params) {
// TODO: attempt authentication against a network service.
try {
// Simulate network access.
String result = "";
BufferedReader in = null;
String path ="http://localhost:8080/login/?" +"email =" + mEmail + "& password =" + mPassword;
URL url =new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
InputStream inStream = conn.getInputStream();
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null)
{
result += "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n" + line;
}
}catch (MalformedURLException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//在這里我們還要對返回的json數(shù)據(jù)進行 要主動映射到modle上
………
for (String credential : DUMMY_CREDENTIALS) {
String[] pieces = credential.split(":");
if (pieces[0].equals(mEmail)) {
// Account exists, return true if the password matches.
return pieces[1].equals(mPassword);
}
}
// TODO: register the new account here.
return true;
}
@Override
protected void onPostExecute(final Boolean success) {
mAuthTask = null;
if (success) {
// do SomeThing
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
}
}
@Override
protected void onCancelled() {
mAuthTask = null;
showProgress(false);
}
}
private void enterhome() {
Intent intent = new Intent(LoginActivity.this, MainListActivity.class);
startActivity(intent);
}
發(fā)現(xiàn)姿勢也很簡單单起,點擊loginbtn
開啟一個異步線程 在AsyncTask
在 doInBackground
中訪問登錄API,在onPostExecute
中進行UI更新劣坊;也能很簡單流暢的解決UI線程請求網(wǎng)絡(luò) 非UI線程更新UI的問題, 但是AsyncTask
處理大數(shù)據(jù)耗時就會有弊端馏臭,況且他默認(rèn)線程也是5個,容易造成泄漏,接下來介紹用Retrofit
實現(xiàn)以上相同的功能的方式
2 Retrofit
/**
* 登錄括儒!
*/
private void getLogin() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8080/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiManager apiService = retrofit.create(ApiManager.class);
Call<LoginResult> call = apiService.getData("lyk", "1234");
call.enqueue(new Callback<LoginResult>() {
@Override
public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
if (response.isSuccess()) {
// do SomeThing
} else {
//直接操作UI 返回的respone被直接解析成你指定的modle
}
}
@Override
public void onFailure(Call<LoginResult> call, Throwable t) {
// do onFailure代碼
}
});
}
ApiManager接口
/**
* Created by LIUYONGKUI on 2016-05-03.
*/
public interface ApiManager {
@GET("login/")
Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);
好了 看了以上代碼 或許你已經(jīng)看到了他的鏈?zhǔn)絻?yōu)雅高大上的地方了绕沈,也許看不懂,有點蒙逼帮寻,但沒關(guān)系我們繼續(xù)入門乍狐。
1 配置gradle
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
com.squareup.retrofit2:converter-gson:2.0.0-beta4 此依賴非必須,只是方便我對http返回的數(shù)據(jù)進行解析固逗。
2 定義實例化
1》初始化Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8080/")
.addConverterFactory(GsonConverterFactory.create())
.build();
通過 Retrofit.Builder 來創(chuàng)建一個retrofit客戶端浅蚪,接著添加host url, 然后制定數(shù)據(jù)解析器,上面依賴的gson就是用在這里做默認(rèn)數(shù)據(jù)返回的烫罩, 之后通過build()創(chuàng)建出來
Retrofit也支持且內(nèi)部自帶如下格式:
- Gson: com.squareup.retrofit2:converter-gson
- Jackson: com.squareup.retrofit2:converter-jackson
- Moshi: com.squareup.retrofit2:converter-moshi
- Protobuf: com.squareup.retrofit2:converter-protobuf
- Wire: com.squareup.retrofit2:converter-wire
- Simple XML: com.squareup.retrofit2:converter-simplexml
- Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
2》編寫API
@GET("login/")
Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);
Call<T>是繼承Cloneable的 并支持泛型惜傲,且此類是Retrofit統(tǒng)一返回對象,支持Callback<T>回調(diào)贝攒,在2.0上已支持RxJava觀察者對象Observable<T>盗誊,此案例暫時用call ,后面入門了retrofit之后再接入RxJava隘弊,接著我們可以傳入制定的解析Modle哈踱,就會在主線程里返回對應(yīng)的model數(shù)據(jù),無需開發(fā)者手動解析json數(shù)據(jù)梨熙,返回格式由開發(fā)者自己設(shè)置开镣,這里主要用注解@get @post 設(shè)置請求方式,后面“l(fā)ogin/”是方法Url, @Query("name")來設(shè)定body的parameters.
如果想用表單 @FieldMap
@FormUrlEncoded
@POST("/url")
Call<T> postForm(
@FieldMap Map<String , Object> maps);-
如果直接用對象 @Body
@POST("url") Call<T> PostBody( @Body Objects objects);
-
如果直接多參數(shù) @QueryMap
@PUT("/url") Call<T> queryMap( @QueryMap Map<String, String> maps);
-
如果上傳文件 @Part
@Multipart @POST("/url") Call<ResponseBody> uploadFlie( @Part("description") RequestBody description, @Part("files") MultipartBody.Part file);
-
如果多文件上傳 @PartMap()
@Multipart @POST("{url}") Call<T> uploadFiles( @Path("url") String url, @PartMap() Map<String, RequestBody> maps);
3》 調(diào)用API
Retrofit支持異步和同步咽扇,案例中用call.enqueue(new Callback<LoginResult>)來采用異步請求邪财,如果 調(diào)用call.execute() 則采用同步方式
Call<LoginResult> call = apiService.getData("lyk", "1234");
call.enqueue(new Callback<LoginResult>() {
@Override
public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
}
@Override
public void onFailure(Call<LoginResult> call, Throwable t) {
}
});
}
取消請求
直接用call實例進行cancel即可
call.cancel();
如果還未理解請閱讀參考入門資料:Retrofit 2.0:有史以來最大的改進
三 進階拓展
通過以上的介紹和案列,我們了解了怎樣運用Retrofit請求網(wǎng)絡(luò)數(shù)據(jù)质欲,展現(xiàn)數(shù)據(jù)更新UI树埠,用什么數(shù)據(jù)模型接收 Retroifit就會返回什么類型的數(shù)據(jù),我們也不用關(guān)心是否在主線程里訪問網(wǎng)絡(luò) 還是子線程更新ui的問題把敞,但實際開發(fā)中會存在很多問題弥奸,很多同學(xué)會遇到:Retrofit的內(nèi)部Log都無法輸出 , header怎么加入,請求怎么支持https奋早,包括怎么結(jié)合RxJava.? 不用擔(dān)心盛霎,這些Retrofit 2.0 都提供了支持okhttp的自定義的Interceptor(攔截器),通過不同的Interceptor可以實現(xiàn)不同的自定義請求形式耽装,比如統(tǒng)一加head愤炸,參數(shù),加入證書(ssl)等掉奄,前提必須結(jié)合okhttp來實現(xiàn) , 通過給OkHttpClient添加Interceptor规个,然后給Retrofit設(shè)置http客戶端即可.Retrofit提供了
.client()方法供我們傳入自定義的網(wǎng)絡(luò)客戶端凤薛,當(dāng)然默認(rèn)請求客戶端就是okhttps.
OkHttp入門請移步:
~https://github.com/square/okhttp
~ OKHttp源碼解析
1 開啟Log
可以用攔截器自己實現(xiàn), retrofit已經(jīng)提供了HttpLoggingInterceptor
里面有四種級別诞仓,輸出的格式 可以看下面介紹缤苫。
public enum Level {
/** No logs. */
NONE,
/**
* Logs request and response lines.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
* }</pre>
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }</pre>
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END GET
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
* }</pre>
*/
BODY
}
開啟請求頭
Retrofit retrofit = new Retrofit.Builder().client(new OkHttpClient.Builder()
.addNetworkInterceptor(
new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS))
.build())
開啟body日志
.addNetworkInterceptor(
new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
基礎(chǔ)輸出
.addNetworkInterceptor(
new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
2 增加頭部信息
通用請求頭
new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.addHeader("mac", "f8:00:ea:10:45")
.addHeader("uuid", "gdeflatfgfg5454545e")
.addHeader("userId", "Fea2405144")
.addHeader("netWork", "wifi")
.build();
return chain.proceed(request);
}
})
.build()
單獨加入
@Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-your-App"})
@get("users/{username}")
Call<User> getUser(@Path("username") String username);
3 添加證書Pinning
證書可以在自定義的OkHttpClient加入certificatePinner 實現(xiàn)
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(new CertificatePinner.Builder()
.add("YOU API.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
.add("YOU API..com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
.add("YOU API..com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
.add("YOU API..com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
.build())
4 支持https
加密和普通http客戶端請求支持https一樣,步驟如下:
1 CertificateFactory 得到Context.getSocketFactory
2 添加證書源文件
3 綁定到okhttpClient
4設(shè)置okhttpClient到retrofit中
證書同樣可以設(shè)置到okhttpclient中墅拭,我們可以把證書放到raw路徑下
SLSocketFactory sslSocketFactory =getSSLSocketFactory_Certificate(context,"BKS", R.raw.XXX);
準(zhǔn)備證書源文件
加入證書源文件活玲,我的證書是放在Raw下面的:綁定證書
protected static SSLSocketFactory getSSLSocketFactory(Context context, int[] certificates) {
if (context == null) {
throw new NullPointerException("context == null");
}
CertificateFactory certificateFactory;
try {
certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
for (int i = 0; i < certificates.length; i++) {
InputStream certificate = context.getResources().openRawResource(certificates[i]);
keyStore.setCertificateEntry(String.valueOf(i), certificateFactory.generateCertificate(certificate));
if (certificate != null) {
certificate.close();
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
構(gòu)建HostnameVerifier
protected static HostnameVerifier getHostnameVerifier(final String[] hostUrls) {
HostnameVerifier TRUSTED_VERIFIER = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
boolean ret = false;
for (String host : hostUrls) {
if (host.equalsIgnoreCase(hostname)) {
ret = true;
}
}
return ret;
}
};
return TRUSTED_VERIFIER;
}
設(shè)置setSocketFactory
okhttpBuilder.socketFactory(HttpsFactroy.getSSLSocketFactory(context, certificates));
certificates 是你raw下證書源ID, int[] certificates = {R.raw.myssl}
設(shè)置setNameVerifie
okhttpBuilder.hostnameVerifier(HttpsFactroy.getHostnameVerifier(hosts));
hosts是你的host數(shù)據(jù) 列如 String hosts[]`= {“https//:aaaa,com”, “https//:bbb.com”}
實現(xiàn)自定義 添加到Retrofit
okHttpClient = okhttpBuilder.build();
Retrofit retrofit = new Retrofit.Builder() .client(okHttpClient) .build();
如果信任所有https請求,
可以直接將OkHttpClient的HostnameVerifier設(shè)置為false
OkHttpClient client = new OkHttpClient();
client.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] x509Certificates,
String s) throws java.security.cert.CertificateException {
}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] x509Certificates,
String s) throws java.security.cert.CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
} };
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
client.setSslSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
clent.protocols(Collections.singletonList(Protocol.HTTP_1_1))
.build();
常規(guī)問題歸總
1 url被轉(zhuǎn)義
http://api.myapi.com/http%3A%2F%2Fapi.mysite.com%2Fuser%2Flist
請將@path改成@url
public interface APIService {
@GET Call<Users> getUsers(@Url String url);}
或者:
public interface APIService {
@GET("{fullUrl}")
Call<Users> getUsers(@Path(value = "fullUrl", encoded = true) String fullUrl);
}
2Method方法找不到
java.lang.IllegalArgumentException: Method must not be null
請指定具體請求類型@get @post等
public interface APIService {
@GET Call<Users> getUsers(@Url String url);
}
3Url編碼不對谍婉,@fieldMap parameters must be use FormUrlEncoded
如果用fieldMap加上FormUrlEncoded編碼
@POST()
@FormUrlEncoded
Observable<ResponseBody> executePost(
@FieldMap Map<String, Object> maps);
上層需要轉(zhuǎn)換將自己的map轉(zhuǎn)換為FieldMap
@FieldMap(encoded = true) Map<String, Object> parameters,
4 paht和url一起使用
Using @Path and @Url paramers together with retrofit2
java.lang.IllegalArgumentException: @Path parameters may not be used with @Url. (parameter #4
如果你是這樣的:
@GET
Call<DataResponse> getOrder(@Url String url,
@Path("id") int id);
請在你的url指定占位符.url:
www.myAPi.com/{Id}
總結(jié)
看了以上的知識點你發(fā)現(xiàn)Retrofit同樣支持RxJava,通過以下設(shè)置Call適配模式.就可以完美關(guān)聯(lián)RxJava舒憾。
retrofit .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
關(guān)于 Retrofit+ RxJava的案列, 結(jié)尾源碼已經(jīng)結(jié)合,以及實際遇到的坑可以看本人的系列文章(Retrofit+Rxjava使用技巧一文)穗熬。RxJava也是一款強大的多線程通訊利器镀迂,也支持本地線程安全,從以前編程習(xí)慣遷移到這種鏈?zhǔn)斤L(fēng)格唤蔗, 估計入門會讓你頭痛探遵,但會讓你在實際應(yīng)用開發(fā)中無時無刻,隨心所欲進行多線程響應(yīng)式編程開發(fā)措译。一句話 :誰用誰知道别凤!
Retrofit 2.0系列請閱讀
參考文章: