Retrofit的簡單用法在上一篇文章 分分鐘使用Retrofit+Rxjava實作網絡請求
已經做過介紹了,今天就不贅述了。
今天主要分享一下如何結合Rxjava,封裝一個RetrofitManager管理類,統一管理聯網操作。
《一》讓我們先來看看封裝後的用法:
RetrofitManager.getInstance().getRequestService().getWeather("北京")
.compose(RxSchedulers.io_main())
.subscribeWith(new DisposableObserver<Object>() {
@Override
public void onNext(Object result) {
Log.e("TAG", "result=" + result.toString());
}
@Override
public void onError(Throwable e) {
Log.e("TAG", "onError=" + e.getMessage());
}
@Override
public void onComplete() {
Log.e("TAG", "onComplete");
}
});
封裝後的用法大家看到了,鍊式調用,一步到位,非常簡潔明了。接下來我就帶着大家一步步封裝一個RetrofitManager。
《二》封裝Retrofit+Rxjava的管理類RetrofitManager
(1)在app的build.gradle下配置Retrofit和Rxjava相關的依賴包
//rxandroid
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
//rxjava
implementation 'io.reactivex.rxjava2:rxjava:2.1.10'
//retrofit
implementation "com.squareup.retrofit2:retrofit:2.4.0"
//gsonConverter
implementation "com.squareup.retrofit2:converter-gson:2.4.0"
//rxjavaAdapter
implementation "com.squareup.retrofit2:adapter-rxjava2:2.4.0"
//retrofit log列印
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
(小提醒: Android Studio3.0之後的依賴,由compile變成了implementation。)
(2)①建立RetrofitManager類,提供單例
public class RetrofitManager {
/**
* 擷取單例
*/
private static RetrofitManager mInstance;
public static RetrofitManager getInstance() {
if (mInstance == null) {
synchronized (RetrofitManager.class) {
if (mInstance == null) {
mInstance = new RetrofitManager();
}
}
}
return mInstance;
}
}
②配置OkHttp,建構Retrofit對象
private static final long DEFAULT_TIMEOUT = 60L;
public Retrofit getRetrofit() {
if (retrofit == null) {
synchronized (RetrofitManager.class) {
if (retrofit == null) {
OkHttpClient mClient = new OkHttpClient.Builder()
//添加公共查詢參數
//.addInterceptor(new CommonQueryParamsInterceptor())
//.addInterceptor(new MutiBaseUrlInterceptor())
//添加header
.addInterceptor(new HeaderInterceptor())
.addInterceptor(new LoggingInterceptor())//添加請求攔截(可以在此處列印請求資訊和響應資訊)
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
//添加https證書,如果有srca.cer的證書,則可以通過sslSocketFactory()配置
//.sslSocketFactory(getSSLSocketFactory(context, "srca.cer"))
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)//基礎URL 建議以 / 結尾
.addConverterFactory(GsonConverterFactory.create())//設定 Json 轉換器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//RxJava 擴充卡
.client(mClient)
.build();
}
}
}
return retrofit;
}
/**
* 實作https請求
*/
private static SSLSocketFactory getSSLSocketFactory(Context context, String name) {
if (context == null) {
throw new NullPointerException("context == null");
}
//CertificateFactory用來證書生成
CertificateFactory certificateFactory;
InputStream inputStream = null;
Certificate certificate;
try {
inputStream = context.getResources().getAssets().open(name);
} catch (IOException e) {
e.printStackTrace();
}
try {
certificateFactory = CertificateFactory.getInstance("X.509");
certificate = certificateFactory.generateCertificate(inputStream);
//Create a KeyStore containing our trusted CAs
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry(name, certificate);
//Create a TrustManager that trusts the CAs in our keyStore
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
//Create an SSLContext that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
}
return null;
}
③通過代理的方式,建立ApiServe接口的執行個體。
public ApiService getRequestService() {
return getRetrofit().create(ApiService.class);
}
ApiService是一個自己定義的interface,所有的網絡請求接口的配置,都在此接口内完成。網絡請求URL的配置可以參考
Retrofit請求參數的配置interface ApiService {
//擷取北京的天氣資訊
// "https://www.sojson.com/open/api/weather/json.shtml?city=" + "北京"
@GET("weather/json.shtml")
Observable<Object> getWeather(@Query("city")String city);
//上傳檔案
@POST("upload/")
Observable<UserAvatarBean> uploadFile(@Body RequestBody body);
}
④Header的配置
/**
* 添加請求頭需要攜帶的參數
*/
public class HeaderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request requestBuilder = request.newBuilder()
.addHeader("Connection", HEADER_CONNECTION)
.addHeader("token", "token-value")
.method(request.method(), request.body())
.build();
return chain.proceed(requestBuilder);
}
}
⑤Retrofit的log日志列印
/**
* log列印:參考:http://blog.csdn.net/csdn_lqr/article/details/61420753
*/
public class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//這個chain裡面包含了request和response,是以你要什麼都可以從這裡拿
Request request = chain.request();
long t1 = System.nanoTime();//請求發起的時間
String method = request.method();
JSONObject jsonObject = new JSONObject();
if ("POST".equals(method) || "PUT".equals(method)) {
if (request.body() instanceof FormBody) {
FormBody body = (FormBody) request.body();
if (body != null) {
for (int i = 0; i < body.size(); i++) {
try {
jsonObject.put(body.name(i), body.encodedValue(i));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
Log.e("request", String.format("發送請求 %s on %s %nRequestParams:%s%nMethod:%s",
request.url(), chain.connection(), jsonObject.toString(), request.method()));
} else {
Buffer buffer = new Buffer();
RequestBody requestBody = request.body();
if (requestBody != null) {
request.body().writeTo(buffer);
String body = buffer.readUtf8();
Log.e("request", String.format("發送請求 %s on %s %nRequestParams:%s%nMethod:%s",
request.url(), chain.connection(), body, request.method()));
}
}
} else {
Log.e("request", String.format("發送請求 %s on %s%nMethod:%s",
request.url(), chain.connection(), request.method()));
}
Response response = chain.proceed(request);
long t2 = System.nanoTime();//收到響應的時間
ResponseBody responseBody = response.peekBody(1024 * 1024);
Log.e("request",
String.format("Retrofit接收響應: %s %n傳回json:【%s】 %n耗時:%.1fms",
response.request().url(),
responseBody.string(),
(t2 - t1) / 1e6d
));
return response;
}
}
看一下日志列印的效果,有了日志列印,我們就能輕松的調試每個網絡請求了。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuMDMiVWZiRTYhRDMhdTN0ADN1cDMyYmZlJ2M4EGNyUWOfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.png)
image.png
⑥設定離線時緩存,我們可以添加一個CacheInterceptor,在沒網絡的時候,取緩存的response 。在這裡緩存的位置在Android/data/包名/files/okhttpCache...目錄下。
OkHttpClient mClient = new OkHttpClient.Builder()
添加離線緩存
.cache(new Cache(File(context.getExternalFilesDir("okhttpCache"), ""), 14 * 1024 * 100))
.addInterceptor(new CacheInterceptor())
.addNetworkInterceptor(new CacheInterceptor())//必須要有,否則會傳回504
.build();
/**
* 設定緩存的攔截器
*/
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetUtils.isNetworkConnected(MyApplication.getContext())) {
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
}
Response response = chain.proceed(request);
if (NetUtils.isNetworkConnected(MyApplication.getContext())) {
String cacheControl = request.cacheControl().toString();
Elog.e("Tag", "有網");
return response.newBuilder().header("Cache-Control", cacheControl)
.removeHeader("Pragma").build();
} else {
Elog.e("Tag", "無網");
return response.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + "60 * 60 * 24 * 7")
.removeHeader("Pragma").build();
}
}
}
判斷網絡狀态,需要添權重限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
下圖為關閉網絡時,擷取到的離線的資料:
《三》OkHttp的攔截器Interceptor
無論是上面添加header,還是處理log日志列印,或是設定緩存,配置一些公共請求參數等等,都是通過添加攔截器addInterceptor()來實作的,是以攔截器有多重要,就不用我多說了啦~
先舉個簡單的栗子,了解一下攔截器是個什麼東西?
官方介紹:攔截器是一種能夠監控,重寫,重試調用的強大機制。攔截發出的請求和傳入的響應的日志.
打個比方:镖局押着一箱元寶走過一個山間小路,突然從山上下來一群山賊攔住了镖局的去路,将镖局身上值錢的東西搜刮幹淨後将其放行。其中山賊相當于攔截器,镖局相當于一個正在執行任務的網絡請求,請求中的參數就是镖局攜帶的元寶。攔截器可以将網絡請求攜帶的參數進行修改驗證,然後放行。這裡面其實設計了AOP程式設計的思想(
面向切面程式設計)。
詳細了解可參考:
OkHttp攔截器 Interceptors 攔截器 手把手帶你深入剖析 Retrofit 2.0 源碼附上RetrofitManager的完整代碼(包括Retrofit檔案的上傳):
package com.zongxueguan.naochanle_android.net.retrofit;
import com.zongxueguan.naochanle_android.global.API;
import com.zongxueguan.naochanle_android.retrofitrx.ApiService;
import com.zongxueguan.naochanle_android.util.UserConstants;
import com.zxg.framework.library.common.log.Elog;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.CacheControl;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import okio.Buffer;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* Created by JoJo on 2018/4/24.
* wechat:18510829974
* description:
*/
public class RetrofitManager {
/**
* 請求接口執行個體對象
*/
private static RetrofitManager mInstance;
private static final long DEFAULT_TIMEOUT = 60L;
private Retrofit retrofit = null;
//請求頭資訊
private final String HEADER_CONNECTION = "keep-alive";
public static RetrofitManager getInstance() {
if (mInstance == null) {
synchronized (RetrofitManager.class) {
if (mInstance == null) {
mInstance = new RetrofitManager();
}
}
}
return mInstance;
}
public Retrofit getRetrofit() {
if (retrofit == null) {
synchronized (RetrofitManager.class) {
if (retrofit == null) {
OkHttpClient mClient = new OkHttpClient.Builder()
//添加公告查詢參數
// .addInterceptor(new CommonQueryParamsInterceptor())
// .addInterceptor(new MutiBaseUrlInterceptor())
// 添加離線緩存
// .cache(new Cache(File(context.getExternalFilesDir("okhttpCache"), ""), 14 * 1024 * 100))
// .addInterceptor(new CacheInterceptor())
// .addNetworkInterceptor(new CacheInterceptor())//必須要有,否則會傳回504
.addInterceptor(new HeaderInterceptor())
.addInterceptor(new LoggingInterceptor())//添加請求攔截(可以在此處列印請求資訊和響應資訊)
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(API.getInstance().BASE_API_URL)//基礎URL 建議以 / 結尾
.addConverterFactory(GsonConverterFactory.create())//設定 Json 轉換器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//RxJava 擴充卡
.client(mClient)
.build();
}
}
}
return retrofit;
}
public ApiService getRequestService() {
return getRetrofit().create(ApiService.class);
}
/**
* 設定公共查詢參數
*/
public class CommonQueryParamsInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.url().newBuilder()
.addQueryParameter("paramsA", "a")
.addQueryParameter("paramsB", "b")
.build();
return chain.proceed(request.newBuilder().url(url).build());
}
}
/**
* 添加請求頭需要攜帶的參數
*/
public class HeaderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request requestBuilder = request.newBuilder()
.addHeader("Connection", HEADER_CONNECTION)
.addHeader("token", "token-value")
.method(request.method(), request.body())
.build();
return chain.proceed(requestBuilder);
}
}
/**
* 設定緩存的攔截器
*/
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetUtils.isNetworkConnected(MyApplication.getContext())) {
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
}
Response response = chain.proceed(request);
if (NetUtils.isNetworkConnected(MyApplication.getContext())) {
String cacheControl = request.cacheControl().toString();
Elog.e("Tag", "有網");
return response.newBuilder().header("Cache-Control", cacheControl)
.removeHeader("Pragma").build();
} else {
Elog.e("Tag", "無網");
return response.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + "60 * 60 * 24 * 7")
.removeHeader("Pragma").build();
}
}
}
/**
* log列印:http://blog.csdn.net/csdn_lqr/article/details/61420753
*/
public class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//這個chain裡面包含了request和response,是以你要什麼都可以從這裡拿
Request request = chain.request();
long t1 = System.nanoTime();//請求發起的時間
String method = request.method();
JSONObject jsonObject = new JSONObject();
if ("POST".equals(method) || "PUT".equals(method)) {
if (request.body() instanceof FormBody) {
FormBody body = (FormBody) request.body();
if (body != null) {
for (int i = 0; i < body.size(); i++) {
try {
jsonObject.put(body.name(i), body.encodedValue(i));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
Elog.e("request", String.format("發送請求 %s on %s %nRequestParams:%s%nMethod:%s",
request.url(), chain.connection(), jsonObject.toString(), request.method()));
} else {
Buffer buffer = new Buffer();
RequestBody requestBody = request.body();
if (requestBody != null) {
request.body().writeTo(buffer);
String body = buffer.readUtf8();
Elog.e("request", String.format("發送請求 %s on %s %nRequestParams:%s%nMethod:%s",
request.url(), chain.connection(), body, request.method()));
}
}
} else {
Elog.e("request", String.format("發送請求 %s on %s%nMethod:%s",
request.url(), chain.connection(), request.method()));
}
Response response = chain.proceed(request);
long t2 = System.nanoTime();//收到響應的時間
ResponseBody responseBody = response.peekBody(1024 * 1024);
Elog.e("request",
String.format("Retrofit接收響應: %s %n傳回json:【%s】 %n耗時:%.1fms",
response.request().url(),
responseBody.string(),
(t2 - t1) / 1e6d
));
return response;
}
}
/**
* 列印log日志:該攔截器用于記錄應用中的網絡請求的資訊
*/
private HttpLoggingInterceptor getHttpLogingInterceptor() {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
//包含所有的請求資訊
//如果收到響應是json才列印
if ("{".equals(message) || "[".equals(message)) {
Log.d("TAG", "收到響應: " + message);
}
Log.d("TAG", "message=" + message);
}
});
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return httpLoggingInterceptor;
}
private String BASE_URL_OTHER = "http://wthrcdn.etouch.cn/";
/**
* 添加可以處理多個Baseurl的攔截器:http://blog.csdn.net/qq_36707431/article/details/77680252
* Retrofit(OKHttp)多BaseUrl情況下url實時自動替換完美解決方法:https://www.2cto.com/kf/201708/663977.html
// http://wthrcdn.etouch.cn/weather_mini?city=北京
// @Headers({"url_name:other"})
// @GET("weather_mini")
// Observable<WeatherEntity> getMessage(@Query("city") String city);
*/
private class MutiBaseUrlInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//擷取request
Request request = chain.request();
//從request中擷取原有的HttpUrl執行個體oldHttpUrl
HttpUrl oldHttpUrl = request.url();
//擷取request的建立者builder
Request.Builder builder = request.newBuilder();
//從request中擷取headers,通過給定的鍵url_name
List<String> headerValues = request.headers("url_name");
if (headerValues != null && headerValues.size() > 0) {
//如果有這個header,先将配置的header删除,是以header僅用作app和okhttp之間使用
builder.removeHeader("url_name");
//比對獲得新的BaseUrl
String headerValue = headerValues.get(0);
HttpUrl newBaseUrl = null;
if ("other".equals(headerValue)) {
newBaseUrl = HttpUrl.parse(BASE_URL_OTHER);
// } else if ("other".equals(headerValue)) {
// newBaseUrl = HttpUrl.parse(BASE_URL_PAY);
} else {
newBaseUrl = oldHttpUrl;
}
//在oldHttpUrl的基礎上重建新的HttpUrl,修改需要修改的url部分
HttpUrl newFullUrl = oldHttpUrl
.newBuilder()
.scheme("http")//更換網絡協定,根據實際情況更換成https或者http
.host(newBaseUrl.host())//更換主機名
.port(newBaseUrl.port())//更換端口
.removePathSegment(0)//移除第一個參數v1
.build();
//重建這個request,通過builder.url(newFullUrl).build();
// 然後傳回一個response至此結束修改
Elog.e("Url", "intercept: " + newFullUrl.toString());
return chain.proceed(builder.url(newFullUrl).build());
}
return chain.proceed(request);
}
}
/**
* Retrofit上傳檔案
*
* @param mImagePath
* @return
*/
public RequestBody getUploadFileRequestBody(String mImagePath) {
File file = new File(mImagePath);
//建構body
RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file))
.build();
return requestBody;
}
}