入职公司后,公司要求组件化开发,经过讨论后我将网络请求框架单独进行了封装,不过当时框架里将常用的util和ui均放入到了共同的Common包下,导致里面部分代码耦合,后来为了降低耦合性又将Common拆分为了lib_common和lib_ui,但是lib_ui依赖了lib_common,还是导致部分代码耦合,最新一期为了降低组件之间的耦合性,所以单独将lib_common中的网络请求单独拆分,并且我又做了新的封装和完善,总之网络框架经过3次大的改造后,使用已经非常稳定了。
1.在Application类中进行初始化操作
ApiConfig build = new ApiConfig.Builder()
.setBaseUrl(baseUrl)//BaseUrl,这个地方加入后项目中默认使用该url
.setInvalidateToken(0)//Token失效码
.setSucceedCode(200)//成功返回码
.setFilter("com.mp5a5.quit.broadcastFilter")//失效广播Filter设置
//.setDefaultTimeout(2000)//响应时间,可以不设置,默认为2000毫秒
//.setHeads(headMap)//动态添加的header,也可以在其他地方通过ApiConfig.setHeads()设置
//.setOpenHttps(true)//开启HTTPS验证
//.setSslSocketConfigure(sslSocketConfigure)//HTTPS认证配置
.build();
build.init(this);
2.定义接口
public interface UploadApi {
@Multipart
@POST("ues/app/upload/pictures")
Observable postUpload(@Part List partList);
}
3.创建请求实例
public class UploadService {
private final UploadApi mUploadApi;
private UploadService() {
//推荐使用这种,因为BaseUrl已经初始化了
mUploadApi = RetrofitFactory.getInstance().create(UploadApi.class);
}
public static UploadService getInstance() {
return UploadServiceHolder.S_INSTANCE;
}
private static class UploadServiceHolder {
private static final UploadService S_INSTANCE = new UploadService();
}
public Observable uploadPic(List picList) {
return mUploadApi.postUpload(picList);
}
}
4.发送请求
UploadService.getInstance()
.uploadPic(t)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose([email protected]())
.subscribe(object : BaseObserver(this, true) {
override fun onSuccess(response: UploadEntity?) {
toast(response?.msg!!)
}
})
由于JDK1.8中接口可以有不需要实现的方法,所以我采用了1.8的新特新封装了网络请求返回参数的回调。1.8还有许多新特性,没有升级的同学可以自己查找资料学习一波。
public interface OnBaseResponseListener {
/**
* 成功
*
* @param response 成功参数
*/
void onSuccess(R response);
/**
* 失败
*
* @param response 失败参数
*/
default void onFailing(R response) {}
/**
* 错误
*/
default void onError() {}
}
其中onSuccess()方法是必须实现的,onFailing(R response)和onError()可以不用实现,如果项目中想处理网络请求失败和错误,则需要重写onFailing(R response)和onError()方法,如果用到了封装的loading框和Toast,则需要super.onFailing(response)和super.onError(e),否则则可以不用super。这个类可以用于网络请求后比如在mvp m层的回调等。
public class BaseResponseEntity implements Serializable {
private static final long serialVersionUID = 1L;
public int code;
public String msg;
public boolean success() {
return ApiConfig.getSucceedCode() == code;
}
public boolean tokenInvalid() {
return ApiConfig.getInvalidateToken() == code;
}
}
这个是项目中所有用到网络请求返回bean的父类,其中code是返回码,根据code判断网络请求状态,例如我有一个请求NBA数据的实例,我返回的bean只需要这样写。
public class NBAEntity extends BaseResponseEntity {
@SerializedName("error_code")
public int code;
public String reason;
public ResultBean result;
@Override
public boolean success() {
return ApiConfig.getSucceedCode() == code;
}
@Override
public boolean tokenInvalid() {
return ApiConfig.getInvalidateToken() == code;
}
public static class ResultBean {
public String title;
}
}
由于我的项目返回的error_code码这个字段并不是code,所以可以采用起别名的方式,然后必须重写success()和tokenInvalid()方法
@SerializedName("error_code")
public int code;
public boolean success() {
return ApiConfig.getSucceedCode() == code;
}
@Override
public boolean tokenInvalid() {
return ApiConfig.getInvalidateToken() == code;
}
这样就可以解决公司返回的code码字段和我封装的字段不相同的问题,当然每个bean都写这段代码显然不是特别友好,所以你可以再封装一个bean继承自BaseResponseEntity,然后给code起别名就可以了,那样项目中的其他的bean只需要继承你自己封装的bean。
public abstract class BaseObserver implements Observer {
/**
* dialog 显示文字
*/
private String mMsg;
private CustomProgressDialogUtils progressDialogUtils;
private Context mContext;
private boolean mShowLoading = false;
/**
* token失效 发送广播标识
*/
public static final String TOKEN_INVALID_TAG = "token_invalid";
public static final String QUIT_APP = "quit_app";
private static final String CONNECT_ERROR = "网络连接失败,请检查网络";
private static final String CONNECT_TIMEOUT = "连接超时,请稍后再试";
private static final String BAD_NETWORK = "服务器异常";
private static final String PARSE_ERROR = "解析服务器响应数据失败";
private static final String UNKNOWN_ERROR = "未知错误";
private static final String RESPONSE_RETURN_ERROR = "服务器返回数据失败";
public BaseObserver() {
}
/**
* 如果传入上下文,那么表示您将开启自定义的进度条
*
* @param context 上下文
*/
public BaseObserver(Context context, boolean isShow) {
this.mContext = context;
this.mShowLoading = isShow;
}
/**
* 如果传入上下文,那么表示您将开启自定义的进度条
*
* @param context 上下文
*/
public BaseObserver(Context context, boolean isShow, String msg) {
this.mContext = context;
this.mShowLoading = isShow;
this.mMsg = msg;
}
@Override
public void onSubscribe(Disposable d) {
onRequestStart();
}
@Override
public void onNext(T response) {
onRequestEnd();
if (response.success()) {
try {
onSuccess(response);
} catch (Exception e) {
e.printStackTrace();
}
} else if (response.getTokenInvalid() == response.code) {
//token失效捕捉,发送广播,在项目中接收该动态广播然后做退出登录等一些列操作
Intent intent = new Intent();
intent.setAction(ApiConfig.getQuitBroadcastReceiverFilter());
intent.putExtra(TOKEN_INVALID_TAG, QUIT_APP);
AppContextUtils.getContext().sendBroadcast(intent);
} else {
try {
onFailing(response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onError(Throwable e) {
onRequestEnd();
if (e instanceof retrofit2.HttpException) {
//HTTP错误
onException(ExceptionReason.BAD_NETWORK);
} else if (e instanceof ConnectException || e instanceof UnknownHostException) {
//连接错误
onException(ExceptionReason.CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) {
//连接超时
onException(ExceptionReason.CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) {
//解析错误
onException(ExceptionReason.PARSE_ERROR);
} else {
//其他错误
onException(ExceptionReason.UNKNOWN_ERROR);
}
}
private void onException(ExceptionReason reason) {
switch (reason) {
case CONNECT_ERROR:
Toast.makeText(AppContextUtils.getContext(), CONNECT_ERROR, Toast.LENGTH_SHORT).show();
break;
case CONNECT_TIMEOUT:
Toast.makeText(AppContextUtils.getContext(), CONNECT_TIMEOUT, Toast.LENGTH_SHORT).show();
break;
case BAD_NETWORK:
Toast.makeText(AppContextUtils.getContext(), BAD_NETWORK, Toast.LENGTH_SHORT).show();
break;
case PARSE_ERROR:
Toast.makeText(AppContextUtils.getContext(), PARSE_ERROR, Toast.LENGTH_SHORT).show();
break;
case UNKNOWN_ERROR:
default:
Toast.makeText(AppContextUtils.getContext(), UNKNOWN_ERROR, Toast.LENGTH_SHORT).show();
break;
}
}
@Override
public void onComplete() {
onRequestEnd();
}
/**
* 网络请求成功并返回正确值
*
* @param response 返回值
*/
public abstract void onSuccess(T response);
/**
* 网络请求成功但是返回值是错误的
*
* @param response 返回值
*/
public void onFailing(T response) {
String message = response.msg;
if (TextUtils.isEmpty(message)) {
Toast.makeText(AppContextUtils.getContext(), RESPONSE_RETURN_ERROR, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(AppContextUtils.getContext(), message, Toast.LENGTH_SHORT).show();
}
}
/**
* 网络请求失败原因
*/
public enum ExceptionReason {
/**
* 解析数据失败
*/
PARSE_ERROR,
/**
* 网络问题
*/
BAD_NETWORK,
/**
* 连接错误
*/
CONNECT_ERROR,
/**
* 连接超时
*/
CONNECT_TIMEOUT,
/**
* 未知错误
*/
UNKNOWN_ERROR
}
/**
* 网络请求开始
*/
private void onRequestStart() {
if (mShowLoading) {
showProgressDialog();
}
}
/**
* 网络请求结束
*/
private void onRequestEnd() {
closeProgressDialog();
}
/**
* 开启Dialog
*/
private void showProgressDialog() {
progressDialogUtils = new CustomProgressDialogUtils();
if (TextUtils.isEmpty(mMsg)) {
progressDialogUtils.showProgress(mContext);
} else {
progressDialogUtils.showProgress(mContext, mMsg);
}
}
/**
* 关闭Dialog
*/
private void closeProgressDialog() {
if (progressDialogUtils != null) {
progressDialogUtils.dismissProgress();
}
}
}
在onNext(T response)对网络请求成功和失败进行处理,如果网络请求成功并且code返回的是成功的code码,则直接用抽象方法做了处理,public abstract void onSuccess(T response),如果code返回成功但是是token失效的code码,则使用了动态广播将消息发送出去,项目中受到该广播后,可以做退出登录等一系列操作。如果网络请求返回失败,我只做了Toast操作。网络请求错误则是在onErrorr(Throwable e)中做了处理。
@Override
public void onError(Throwable e) {
onRequestEnd();
if (e instanceof retrofit2.HttpException) {
//HTTP错误
onException(ExceptionReason.BAD_NETWORK);
} else if (e instanceof ConnectException || e instanceof UnknownHostException) {
//连接错误
onException(ExceptionReason.CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) {
//连接超时
onException(ExceptionReason.CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) {
//解析错误
onException(ExceptionReason.PARSE_ERROR);
} else {
//其他错误
onException(ExceptionReason.UNKNOWN_ERROR);
}
}
public class RetrofitFactory {
private final Retrofit.Builder retrofit;
private Retrofit build;
private RetrofitFactory() {
// 指定缓存路径,缓存大小100Mb
File cacheFile = new File(AppContextUtils.getContext().getCacheDir(), "HttpCache");
Cache cache = new Cache(cacheFile, 1024 * 1024 * 100);
OkHttpClient.Builder httpClientBuilder = new OkHttpClient().newBuilder()
.readTimeout(ApiConfig.getDefaultTimeout(), TimeUnit.MILLISECONDS)
.connectTimeout(ApiConfig.getDefaultTimeout(), TimeUnit.MILLISECONDS)
.addInterceptor(HttpLoggerInterceptor.getLoggerInterceptor())
.addInterceptor(new HttpHeaderInterceptor())
.addNetworkInterceptor(new HttpCacheInterceptor())
.cache(cache);
if (ApiConfig.getOpenHttps()) {
httpClientBuilder.sslSocketFactory(1 == ApiConfig.getSslSocketConfigure().getVerifyType() ?
SslSocketFactory.getSSLSocketFactory(ApiConfig.getSslSocketConfigure().getCertificateInputStream()) :
SslSocketFactory.getSSLSocketFactory(), new UnSafeTrustManager());
httpClientBuilder.hostnameVerifier(new UnSafeHostnameVerify());
}
OkHttpClient httpClient = httpClientBuilder.build();
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.serializeNulls()
.registerTypeAdapterFactory(new NullTypeAdapterFactory())
.create();
retrofit = new Retrofit.Builder()
.client(httpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
if (!TextUtils.isEmpty(ApiConfig.getBaseUrl())) {
build = retrofit.baseUrl(ApiConfig.getBaseUrl()).build();
}
}
private static class RetrofitFactoryHolder {
private static final RetrofitFactory INSTANCE = new RetrofitFactory();
}
public static final RetrofitFactory getInstance() {
return RetrofitFactoryHolder.INSTANCE;
}
/**
* 根据Api接口类生成Api实体
*
* @param clazz 传入的Api接口类
* @return Api实体类
*/
public T create(Class clazz) {
checkNotNull(build, "BaseUrl not init,you should init first!");
return build.create(clazz);
}
/**
* 根据Api接口类生成Api实体
*
* @param baseUrl baseUrl
* @param clazz 传入的Api接口类
* @return Api实体类
*/
public T create(String baseUrl, Class clazz) {
return retrofit.baseUrl(baseUrl).build().create(clazz);
}
private T checkNotNull(@Nullable T object, String message) {
if (object == null) {
throw new NullPointerException(message);
}
return object;
}
}
create()方法之所以有两个,是因为当时项目中涉及到动态切换BaseUrl需求所以才封装了一个两个参数的create方法。
public class HttpCacheInterceptor implements Interceptor {
@Override
@EverythingIsNonNull
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//没网强制从缓存读取
if (!NetworkUtils.isConnected(AppContextUtils.getContext())) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
Log.e("-->", "no network");
}
Response originalResponse = chain.proceed(request);
if (NetworkUtils.isConnected(AppContextUtils.getContext())) {
//有网的时候读接口上的@Headers里的配置
String cacheControl = request.cacheControl().toString();
return originalResponse.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
} else {
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=2419200")
.removeHeader("Pragma")
.build();
}
}
}
public class HttpHeaderInterceptor implements Interceptor {
@Override
@EverythingIsNonNull
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Map heads = ApiConfig.getHeads();
String token = ApiConfig.getToken();
Request.Builder authorization = originalRequest.newBuilder()
.header("Content-type", "application/json")
.header("Authorization", token)
.addHeader("Connection", "close")
.addHeader("Accept-Encoding", "identity");
//动态添加Header
if (null != heads) {
heads.forEach(new BiConsumer() {
@Override
public void accept(String key, String value) {
authorization.addHeader(key, value);
}
});
}
Request build = authorization.build();
return chain.proceed(build);
}
}
public class HttpLoggerInterceptor {
public static HttpLoggingInterceptor getLoggerInterceptor() {
//日志拦截器
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(message -> {
Log.e("-->", message.toString());
});
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return interceptor;
}
}
public class SslSocketFactory {
/**
* HTTPS单向认证
*
* @return
*/
public static SSLSocketFactory getSSLSocketFactory(InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance(ApiConfig.getSslSocketConfigure().getCertificateType());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream input : certificates) {
String iAlias = Integer.toString(index++);
keyStore.setCertificateEntry(iAlias, certificateFactory.generateCertificate(input));
try {
if (null != input) {
input.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
SSLContext sslContext = SSLContext.getInstance(ApiConfig.getSslSocketConfigure().getProtocolType());
TrustManagerFactory managerF = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
managerF.init(keyStore);
sslContext.init(null, managerF.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* HTTPS双向认证
*
* @return
*/
public static SSLSocketFactory getSSLSocketFactory() {
try {
KeyStore keyStore = KeyStore.getInstance(ApiConfig.getSslSocketConfigure().getKeystoreType());
KeyStore trustStore = KeyStore.getInstance(ApiConfig.getSslSocketConfigure().getKeystoreType());
InputStream keyInput = AppContextUtils.getContext().getAssets().open(ApiConfig.getSslSocketConfigure().getClientPriKey());
InputStream trustInput = AppContextUtils.getContext().getAssets().open(ApiConfig.getSslSocketConfigure().getTrustPubKey());
keyStore.load(keyInput, ApiConfig.getSslSocketConfigure().getClientBKSPassword().toCharArray());
trustStore.load(trustInput, ApiConfig.getSslSocketConfigure().getTruststoreBKSPassword().toCharArray());
try {
if (null != keyInput) {
keyInput.close();
}
if (null != keyInput) {
trustInput.close();
}
} catch (IOException e) {
e.printStackTrace();
}
SSLContext sslContext = SSLContext.getInstance(ApiConfig.getSslSocketConfigure().getProtocolType());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(ApiConfig.getSslSocketConfigure().getCertificateType());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(ApiConfig.getSslSocketConfigure().getCertificateType());
trustManagerFactory.init(trustStore);
keyManagerFactory.init(keyStore, ApiConfig.getSslSocketConfigure().getClientBKSPassword().toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
public class UnSafeHostnameVerify implements HostnameVerifier {
@SuppressLint("BadHostnameVerifier")
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
public class UnSafeTrustManager implements X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
public class NullTypeAdapterFactory implements TypeAdapterFactory {
@Override
@SuppressWarnings("unchecked")
public TypeAdapter create(Gson gson, TypeToken type) {
Class rawType= (Class) type.getRawType();
if (rawType!=String.class) {
return null;
}
return (TypeAdapter) new NullAdapter();
}
}
public class NullAdapter extends TypeAdapter {
@Override
public void write(JsonWriter out, String value) throws IOException {
if (null == value) {
out.nullValue();
return;
}
out.value(value);
}
@Override
public String read(JsonReader in) throws IOException {
if (JsonToken.NULL == in.peek()) {
in.nextNull();
return "";
}
return in.nextString();
}
}
这个类是对所有初始化参数进行配置的地方,比如返回码code,BaseURL,失效InvalidateToken,请求头Heads,是否开启https认证等的一系配置。采用了建造者模式,就是为了方便开发者链式调用。
public class ApiConfig implements Serializable {
private static int mInvalidateToken;
private static String mBaseUrl;
private static int mDefaultTimeout = 2000;
private static int mSucceedCode;
private static String mQuitBroadcastReceiverFilter;
private static ArrayMap mHeads;
private static String mToken = "";
private static boolean mOpenHttps;
private static SslSocketConfigure mSslSocketConfigure;
private ApiConfig(Builder builder) {
mInvalidateToken = builder.invalidateToken;
mBaseUrl = builder.baseUrl;
mDefaultTimeout = builder.defaultTimeout;
mSucceedCode = builder.succeedCode;
mQuitBroadcastReceiverFilter = builder.broadcastFilter;
mHeads = builder.heads;
mOpenHttps = builder.openHttps;
mSslSocketConfigure = builder.sslSocketConfigure;
}
public void init(Context appContext) {
AppContextUtils.init(appContext);
}
public static int getInvalidateToken() {
return mInvalidateToken;
}
public static String getBaseUrl() {
return mBaseUrl;
}
public static int getDefaultTimeout() {
return mDefaultTimeout;
}
public static int getSucceedCode() {
return mSucceedCode;
}
public static String getQuitBroadcastReceiverFilter() {
return mQuitBroadcastReceiverFilter;
}
public static ArrayMap getHeads() {
return mHeads;
}
public static void setHeads(ArrayMap mHeads) {
ApiConfig.mHeads = mHeads;
}
public static String getToken() {
return mToken;
}
public static void setToken(String mToken) {
ApiConfig.mToken = mToken;
}
public static boolean getOpenHttps() {
return mOpenHttps;
}
public static SslSocketConfigure getSslSocketConfigure() {
return mSslSocketConfigure;
}
public static final class Builder {
private int invalidateToken;
private String baseUrl;
private int defaultTimeout;
private int succeedCode;
private String broadcastFilter;
private ArrayMap heads;
private boolean openHttps = false;
private SslSocketConfigure sslSocketConfigure;
public Builder setHeads(ArrayMap heads) {
this.heads = heads;
return this;
}
public Builder setFilter(@NonNull String filter) {
this.broadcastFilter = filter;
return this;
}
public Builder setSucceedCode(int succeedCode) {
this.succeedCode = succeedCode;
return this;
}
public Builder setBaseUrl(String mBaseUrl) {
this.baseUrl = mBaseUrl;
return this;
}
public Builder setInvalidateToken(int invalidateToken) {
this.invalidateToken = invalidateToken;
return this;
}
public Builder setDefaultTimeout(int defaultTimeout) {
this.defaultTimeout = defaultTimeout;
return this;
}
public Builder setOpenHttps(boolean open) {
this.openHttps = open;
return this;
}
public Builder setSslSocketConfigure(SslSocketConfigure sslSocketConfigure) {
this.sslSocketConfigure = sslSocketConfigure;
return this;
}
public ApiConfig build() {
return new ApiConfig(this);
}
}
}
采用RxJava进行流的切换操作。
public Single> uploadMultiPicList(@NonNull List pathList) {
return Flowable.fromIterable(pathList).concatMap((Function>) f -> {
Bitmap bitmap = BitmapFactory.decodeFile(f.toString());
File file = compressBitmapToFile(bitmap, AppContextUtils.getContext());
Log.e("-->文件大小:", bytesTrans(file.length()) + ",fileSize=" + file.length() / 1024 + "kb");
RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part imageBodyPart = MultipartBody.Part.createFormData("file", file.getName(), requestBody);
return Flowable.just(imageBodyPart);
}).collect((Callable>) ArrayList::new, List::add)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
由于好久都没有写文章了,所以可能存在一些问题欢迎大家指正。
代码传送门:https://github.com/Mp5A5/HttpRequest 欢迎大家fork或者star。