Android:Okhttp+Retrofit+Rx+RxLificycler+Fragmenation框架搭建自己的技术堆栈(一)

            自己独立开发一个新项目。从技术选型到框架构建,再到具体的代码编辑,单元测试,全部由一个人负责。(说白了,就是把你扔那里,看你能弄出什么幺蛾子)。特此,在这里记录自己的开发过程。

        在没看到产品需求和设计之前,自己先确定大概的项目框架和技术选型。

  •       MVP的设计模式,最大程度上解耦Activity和业务逻辑的关系;
  •       OkHttp+Retrofit 的网络访问方案;
  •       RxJava+RxAndroid+RxLifeCycler实现线程控制的方案   
  •       Fragmenation 实现Fragment的管理;
  •       EventBus实现组件间通信 

        因为是小型项目,所以以上的技术选型基本上可以满足需求。现将各个部分的实现简单记录一下。今天主要记录网络访问这一块。

        网络访问这一块基本上是对OkHttp+Retrofit+Rx的一些封装以及对网络访问错误的统一化处理。先看看我的项目分包和设计到的网络访问的分包吧。


        项目分包分的比较随便,基本上从命名上可以理解

  1.             base:    项目中的基类,,如BaseActivity,BaseFragment等
  2.             bean:    bean类,各种项目中bean对象的封装;
  3.             configs:    项目中的常量和参数(全局性);
  4.             http:    项目中的网络访问部分;
  5.             ui:    界面部分。里面是按照模块分的,每个模块又按照mvp模式继续分包;
  6.             utils:    项目中涉及到的工具类;
  7.             widget    项目中涉及到的自定义View;

       

          其中http下的分包为:

  •     api:Retrofitde 需要的网络接口和api的帮助类(用于实现多个api的管理);
  •     exception:涉及到异常的统一化处理。
  •     function:自定义的实现Rx 中Function接口的类;
  •     intercepter:Okhttp的拦截器;
  •     listener:网络状态的回调;
  •     retrofit:Retrofit的封装和帮助类;
  •     rx:实现对Rx网络访问的封装;


       1.OkHttp+Retrofit的简易封装:

        封装OkHttp
        该方法返回一个okhttp的实例:该实例实现了日志打印,缓存处理,以及自定义消息头。这三者的实现都跟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
        Retrofit是个非常优秀的网络框架。这里我加入了Json解析和Rx2的功能。
public Retrofit retrofit(){
        return  new Retrofit.Builder()
                .baseUrl(HTTP_BASE_URL)
                .client(okHttpClient())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

    }
        封装RetrofitHelper
        RetrofitHelper全局的单利,通过它,可以获得Retrofit的客户端。
    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分类
        ApiHelper主要是用户管理Retrofit的api接口分类。因为很多情况,网络访问接口我都会以模块来分类,一个模块对应一个api接口类。这样做不仅可以便于我们做网络接口的单元测试,同时也方便我们管理和查找我们的接口。
public class ApiHelper {
    private static LotteryApi mLotteryApi;

    public static LotteryApi getLotteryApi(){
        if(mLotteryApi == null){
            RetrofitHelper.getHelper().retrofit().create(LotteryApi.class);
        }
        return mLotteryApi;
    }



}
        当然,因为保密问题,只罗列出了一个接口。

    2.异常的统一化处理

        对于异常的话,这里可以分为两个大类:
        客户端异常:
                客户端异常就是指在非正常的网络访问,或者在网络访问过程中对数据的错误处理触发的,可以在客户端捕获的异常或错误。具体的例子如,连接超时,读写超时,Json解析错误等。这些异常的发生都可以使得客户端程序崩溃,或者异常中断,以至于无法获得我们需要的数据。但是这类数据通常我们可以在代码中捕获。

        服务端异常:
                服务端的异常,说白了就是后端与我们的约定的异常信息。这类信息往往不会触发程序异常奔溃,网络交互功能可以正常进行。但是往往我们无法获取我们期望的数据。数据往往为空。最常见例子就是:我们往往与后端约定返回码200为网络访问成功,非200则为某种异常,这种异常信息往往由后端自己定义。如下图

            当这类异常发生的时候,我们的程序中是正常运行的,不抛出异常则无法使我们的程序中断,也就无法对此做相应的UI处理。当然,也许会有人说,跟以前一样,在拿数据时候先判断返回码。这是一种方式。但是,这种方式有两个弊端。一因为涉及的网络访问太多,每次网络访问都这样写,代码无法复用(如果自己封装一套当然也是可以的),二则是无法实现服务端异常与客户端异常的统一化处理,用这种方式处理服务端异常,往往是在我们从后端拿到数据后,而我们拿到数据后的操作往往是应该配合UI的赋值操作,如果这时候还要去做一系列的异常处理,至少在代码风格上显得不太优雅。所以,将所有的异常进行统一的处理是显得很有必要的。最后,我希望的结果是,在一次网络访问中,只有两种结果,成功与失败。成功可以获取数据,失败知道失败的原因。
        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 异常的统一转化。
        这里自定义了一个工具类,用于将所有的异常统一转化为ApiException:

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框架
        因为使用的RxAndroid+RxJava+RxLifeCycler框架。所以处理时机需要的选择很重要。在这里我的处理逻辑是如下:我们从服务端获取的数据格式一般是包含状态码,状态信息,和返回数据。
{"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));
    }
}
    直接异常统一转化。简单粗暴。

        转化后的异常在哪里去处理呢?当然是在Oberver对象中了,这里,我自定义了一个Oberver对象。主要是实现将网络访问的结果传递。这里的接口是指第一步定义的接口。

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;
        }
    }

}
        代码太简单,没什么好讲的。

        同时,在Observerable中,封装了异常的统一化处理方案,如下
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就可以了。
        当然,这里的内容有一部分设计到了MVP,这里后面会将。
        总结,进行这样封装后,网络访问一句搞定。

    
























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值