自己独立开发一个新项目。从技术选型到框架构建,再到具体的代码编辑,单元测试,全部由一个人负责。(说白了,就是把你扔那里,看你能弄出什么幺蛾子)。特此,在这里记录自己的开发过程。
在没看到产品需求和设计之前,自己先确定大概的项目框架和技术选型。
- MVP的设计模式,最大程度上解耦Activity和业务逻辑的关系;
- OkHttp+Retrofit 的网络访问方案;
- RxJava+RxAndroid+RxLifeCycler实现线程控制的方案
- Fragmenation 实现Fragment的管理;
- EventBus实现组件间通信
因为是小型项目,所以以上的技术选型基本上可以满足需求。现将各个部分的实现简单记录一下。今天主要记录网络访问这一块。
网络访问这一块基本上是对OkHttp+Retrofit+Rx的一些封装以及对网络访问错误的统一化处理。先看看我的项目分包和设计到的网络访问的分包吧。
项目分包分的比较随便,基本上从命名上可以理解
- base: 项目中的基类,,如BaseActivity,BaseFragment等
- bean: bean类,各种项目中bean对象的封装;
- configs: 项目中的常量和参数(全局性);
- http: 项目中的网络访问部分;
- ui: 界面部分。里面是按照模块分的,每个模块又按照mvp模式继续分包;
- utils: 项目中涉及到的工具类;
- widget 项目中涉及到的自定义View;
其中http下的分包为:
- api:Retrofitde 需要的网络接口和api的帮助类(用于实现多个api的管理);
- exception:涉及到异常的统一化处理。
- function:自定义的实现Rx 中Function接口的类;
- intercepter:Okhttp的拦截器;
- listener:网络状态的回调;
- retrofit:Retrofit的封装和帮助类;
- rx:实现对Rx网络访问的封装;
1.OkHttp+Retrofit的简易封装:
封装OkHttp
// 获取http实例;
private OkHttpClient okHttpClient(){
// 缓存目录;
File cacheFile = new File(BaseApplication.getContext().getCacheDir(), HTTP_CASH_FILE_DIR);
// logging 网络拦截
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
// 实例化client
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(NET_CONNECTED_TIME_OUT, TimeUnit.SECONDS)
.readTimeout(NET_READ_TIME_OUT,TimeUnit.SECONDS)
.writeTimeout(NET_WRITE_TIME_OUT,TimeUnit.SECONDS)
.cache(new Cache(cacheFile,HTTP_CASH_SIZE))
.addInterceptor(httpLoggingInterceptor) // 日志
.addInterceptor(new HeadsInterceptor()) // 自定义的消息头
.addInterceptor(new CacheInterceptor()) // 缓存
.build();
return client;
}
日志打印:HttpLogingInterceptor已经帮我们封装的很好了,我们我们只要在我们的gradle文件中声明一下就可以很方便的使用了。
cache缓存的话,我们需要自定义一个缓存目录,(一般在应用缓存目录或者是系统内置存储的缓存目录,当然你也可以自己定义),定义完后,在Okhttp中设置就可以了。当然做完这些是不完美的。因为这些仅仅是自己一厢情愿,你还要在你的收到的响应里面去改几个参数。于是CacheInterceptor就应运而生了。
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//get the origin response;
Response originResponse = chain.proceed(chain.request());
String cacheSetting = originResponse.cacheControl().toString();
// if server can not support cache ,then cacheSetting is null or empty;
if(TextUtils.isEmpty(cacheSetting)){
cacheSetting = "public,max-age=60";
}
// return the rebuild response;
return originResponse.newBuilder()
.addHeader("Cache-Control",cacheSetting)
.removeHeader("Pragma")
.build();
}
}
最后,自定义消息头也是经常性用到的。在用户注册或者登陆后,为了辨别用户身份,后端基本上都会要求我们在网络请求中带上token。如果涉及到加密和解密的话,也会叫我们带上一些公钥。这些都是通过网络拦截器实现的。
public class HeadsInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
SPUtils sp = SPUtils.getInstance();
String deviceId = DeviceUtils.getDeviceId();
String token = sp.getString(CommonConfigs.KEY_TOKEN, null);
String secretKey = sp.getString(CommonConfigs.KEY_SECRET_KEY, null);
Request.Builder builder = chain.request().newBuilder();
// add head;
if(BaseApplication.getLoginState()){
builder.addHeader(CommonConfigs.KEY_DEVICE_ID,deviceId)
.addHeader(CommonConfigs.KEY_TOKEN,token)
.addHeader(CommonConfigs.KEY_SECRET_KEY,secretKey);
}else {
builder.addHeader(CommonConfigs.KEY_DEVICE_ID,deviceId);
}
return chain.proceed(builder.build());
}
}
这里,用户登录后,后端希望我的每个访问都能带上用户token secretKey和deviceId;
封装Retrofit
public Retrofit retrofit(){
return new Retrofit.Builder()
.baseUrl(HTTP_BASE_URL)
.client(okHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
封装RetrofitHelper
private static RetrofitHelper mHelper;
// 私有化构建函数;
private RetrofitHelper(){
}
// 对外提供获取该实例的方法;
public static RetrofitHelper getHelper(){
if(mHelper == null){
synchronized (RetrofitHelper.class){
if(mHelper == null){
mHelper = new RetrofitHelper();
}
}
}
return mHelper;
}
ApiHelper管理Api分类
public class ApiHelper {
private static LotteryApi mLotteryApi;
public static LotteryApi getLotteryApi(){
if(mLotteryApi == null){
RetrofitHelper.getHelper().retrofit().create(LotteryApi.class);
}
return mLotteryApi;
}
}
当然,因为保密问题,只罗列出了一个接口。
2.异常的统一化处理

2.1定义网络回调接口
public interface OnHttpCallback<T> {
// 成功的回调;
void onSucceed();
// 失败的回调;
void onFailed(ApiException exception);
}
该接口只有两个方法,成功和失败。这里使用了泛型。成功的方法中返回成功后的数据。失败的方法中返回失败的异常对象。ApiException这个类是我自定义的异常类,是我们统一的异常类,它只有两个属性,异常码,和异常声明,具体如下;
public class ApiException extends Exception {
private String msg;
private int code;
public ApiException(Throwable throwable, int code){
super(throwable);
this.code = code;
}
public ApiException(String msg, int code){
this.msg = msg;
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
当然,也要为服务端异常封装一个类
public class ServerException extends RuntimeException {
private String msg;
private int code;
public ServerException(int code ,String msg){
this.code = code;
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
2.2 异常的统一转化。
public class ExceptionEngine {
public static final int UN_KNOW_ERROR = 10000;
public static final int ANALYTIC_SERVER_DATA_ERROR = 10001;
public static final int ANALYTIC_CLIENT_DATA_ERROR = 10002;
public static final int NET_CONNECT_TIME_OUT = 10003;
public static final int NET_CONNECT_ERROR = 10004;
// 异常统一化处理;
public static ApiException handleException(Throwable throwable){
ApiException apiException = null;
if(throwable instanceof HttpException){
HttpException httpException = (HttpException) throwable;
apiException = new ApiException(throwable,httpException.code());
apiException.setMsg("网络错误");
}else if (throwable instanceof ServerException){
ServerException serverException = (ServerException) throwable;
apiException = new ApiException(serverException.getMsg(),serverException.getCode());
}else if(throwable instanceof JSONException || throwable instanceof JsonParseException
|| throwable instanceof ParseException || throwable instanceof MalformedJsonException){
apiException = new ApiException(throwable,ANALYTIC_SERVER_DATA_ERROR);
apiException.setMsg("解析错误");
}else if(throwable instanceof ConnectException){
apiException = new ApiException(throwable,NET_CONNECT_ERROR);
apiException.setMsg("连接失败");
}else if(throwable instanceof SocketTimeoutException){
apiException = new ApiException(throwable,NET_CONNECT_TIME_OUT);
apiException.setMsg("连接超时");
}else {
apiException = new ApiException(throwable,UN_KNOW_ERROR);
apiException.setMsg("未知错误");
}
return apiException;
}
2.3:配合Rx框架
{"code":0, "message":"登录成功",”data”:{“token”:3434…….,”secretKey”:ewadwadwad… ,”data”:{……}}}
数据层级上而言,一般情况下,我们需要的数据是data对象。而code和message属性则是我们判断是否发生了服务端异常的标准。一般我们在网络访问成功后,都需要剔除出有用的数据,这个操作一般在Rx 的map方法中进行操作。我们就在map的过程中判断是否存在服务端异常,如果存在则抛出异常。先看看对于服务端数据的封装对象;
public class HttpResponse<T> {
private int code;
private String message;
private T data;
public HttpResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public T getData() {
return data;
}
public boolean isSuccessful(){
if(code == CommonConfigs.HTTP_RESPONSE_SUCCEED_CODE){
return true;
}
return false;
}
}
在Rx的map中,我们如果没有异常,则返回data中数据,如果发生异常,则抛出一个ServerException,于是,我们可以自定义一个Function;
public class MapFunction<T> implements Function<HttpResponse<T>,T> {
@Override
public T apply(HttpResponse<T> tHttpResponse) throws Exception {
// 如果响应码不是约定好的,则抛出服务器异常;
if(!tHttpResponse.isSuccessful()){
throw new ServerException(tHttpResponse.getCode(),tHttpResponse.getMessage());
}
// 过滤出有用数据;
return tHttpResponse.getData();
}
}
在抛出服务端异常后,接下来是对异常的统一转化,这里我是在Rx的onErrorResumeNext()方法中进行的,该方法的作用是Rx在抛出错误后,停止原先Observerable的数据分发,重新订阅onErrorResumeNext()方法中的被观察者,说白了,就是原先的被观察者抛出异常后,会停止对其订阅,转而重新订阅该方法中的被观察者。该方法的内部也是一个function接口;
public class ErrorFunction<T> implements Function<Throwable,Observable<T>> {
@Override
public Observable<T> apply(Throwable throwable) throws Exception {
return Observable.error(ExceptionEngine.handleException(throwable));
}
}
直接异常统一转化。简单粗暴。
public class RxObserver<T> implements Observer<T> {
OnHttpCallback<T> mCallback;
Disposable mDisposable;
public OnHttpCallback<T> getmCallback() {
return mCallback;
}
public RxObserver (OnHttpCallback callback){
this.mCallback = callback;
}
@Override
public void onSubscribe(Disposable d) {
mDisposable = d;
}
@Override
public void onNext(T t) {
if(mCallback!=null){
mCallback.onSucceed();
}
}
@Override
public void onError(Throwable e) {
ApiException exception = null;
if(!(e instanceof ApiException)){
exception = ExceptionEngine.handleException(e);
}
if(mCallback!=null){
mCallback.onFailed(exception);
}
}
@Override
public void onComplete() {
}
// dispose;
public void cancle(){
if(mDisposable != null){
mDisposable.dispose();
mDisposable = null;
}
}
}
代码太简单,没什么好讲的。
public class RxObservable<T>{
Observable<HttpResponse<T>> observable;
public RxObservable(Observable<HttpResponse<T>> observable){
this.observable = observable;
}
public Observable<T> init(){
return observable.map(new MapFunction<T>())
.onErrorResumeNext(new ErrorFunction<T>());
}
}
这里原来是计划采用装饰者模式的。但是怕实现过程中有些东西遗漏掉,就放弃了。
最后,自己封装了一个网络访问的工具类,该工具类将Rx生命周期的回收也加入了进去;
public class RxHelper {
// 网络交互,并且绑定生命周期;
public static void subscribe(@NonNull Observable observable, @NonNull ObservableTransformer
transformer, @NonNull OnHttpCallback callback) {
new RxObservable<>(observable).init()
.compose(transformer)
.subscribeOn(Schedulers.newThread())
.unsubscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new RxObserver(callback));
}
// 网络交互,但是不绑定生命周期;
public static void subscribe(@NonNull Observable observable, @NonNull OnHttpCallback callback) {
new RxObservable<>(observable).init()
.subscribeOn(Schedulers.newThread())
.unsubscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new RxObserver(callback));
}
// 获取被观察者;
public static Observable observable(@NonNull Observable observable){
return new RxObservable<>(observable).init();
}
// 获取观察者;
public static Observer observer(@NonNull OnHttpCallback callback){
return new RxObserver(callback);
}
最后,当我们需要进行网络访问的时候,只要调用RxHelper就可以了。