Android Retrofit 2.0(一)初次見面請多多關照
前言
Retrofit 是SQUARE美國一家移動支付公司最近新發布的在Android平臺上 Http 訪問的開源專案。官方標語:“A type-safe HTTP client for Android and Java”語意很明顯是一款Android安全型別的http客戶端。 這裡安全指什麼呢?是支援https或是支援本地執行緒安全呢?而且,Retrofit其內部都是支援lambda語法(鏈式語法),內部支援okHttp, 並且支援響應式RxJava,當然JDK 1.8 和 android studio工具也支援lambda。帶著這些疑問 我開始探究一下。
Retrofit 官方GitHub
系列文章推薦:
新增依賴
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返回的資料進行解析,下面會講到。
基礎使用
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()) { // 請求成功 } else { //直接操作UI 或彈框提示請求失敗 } } @Override public void onFailure(Call<LoginResult> call, Throwable t) { //錯誤處理程式碼 } }); }
ApiManager介面
public interface ApiManager {
@GET("login/")
Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);
以上就是實現一個登入Login介面的小功能 ,先了解一下Retrofit的基本用法。
基礎介紹
1、首先,例項化一個Retrofit物件。
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:8080/") .addConverterFactory(GsonConverterFactory.create()) .build();
*addConverterFactory 制定資料解析器,上面新增依賴的gson就是用在這裡做預設資料返回的, 之後通過build()創建出來。
Retrofit內部自帶如下格式:- 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、然後,定義帶參的請求介面
@GET("login/")
Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);
Call<T>是繼承Cloneable的 並支援泛型,且此類是Retrofit統一返回物件,支援Callback<T>回撥,在2.0上已支援RxJava觀察者物件Observable<T>,此案例暫時用call ,後面入門了retrofit之後再接入RxJava。
接著我們可以傳入制定的解析Modle,就會在主執行緒裡返回對應的model資料,無需開發者手動解析json資料。返回格式由開發者自己設定。
這裡主要用註解@get @post 設定請求方式,後面“login/”是方法Url, @Query("name")來設定body的parameters。
- 如果想用表單 @FieldMap
@FormUrlEncoded @POST("/url") Call<T> postForm( @FieldMap Map<String , Object> maps);
如果直接用物件 @Body
@POST("url") Call<T> PostBody( @Body Objects objects);
如果直接多引數 @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、最後,呼叫API發起請求
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) {
}
});
}
Retrofit支援非同步和同步:
- call.enqueue(new Callback<LoginResult>)採用非同步請求;
- call.execute() 採用同步方式。
4、取消請求
call.cancel();
進階練習
我們依然要面對的問題是 ,Retrofit 的內部怎麼無法輸出Log ?header怎麼加入?請求怎麼支援https?包括怎麼結合RxJava? 不用擔心,這些問題Retrofit 2.0 都提供了支援okhttp的自定義的Interceptor(攔截器)解決了。通過不同的攔截器可以實現不同的自定義請求形式,比如統一加head、引數、加入證書(ssl)等,前提必須結合okhttp來實現 , 通過給OkHttpClient新增Interceptor,然後給Retrofit設定http即可。Retrofit提供了.client()方法供我們自定義請求,當然預設請求就是okhttps的。
1、開啟Log
用攔截器實現, retrofit已經提供了 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))
基礎的輸出新增攔截器
.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()
特殊API介面單獨加入
@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 實現
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一樣,步驟如下:
- CertificateFactory 得到Context.getSocketFactory;
- 新增證書原始檔;
- 繫結到okhttpClient;
- 設定okhttpClient到retrofit中。
證書同樣可以設定到okhttpclient中,我們可以把證書放到raw路徑下:
SLSocketFactory sslSocketFactory =getSSLSocketFactory_Certificate(context,"BKS", R.raw.XXX);
準備證書原始檔
加入證書原始檔,我的證書是放在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();
構建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;
}
設定setSocketFactory
okhttpBuilder.socketFactory(HttpsFactroy.getSSLSocketFactory(context, certificates));
certificates 是你raw下證書源ID, int[] certificates = {R.raw.myssl}
設定setNameVerifie
okhttpBuilder.hostnameVerifier(HttpsFactroy.getHostnameVerifier(hosts));
hosts是你的host資料,例如 String hosts[]`= {“https//:aaaa,com”, “https//:bbb.com”}
實現自定義 新增到Retrofit
okHttpClient = okhttpBuilder.build();
Retrofit retrofit = new Retrofit.Builder() .client(okHttpClient) .build();
如果信任所有https請求,
可以直接將OkHttpClient的HostnameVerifier設定為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();
常規問題
1 url被轉義
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);
上層需要轉換將自己的map轉換為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}
總結
看了以上的知識點你發現Retrofit同樣支援RxJava,通過以下設定Call適配模式.就可以完美關聯RxJava。
retrofit .addCallAdapterFactory(RxJavaCallAdapterFactory.create())