Retrofit的簡(jiǎn)單用法在上一篇文章分分鐘使用Retrofit+Rxjava實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求已經(jīng)做過(guò)介紹了,今天就不贅述了京腥。
今天主要分享一下如何結(jié)合Rxjava赦肃,封裝一個(gè)RetrofitManager管理類(lèi),統(tǒng)一管理聯(lián)網(wǎng)操作公浪。
《一》讓我們先來(lái)看看封裝后的用法:
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");
}
});
封裝后的用法大家看到了他宛,鏈?zhǔn)秸{(diào)用,一步到位欠气,非常簡(jiǎn)潔明了厅各。接下來(lái)我就帶著大家一步步封裝一個(gè)RetrofitManager。
《二》封裝Retrofit+Rxjava的管理類(lèi)RetrofitManager
(1)在app的build.gradle下配置Retrofit和Rxjava相關(guān)的依賴(lài)包
//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之后的依賴(lài)预柒,由compile變成了implementation队塘。)
(2)①新建RetrofitManager類(lèi),提供單例
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宜鸯,構(gòu)建Retrofit對(duì)象
private static final long DEFAULT_TIMEOUT = 60L;
public Retrofit getRetrofit() {
if (retrofit == null) {
synchronized (RetrofitManager.class) {
if (retrofit == null) {
OkHttpClient mClient = new OkHttpClient.Builder()
//添加公共查詢參數(shù)
//.addInterceptor(new CommonQueryParamsInterceptor())
//.addInterceptor(new MutiBaseUrlInterceptor())
//添加header
.addInterceptor(new HeaderInterceptor())
.addInterceptor(new LoggingInterceptor())//添加請(qǐng)求攔截(可以在此處打印請(qǐng)求信息和響應(yīng)信息)
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
//添加https證書(shū),如果有srca.cer的證書(shū)憔古,則可以通過(guò)sslSocketFactory()配置
//.sslSocketFactory(getSSLSocketFactory(context, "srca.cer"))
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)//基礎(chǔ)URL 建議以 / 結(jié)尾
.addConverterFactory(GsonConverterFactory.create())//設(shè)置 Json 轉(zhuǎn)換器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//RxJava 適配器
.client(mClient)
.build();
}
}
}
return retrofit;
}
/**
* 實(shí)現(xiàn)https請(qǐng)求
*/
private static SSLSocketFactory getSSLSocketFactory(Context context, String name) {
if (context == null) {
throw new NullPointerException("context == null");
}
//CertificateFactory用來(lái)證書(shū)生成
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;
}
③通過(guò)代理的方式,創(chuàng)建ApiServe接口的實(shí)例淋袖。
public ApiService getRequestService() {
return getRetrofit().create(ApiService.class);
}
ApiService是一個(gè)自己定義的interface,所有的網(wǎng)絡(luò)請(qǐng)求接口的配置鸿市,都在此接口內(nèi)完成。網(wǎng)絡(luò)請(qǐng)求URL的配置可以參考Retrofit請(qǐng)求參數(shù)的配置
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的配置
/**
* 添加請(qǐng)求頭需要攜帶的參數(shù)
*/
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 {
//這個(gè)chain里面包含了request和response适贸,所以你要什么都可以從這里拿
Request request = chain.request();
long t1 = System.nanoTime();//請(qǐng)求發(fā)起的時(shí)間
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("發(fā)送請(qǐng)求 %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("發(fā)送請(qǐng)求 %s on %s %nRequestParams:%s%nMethod:%s",
request.url(), chain.connection(), body, request.method()));
}
}
} else {
Log.e("request", String.format("發(fā)送請(qǐng)求 %s on %s%nMethod:%s",
request.url(), chain.connection(), request.method()));
}
Response response = chain.proceed(request);
long t2 = System.nanoTime();//收到響應(yīng)的時(shí)間
ResponseBody responseBody = response.peekBody(1024 * 1024);
Log.e("request",
String.format("Retrofit接收響應(yīng): %s %n返回json:【%s】 %n耗時(shí):%.1fms",
response.request().url(),
responseBody.string(),
(t2 - t1) / 1e6d
));
return response;
}
}
看一下日志打印的效果灸芳,有了日志打印,我們就能輕松的調(diào)試每個(gè)網(wǎng)絡(luò)請(qǐng)求了拜姿。⑥設(shè)置離線時(shí)緩存,我們可以添加一個(gè)CacheInterceptor烙样,在沒(méi)網(wǎng)絡(luò)的時(shí)候,取緩存的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())//必須要有,否則會(huì)返回504
.build();
/**
* 設(shè)置緩存的攔截器
*/
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", "有網(wǎng)");
return response.newBuilder().header("Cache-Control", cacheControl)
.removeHeader("Pragma").build();
} else {
Elog.e("Tag", "無(wú)網(wǎng)");
return response.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + "60 * 60 * 24 * 7")
.removeHeader("Pragma").build();
}
}
}
判斷網(wǎng)絡(luò)狀態(tài)壁却,需要添加權(quán)限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
下圖為關(guān)閉網(wǎng)絡(luò)時(shí)批狱,獲取到的離線的數(shù)據(jù):
《三》OkHttp的攔截器Interceptor
無(wú)論是上面添加header,還是處理log日志打印展东,或是設(shè)置緩存赔硫,配置一些公共請(qǐng)求參數(shù)等等,都是通過(guò)添加攔截器addInterceptor()來(lái)實(shí)現(xiàn)的盐肃,所以攔截器有多重要爪膊,就不用我多說(shuō)了啦~
先舉個(gè)簡(jiǎn)單的栗子权悟,了解一下攔截器是個(gè)什么東西?
官方介紹:攔截器是一種能夠監(jiān)控推盛,重寫(xiě)峦阁,重試調(diào)用的強(qiáng)大機(jī)制。攔截發(fā)出的請(qǐng)求和傳入的響應(yīng)的日志.
打個(gè)比方:鏢局押著一箱元寶走過(guò)一個(gè)山間小路耘成,突然從山上下來(lái)一群山賊攔住了鏢局的去路榔昔,將鏢局身上值錢(qián)的東西搜刮干凈后將其放行。其中山賊相當(dāng)于攔截器瘪菌,鏢局相當(dāng)于一個(gè)正在執(zhí)行任務(wù)的網(wǎng)絡(luò)請(qǐng)求撒会,請(qǐng)求中的參數(shù)就是鏢局?jǐn)y帶的元寶。攔截器可以將網(wǎng)絡(luò)請(qǐng)求攜帶的參數(shù)進(jìn)行修改驗(yàn)證控嗜,然后放行茧彤。這里面其實(shí)設(shè)計(jì)了AOP編程的思想(面向切面編程)。
詳細(xì)了解可參考:
OkHttp攔截器
Interceptors 攔截器
手把手帶你深入剖析 Retrofit 2.0 源碼
RetrofitManager的完整代碼及用到的相關(guān)代碼(包括Retrofit文件的上傳):
/**
* Created by JoJo on 2018/4/24.
* wechat:18510829974
* description:
*/
public class RetrofitManager {
/**
* 請(qǐng)求接口實(shí)例對(duì)象
*/
private static RetrofitManager mInstance;
private static final long DEFAULT_TIMEOUT = 60L;
private Retrofit retrofit = null;
//請(qǐng)求頭信息
private final String HEADER_CONNECTION = "keep-alive";
private String userAgent = "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36";
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()
//添加公告查詢參數(shù)
// .addInterceptor(new CommonQueryParamsInterceptor())
// .addInterceptor(new MutiBaseUrlInterceptor())
// 添加離線緩存
// .cache(new Cache(File(context.getExternalFilesDir("okhttpCache"), ""), 14 * 1024 * 100))
// .addInterceptor(new CacheInterceptor())
// .addNetworkInterceptor(new CacheInterceptor())//必須要有疆栏,否則會(huì)返回504
.addInterceptor(new HeaderInterceptor())
.addInterceptor(new LoggingInterceptor())//添加請(qǐng)求攔截(可以在此處打印請(qǐng)求信息和響應(yīng)信息)
.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)//基礎(chǔ)URL 建議以 / 結(jié)尾
.addConverterFactory(GsonConverterFactory.create())//設(shè)置 Json 轉(zhuǎn)換器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//RxJava 適配器
.client(mClient)
.build();
}
}
}
return retrofit;
}
public ApiService getRequestService() {
return getRetrofit().create(ApiService.class);
}
/**
* 設(shè)置公共查詢參數(shù)
*/
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());
}
}
/**
* 添加請(qǐng)求頭需要攜帶的參數(shù)
*/
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")
//.addHeader("User-Agent",userAgent) //如果天氣的接口報(bào):invalid User-Agent header曾掂,把此處注釋打開(kāi)即可
.method(request.method(), request.body())
.build();
return chain.proceed(requestBuilder);
}
}
/**
* 設(shè)置緩存的攔截器
*/
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", "有網(wǎng)");
return response.newBuilder().header("Cache-Control", cacheControl)
.removeHeader("Pragma").build();
} else {
Elog.e("Tag", "無(wú)網(wǎng)");
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 {
//這個(gè)chain里面包含了request和response,所以你要什么都可以從這里拿
Request request = chain.request();
long t1 = System.nanoTime();//請(qǐng)求發(fā)起的時(shí)間
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("發(fā)送請(qǐng)求 %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("發(fā)送請(qǐng)求 %s on %s %nRequestParams:%s%nMethod:%s",
request.url(), chain.connection(), body, request.method()));
}
}
} else {
Elog.e("request", String.format("發(fā)送請(qǐng)求 %s on %s%nMethod:%s",
request.url(), chain.connection(), request.method()));
}
Response response = chain.proceed(request);
long t2 = System.nanoTime();//收到響應(yīng)的時(shí)間
ResponseBody responseBody = response.peekBody(1024 * 1024);
Elog.e("request",
String.format("Retrofit接收響應(yīng): %s %n返回json:【%s】 %n耗時(shí):%.1fms",
response.request().url(),
responseBody.string(),
(t2 - t1) / 1e6d
));
return response;
}
}
/**
* 打印log日志:該攔截器用于記錄應(yīng)用中的網(wǎng)絡(luò)請(qǐng)求的信息
*/
private HttpLoggingInterceptor getHttpLogingInterceptor() {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
//包含所有的請(qǐng)求信息
//如果收到響應(yīng)是json才打印
if ("{".equals(message) || "[".equals(message)) {
Log.d("TAG", "收到響應(yīng): " + message);
}
Log.d("TAG", "message=" + message);
}
});
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return httpLoggingInterceptor;
}
private String BASE_URL_OTHER = "http://wthrcdn.etouch.cn/";
/**
* 添加可以處理多個(gè)Baseurl的攔截器:http://blog.csdn.net/qq_36707431/article/details/77680252
* Retrofit(OKHttp)多BaseUrl情況下url實(shí)時(shí)自動(dòng)替換完美解決方法: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實(shí)例oldHttpUrl
HttpUrl oldHttpUrl = request.url();
//獲取request的創(chuàng)建者builder
Request.Builder builder = request.newBuilder();
//從request中獲取headers壁顶,通過(guò)給定的鍵url_name
List<String> headerValues = request.headers("url_name");
if (headerValues != null && headerValues.size() > 0) {
//如果有這個(gè)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的基礎(chǔ)上重建新的HttpUrl若专,修改需要修改的url部分
HttpUrl newFullUrl = oldHttpUrl
.newBuilder()
.scheme("http")//更換網(wǎng)絡(luò)協(xié)議,根據(jù)實(shí)際情況更換成https或者h(yuǎn)ttp
.host(newBaseUrl.host())//更換主機(jī)名
.port(newBaseUrl.port())//更換端口
.removePathSegment(0)//移除第一個(gè)參數(shù)v1
.build();
//重建這個(gè)request许蓖,通過(guò)builder.url(newFullUrl).build();
// 然后返回一個(gè)response至此結(jié)束修改
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);
//構(gòu)建body
RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file))
.build();
return requestBody;
}
}
需要用到的類(lèi):
/**
* 線程調(diào)度
*/
public class RxSchedulers {
public static <T> ObservableTransformer<T,T> io_main(){
return new ObservableTransformer<T, T>() {
@Override
public ObservableSource<T> apply(Observable<T> observable) {
return observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
}
《四》最后调衰,附上我的一個(gè)Kotlin編寫(xiě)+組件化開(kāi)發(fā)的開(kāi)源項(xiàng)目Designer
Kotlin+組件化開(kāi)發(fā)實(shí)踐—開(kāi)源項(xiàng)目Designer-App
Designer項(xiàng)目算是傾注了我蠻多心血了膊爪,每個(gè)頁(yè)面和功能都當(dāng)成是上線的App來(lái)做,App的logo還特地做了UI設(shè)計(jì)??力求做到精致和完善嚎莉,其中還包括了很多自己項(xiàng)目開(kāi)發(fā)中的經(jīng)驗(yàn)匯總和對(duì)新技術(shù)的探索和整合米酬,希望對(duì)各位讀者有所幫助,歡迎點(diǎn)個(gè)star趋箩,follow赃额,或者給個(gè)小心心,嘻嘻??也可以分享給你更多的朋友一起學(xué)習(xí)叫确,您的支持是我不斷前進(jìn)的動(dòng)力跳芳。如果有任何問(wèn)題,歡迎在GitHub上給我提issue或者留言竹勉。