Retrofit的简单用法在上一篇文章分分钟使用Retrofit+Rxjava实现网络请求已经做过介绍了,今天就不赘述了。
今天主要分享一下如何结合Rxjava,封装一个RetrofitManager管理类,统一管理联网操作。
《一》让我们先来看看封装后的用法:
RetrofitManager.getInstance().getRequestService().getWeather("北京")
.compose(RxSchedulers.io_main())
.subscribeWith(new DisposableObserver
封装后的用法大家看到了,链式调用,一步到位,非常简洁明了。接下来我就带着大家一步步封装一个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
④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();
}
}
}
判断网络状态,需要添加权限:
下图为关闭网络时,获取到的离线的数据:
《三》OkHttp的拦截器Interceptor
无论是上面添加header,还是处理log日志打印,或是设置缓存,配置一些公共请求参数等等,都是通过添加拦截器addInterceptor()来实现的,所以拦截器有多重要,就不用我多说了啦~
先举个简单的栗子,了解一下拦截器是个什么东西?
官方介绍:拦截器是一种能够监控,重写,重试调用的强大机制。拦截发出的请求和传入的响应的日志.
打个比方:镖局押着一箱元宝走过一个山间小路,突然从山上下来一群山贼拦住了镖局的去路,将镖局身上值钱的东西搜刮干净后将其放行。其中山贼相当于拦截器,镖局相当于一个正在执行任务的网络请求,请求中的参数就是镖局携带的元宝。拦截器可以将网络请求携带的参数进行修改验证,然后放行。这里面其实设计了AOP编程的思想(面向切面编程)。
详细了解可参考:
OkHttp拦截器
Interceptors 拦截器
手把手带你深入剖析 Retrofit 2.0 源码
RetrofitManager的完整代码及用到的相关代码(包括Retrofit文件的上传):
/**
* 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";
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()
//添加公告查询参数
// .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")
//.addHeader("User-Agent",userAgent) //如果天气的接口报:invalid User-Agent header,把此处注释打开即可
.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 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 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;
}
}
需要用到的类:
/**
* 线程调度
*/
public class RxSchedulers {
public static ObservableTransformer io_main(){
return new ObservableTransformer() {
@Override
public ObservableSource apply(Observable observable) {
return observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
}
《四》最后,附上我的一个Kotlin编写+组件化开发的开源项目Designer
Kotlin+组件化开发实践—开源项目Designer-App
Designer项目算是倾注了我蛮多心血了,每个页面和功能都当成是上线的App来做,App的logo还特地做了UI设计力求做到精致和完善,其中还包括了很多自己项目开发中的经验汇总和对新技术的探索和整合,希望对各位读者有所帮助,欢迎点个star,follow,或者给个小心心,嘻嘻也可以分享给你更多的朋友一起学习,您的支持是我不断前进的动力。如果有任何问题,欢迎在GitHub上给我提issue或者留言。