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;
}
}
看一下日志打印的效果,有了日志打印,我们就能轻松的调试每个网络请求了。
⑥设置离线时缓存,我们可以添加一个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;
}
}