Retrofit RXjava 网络框架Android网络请求Android-Rxjava&retrofit&dagger

Retrofit + OkHttp +RxJava 网络库构建及

2017-06-16  本文已影响308人  Yagami3zZ

前言:

Retrofit是Square公司开发的一款针对Android网络请求的框架,Retrofit2底层基于OkHttp实现的,OkHttp现在已经得到Google官方认可,大量的app都采用OkHttp做网络请求,其源码详见OkHttp Github

RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。RxJava在处理异步操作时,能够让异步代码异常简洁,且不会随着程序逻辑的复杂性增加而丢失其简洁性。同时Rxjava在涉及到操作的线程切换时也非常的简洁和方便。

这篇文章主要针对已对Retrofit 和RxJava有基本了解的Developer,在OkHttp和RxJava结合使用时,项目应用中的普遍存在的一些问题的解决方案进行介绍。Retrofit和RxJava 基本用法这里不再介绍,感兴趣的童鞋请自行搜索或点击文章最后的推荐链接查阅。项目中用到的Retrofit 和Rxjava版本和配置如下:

compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.3.0'
compile 'io.reactivex:rxjava:1.3.0'
compile 'io.reactivex:rxandroid:1.2.1'

在新项目中发现原来的网络库在使用Retrofit时,是使用Retrofit的同步请求方式,外层通过AsyncTask进行线程异步。调用方式比较繁琐和麻烦。后来决定重新做个网络库,就有了这篇文章。Retrofit本身提供同步和异步调用方式。

同步请求:

BookSearchResponse response =call.execute().body();
网络请求需要在子线程中完成,不能直接在UI线程执行,不然会crash

异步请求:

call.enqueue(newCallback() {
@Override
publicvoid onResponse(Call call,Respons eresponse) {
     asyncText.setText("异步请求结果: "+response.body().books.get(0).altTitle);
}
@Override
publicvoid onFailure(Callcall, Throwable t) {

     }
});

异步请求相对同步请求更简便和快捷,开发者只需要再onResponse和OnFailure中处理对应回调即可。但是这种回调方式本身也有不方便的地方。因为回调直接是在UI线程,如果在OnResponse中回调的数据还要进行耗时操作,比如和数据库中的数据对比,或者返回结果是图片的Url 需要再次通过网络请求得到网络图片,上述回调的方式就需要再开线程来处理,而使用RxJava的话,其优点在于异步操作和线程切换,我们就可以比较优雅和轻松的解决上述问题。

网络库架构图如下:

网络架构.png

先简要看下网络请求配置:

public class OKHttpClientUtils {
    public static OkHttpClient sOkHttpClient;
    private static Converter.Factory sGsonConverterFactory = GsonConverterFactory.create();
    private static Converter.Factory sStringConverterFactory = StringConverterFactory.create();
    private static CallAdapter.Factory sRXJavaCallAdapterFactory =   
    RxJavaCallAdapterFactory.create();
    private static Context sContext; //这里的Context必须是applicationContext

    public static void init(CustomContext context) {
        if (sOkHttpClient == null) {
            sOkHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(30, TimeUnit.SECONDS)
                    .readTimeout(30, TimeUnit.SECONDS)
                    .cookieJar(new CommonCookieJar())
                    .addInterceptor(new CommonAppInterceptor())
                    .build();

            sContext = context.getAppContext().getApplicationContext();
        }
    }
    
    public static class CommonCookieJar implements CookieJar {
        @Override
        public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
            Log.v("OKHttpClientUtils", "response cookieHeader---->" + cookies);
            CookieHelper.saveCookies(cookies);
        }

        @Override
        public List<Cookie> loadForRequest(HttpUrl url) {
            Log.v("OKHttpClientUtils", "requestCookie---->" +                    
            CookieHelper.getCookieHeader(url.uri()));
            return CookieHelper.getCookieHeader(url.uri());
        }
    }

    public static class CommonAppInterceptor implements Interceptor {
       ...//处理公共请求参数统一添加
       ...//处理公共请求Header统一添加
    }
    
    public static <T> T createService(Class<T> clazz) {
        Retrofit retrofit =
                new Retrofit.Builder()
                        .client(sOkHttpClient)
                        .baseUrl(getAndroidHost(clazz))
                        .addConverterFactory(sStringConverterFactory)
                        .addConverterFactory(sGsonConverterFactory)
                        .addCallAdapterFactory(sRXJavaCallAdapterFactory)
                        .build();
        return retrofit.create(clazz);
    }

    /**
     * 获取host  retrofit2 baseUrl 需要以 "/" 结尾
     */
    public static <T> String getAndroidHost(Class<T> clazz) {
       //通过注解拿到各个微服务配置的host
    }
}

上面显示的OkHttpClientUtil中的各项配置下文会介绍。

本文将主要通过以下几个方面进行介绍:

1、通用实体定义:

public class StatusResponse<Result> implements Serializable {
    private static final long serialVersionUID = 6316903436640469387L;

    /**
     * code 取值  说明
     * 0    成功
     * < 0  通用错误码,与具体业务无关
     * > 0  业务错误码
     */
    public int code = 0;
    public String msg;
    public String errorMsg;

    /**
     * showType 说明
     * 0    Toast 形式
     * 1    Alert  形式
     */
    public int showType = -1;

    Result result;

    public boolean isOK() {
        return code == 0;
    }
}

客户端跟服务器端定义的规则为,所有的请求数据包含code,msg,errorMsg,和showType。 Result泛型为各接口返回的数据。其中当code==0 时为正常情况,code<0 时客户端需根据showType 及errorMsg分别用弹框或toast方式提示对应错误信息,code>0客户端需要自行处理对应情况。后续所有网络请求返回数据均按照StatusResponse<T>的形式返回数据。

2、如何优雅地处理服务器返回错误码及自定义异常

因为上面提到客户端需要统一处理code<0的异常情况,所以想要用一种比较优雅的方式来全局处理。查阅了相关资料,发现基本是将code <0 作为一种自定义异常情况来处理。但是报出异常的方式有几种。

一种做法是通过重写GsonConverterFactory,在服务器数据进行Gson转化时,重写GsonResponseBodyConverter 类。

class MyGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final Type type;

    MyGsonResponseBodyConverter(Gson gson, Type type) {
        this.gson = gson;
        this.type = type;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
        try {
            String response = value.string();
            StatusResponse<T> resultResponse = JsonUtil.fromJson(response,type);
            //对返回码进行判断,如果是0,便返回object
            if (resultResponse.code == 0) {
                return resultResponse.infos;
            } else {
                //抛出自定义服务器异常
                throw new ServerException(resultResponse.state, resultResponse.error);
            }
        }finally {
//            Utils.closeQuietly(reader);
        }
    }
}

在convert时 resultResponse.code是否等于0来判断是否抛出自定义的ServerException。但是我觉得这种方式需要重写GsonConverterFactory GsonResponseBodyConverter 等相关类,在使用时还是有不安全性和不便捷性。所以还是选择通过Rxjava的Map方式实现的code码判断和异常抛出。

服务器错误码统一处理及自定义异常.png

我们先来看调用的时候如何调用,可以先不用管MapTransformer 而只看call 方法里的内容

public class MapTransformer<T> implements 
Observable.Transformer<StatusResponse<T>,StatusResponse<T>> {
@Override
public Observable<StatusResponse<T>> call(Observable<StatusResponse<T>> 
statusResponseObservable) {
    return statusResponseObservable.subscribeOn(Schedulers.io())
            .map(new ServerResultFunc<T>()) 
//              Instructs an ObservableSource to pass control to another ObservableSource
//              rather than invoking onError if it encounters an error.
            .onErrorResumeNext(new HttpResultFunc<StatusResponse<T>>())
            .observeOn(AndroidSchedulers.mainThread());
     }
}

主要包括这几个类:

1)ServerResultFunc:

进行Map操作的类,主要是在进行转化的时候,通过判断tStatusResponse.getCode()
是否<0 来决定是否抛出自定义的ServerException 异常。

这里自己也思考了很久,主要包括两个问题。
一个问题是code >0 是否应该作为异常处理,第二个问题是在进行转化的时候,是否应该将StatusResponse去 掉,即 ServerResultFunc<T> implements Func1<StatusResponse<T>, T> 直接将T
而不是StatusResponse<T> 回调给OnNext(参数...) 作为回调参数,这两个问题我们后面解答。

public class ServerResultFunc<T> implements Func1<StatusResponse<T>, StatusResponse<T>> {
    @Override
    public StatusResponse<T> call(StatusResponse<T> tStatusResponse) {
        if (tStatusResponse.getCode() < 0) {
            throw new ServerException(tStatusResponse.getCode(),tStatusResponse.getErrorMsg(),
                    tStatusResponse.getShowType());
        }
        return tStatusResponse;
    }
}

2)ServerException :

public class ServerException extends RuntimeException {

    private static final long serialVersionUID = 8484806560666715715L;
    private int code;
    private String errorMsg;
    private int showType = -1;

    public ServerException(int code, String msg,int showType) {
        this.code = code;
        this.errorMsg = msg;
        this.showType = showType;
    }

    public int getCode() {
        return code;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public int getShowType() {
        return showType;
    }
}

3)HttpResultFunc:

这个类主要是onErrorResumeNext时触发,作用是当遇到error时不会直接触发onError而是先走到HttpResultFunc call方法,即在上面进行Map时,ServerResultFunc中code <0 抛出ServerException时,截获这个exception 使其先到HttpResultFunc 的call方法中,通过ExceptionEngine.handleException(throwable)构造我们的自定义的ApiException再将ApiException 交给OnError进行回调。

public class HttpResultFunc <T> implements Func1<Throwable, Observable<T>> {

    @Override
    public Observable<T> call(Throwable throwable) {
//      Returns an Observable that invokes an Observer's onError method when the Observer subscribes to it.
        return Observable.error(ExceptionEngine.handleException(throwable));
    }
}

4) ExceptionEngine :

public class ExceptionEngine {
    //对应HTTP的状态码
    private static final int UNAUTHORIZED = 401;
    private static final int FORBIDDEN = 403;
    private static final int NOT_FOUND = 404;
    private static final int REQUEST_TIMEOUT = 408;
    private static final int INTERNAL_SERVER_ERROR = 500;
    private static final int BAD_GATEWAY = 502;
    private static final int SERVICE_UNAVAILABLE = 503;
    private static final int GATEWAY_TIMEOUT = 504;

    public static ApiException handleException(Throwable e){
        ApiException ex;
        if (e instanceof HttpException){             //HTTP错误
            HttpException httpException = (HttpException) e;
            ex = new ApiException(e, ERROR.HTTP_ERROR);
            switch(httpException.code()){
                case UNAUTHORIZED:
                case FORBIDDEN:
                case NOT_FOUND:
                case REQUEST_TIMEOUT:
                case GATEWAY_TIMEOUT:
                case INTERNAL_SERVER_ERROR:
                case BAD_GATEWAY:
                case SERVICE_UNAVAILABLE:
                default:
                    ex.setErrorMsg("网络错误");  //均视为网络错误
                    break;
            }
            return ex;
        } else if (e instanceof ServerException){    //服务器返回的错误
            ServerException resultException = (ServerException) e;
            ex = new ApiException(resultException, 
            resultException.getCode(),resultException.getShowType());
            ex.setSpecialException(true);
            ex.setErrorMsg(resultException.getErrorMsg());
            return ex;
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException){
            ex = new ApiException(e, ERROR.PARSE_ERROR);
            ex.setErrorMsg("解析错误");            //均视为解析错误
            return ex;
        }else if(e instanceof ConnectException){
            ex = new ApiException(e, ERROR.NETWORK_ERROR);
            ex.setErrorMsg("连接失败");  //均视为网络错误
            return ex;
        }else {
            ex = new ApiException(e, ERROR.UNKNOWN);
            ex.setErrorMsg("未知错误");          //未知错误
            return ex;
        }
    }
}

5) ERROR:

/**
 * 与服务器约定好的异常 100000以上为客户端定义的错误码code
 */
public class ERROR {
    /**
     * 未知错误
     */
    public static final int UNKNOWN = 100000;
    /**
     * 解析错误
     */
    public static final int PARSE_ERROR = 100001;
    /**
     * 网络错误
     */
    public static final int NETWORK_ERROR = 100002;
    /**
     * 协议出错
     */
    public static final int HTTP_ERROR = 100003;
}

6) ApiException:

 * code 取值  说明
 * 0    成功
 * < 0  通用错误码,与具体业务无关
 * > 0  业务错误码
 * <p>
 * showType 说明
 * 0    Toast 形式
 * 1    Alert  形式
 * msg 无意义。
 * <p>
 * code < 0,框架处理,有errorMsg返回时,参考showType使用Toast或者Alert提示,无errorMsg时,使用客户端内置的出错提示,区分红包、
 * 收银台、主站等不同系统内置提示。code > 0,交由业务逻辑处理,框架不处理。
 */
public class ApiException extends Exception {
    private static final long serialVersionUID = 4932302602588317500L;
    private boolean isSpecialException = false;
    private int code;
    private String errorMsg;
    private int showType = -1;

    public ApiException(Throwable throwable, int code) {
        super(throwable);
        this.code = code;
    }

    public ApiException(Throwable throwable, int code, int showType) {
        this(throwable, code);
        this.showType = showType;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public int getCode() {
        return code;
    }

    public int getShowType() {
        return showType;
    }

    public boolean isSpecialException() {
        return isSpecialException;
    }

    public void setSpecialException(boolean specialException) {
        isSpecialException = specialException;
    }
}

7) BaseSubscriber:

public abstract class BaseSubscriber<T> extends Subscriber<T> {

    public BaseSubscriber(CustomContext tag) {
        SubscriptionManager.getInstance().add(tag, this);
    }
    @Override
    public void onCompleted() {
    }

    @Override
    public void onError(Throwable e) {
        if (e instanceof ApiException) {
            ApiException apiException = (ApiException) e;

            int code = apiException.getCode();
            if (code < 0) {
                String errorMsg = apiException.getErrorMsg();
                int showType = apiException.getShowType();

                //为了和APP主项目解耦,采用EventBus发送消息给MainActivity来进行对应提示
                SubscriberEvent subscriberEvent = new SubscriberEvent(showType, errorMsg);
                EventBus.getDefault().post(subscriberEvent);

                Log.i("network", "onError--errorMsg->" + errorMsg);
                Log.i("network", "onError--code->" + apiException.getCode());
                Log.i("network", "onError--showType->" + showType);

                if (code == -200) {
                    EventBus.getDefault().post(new AuthEvent(false));
                }
            }

            onError((ApiException) e);

        } else {
            onError(new ApiException(e, ERROR.UNKNOWN));
            Log.i("network", "onError-otherError->" + e.toString());
        }
        Crashlytics.logException(e);
        Log.e("network", "exception-->" + e.toString());
    }

    /**
     * 错误回调
     */
    protected abstract void onError(ApiException ex);
}

通过在BaseSubscriber的OnError中统一处理code <0的情况,而 code==0即正常情况,会回调到BaseSubscriber的onNext中,而code>0也是走到onNext的回调。

到这里统一错误码自定义异常处理就完成了,这里我们回到开头提的两个问题

第一 code >0是否应该算作异常,后来经过实践,code>0 最好不算做异常,因为这里要客户端根据不同的code做业务处理,放在onNext处理比较方便,而且onError中无法获取StatusResponse<T>,也就无法满足客户端根据code处理各种业务的需求(各种业务中需要用到StatusResponse<T>的数据)。

第二 在进行转化的时候,是否应该将StatusResponse去掉,即 ServerResultFunc<T> implements Func1<StatusResponse<T>, T> 直接将T而不是StatusResponse<T> 回调给OnNext(参数...) 作为回调参数。如果这样做有个坏处是,OnNext中无法拿到StatusResponse也就无法拿到StatusResponse.getCode()。这个跟我们code>0时客户端自定义处理业务的需求相违背,所以这里仍然保留StatusResponse。

3、简便的调用方式(满足微服务多域名BaseUrl等):

因为项目后台采用微服务,每个模块的接口域名都不一样,即BaseUrl有多个,所以这里需要创建多个Retrofit对象,并通过注解的方式,拿到develop(开发环境) alpha(测试环境)online(正式环境下配置的域名)

1)示例1 ActionCommon.java:

    @HOST(develop = API.Helper.HTTPS_PREFIX + API.Helper.HOST_APP_DEVELOP, 
    alpha = API.Helper.HTTPS_PREFIX + API.Helper.HOST_APP_ALPHA,
    online = API.Helper.HTTPS_PREFIX + API.Helper.HOST_APP_ONLINE)
    
    public interface ActionCommon {
        @GET("ooxx/user/userInfo.do")
        Observable<StatusResponse<UserInfoResponse>> getUserInfo();
    
        @GET("ooxx/index.do")
        Observable<StatusResponse<HallResponse>> hallIndex();
    
        @GET("/user/ooxx/list.do")
        Observable<StatusResponse<BaseListResponse<ListEntity>>> getList(@QueryMap Map<String, String> map);
    
        @GET("/user/ooxx/detail.do")
        Observable<StatusResponse<DetailEntity>> getDetail(@QueryMap Map<String, String> map);
    }

上面的注解HOST配置为这几个接口对应的微服务的域名,分别为develop(开发环境) alpha(测试环境)online(正式环境)下配置的域名)。

2)示例2 ActionBonus.java:

@HOST(develop = API.Helper.HTTPS_PREFIX + API.Helper.HOST_BONUS_DEVELOP,
alpha = API.Helper.HTTPS_PREFIX + API.Helper.HOST_BONUS_ALPHA,
online = API.Helper.HTTPS_PREFIX + API.Helper.HOST_BONUS_ONLINE)

public interface ActionBonus {
    @GET("/bonus/list.do")
    Observable<StatusResponse<BonusResponse>> list(@QueryMap Map<String, String> map);
}

3)API.java:

public class API {
/**
 * 主站服务
 */
public final static ActionCommon ACTION_COMMON = OKHttpClientUtils.createService(ActionCommon.class); 
/**
 * 红包服务
 */
public final static ActionBonus ACTION_BONUS = OKHttpClientUtils.createService(ActionBonus.class);
/**
 * 用户服务
 */
public final static ActionUser ACTION_USER = OKHttpClientUtils.createService(ActionUser.class);

public static class Helper {
    /**
     * 主站服务
     */
    static final String HOST_APP_DEVELOP = "develop.app." + DEVELOP_DOMAIN;
    static final String HOST_APP_ALPHA = "test.app." + ALPHA_DOMAIN;
    static final String HOST_APP_ONLINE = "app." + ONLINE_DOMAIN;
    /**
     * 红包服务
     */
    static final String HOST_BONUS_DEVELOP = "develop.rp." + DEVELOP_DOMAIN;
    static final String HOST_BONUS_ALPHA = "test.rp." + ALPHA_DOMAIN;
    static final String HOST_BONUS_ONLINE = "bonus." + ONLINE_DOMAIN;
    
    ....
    
     }
}

createService中所做操作:

public static <T> T createService(Class<T> clazz) {
    Retrofit retrofit =
            new Retrofit.Builder()
                    .client(sOkHttpClient)
                    .baseUrl(getAndroidHost(clazz))
                    .addConverterFactory(sStringConverterFactory)
                    .addConverterFactory(sGsonConverterFactory)
                    .addCallAdapterFactory(sRXJavaCallAdapterFactory)
                    .build();
    return retrofit.create(clazz);
}

/**
 * 获取host  retrofit2 baseUrl 需要以 "/" 结尾
 */
public static <T> String getAndroidHost(Class<T> clazz) {

    HOST host = clazz.getAnnotation(HOST.class);
    String trueHost;
    try {
        if (MiscUtils.isDevelop(sContext)) {
            // 开发环境
            trueHost = host.develop();
        } else if (MiscUtils.isAlpha(sContext)) {
            // 测试环境
            trueHost = host.alpha();
        } else {
            // 线上环境
            trueHost = host.online();
        }
    } catch (Exception e) {
        // 有异常默认返回线上地址
        e.printStackTrace();
        trueHost = host.online();
    }
    return trueHost + "/";
}

下面看个具体调用实例:

API.ACTION_COMMON = OKHttpClientUtils.createService(ActionCommon.class);

public static Observable<StatusResponse<DetailEntity>> getDetail(String pid, String Id) {
    Map<String, String> params = new HashMap<String,String>();
//        Map<String,String> params=new HashMap<String, String>();
        params.put("pid",pid);
        params.put("id",Id);
        return API.ACTION_COMMON.getDetail(params)
                .compose(new MapTransformer<DetailEntity>());
    }

getDetail(pid,id).subscribe(new BaseSubscriber<StatusResponse<DetailEntity>>(this){

        @Override
        public void onNext(StatusResponse<DetailEntity> data) {
            DetailEntity detailEntity=data.getResult();
            ...
        }
        
        @Override
        protected void onError(ApiException ex) {
            ...
        }
    });

通过getDetail(pid,id) 即可完成该接口的网络请求。当然上述的compose方法只是目前项目中比较普遍的调用方式,如果你在拿到Observable<StatusResponse<DetailEntity>>需要进行其他的map flatmap等操作的话,可以自己实现对应方法的调用,不过需要处理MapTransformer中对服务器错误码自定义异常的处理操作,即(只是举个示例)

API.ACTION_COMMON.getDetail(params).subscribeOn(Schedulers.io())
                .map(new ServerResultFunc<T>())
                ...
                .map(...)
                ...
                .flatMap(...)
                .onErrorResumeNext(new HttpResultFunc<StatusResponse<T>>())
                .observeOn(AndroidSchedulers.mainThread());

4.Cookie本地保存及请求时添加统一处理

Cookie本地保存及请求添加.png
new OkHttpClient.Builder().cookieJar(new CommonCookieJar())
    public static class CommonCookieJar implements CookieJar {
        @Override
        public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
            Log.v("OKHttpClientUtils", "response cookieHeader---->" + cookies);
            CookieHelper.saveCookies(cookies);
        }
    
        @Override
        public List<Cookie> loadForRequest(HttpUrl url) {
            Log.v("OKHttpClientUtils", "requestCookie---->" +                    
            CookieHelper.getCookieHeader(url.uri()));
            return CookieHelper.getCookieHeader(url.uri());
        }
    }

getCookie():

Cookie.Builder build = new Cookie.Builder();
build.name(savedCookieName);
build.value(sp.getString(savedCookieName));
build.domain(API.Helper.getCurrentDomain(context.getAppContext()));
List.add(build.build())
...

saveCookie():

SharedPreference.putString(cookieName,cookieValue);
...

说明:
saveFromResponse(HttpUrl url, List<Cookie> cookies) 中 通过CookieHelper.saveCookies(cookies),
将后台接口返回的cookie保存在本地,并每次更新(客户端本地加了一个cookie的白名单列表,只有在白名单中,才会将对应cookie存储在本地)
loadForRequest(HttpUrl url)中,调用CookieHelper.getCookieHeader(url.uri()),这里主要是将本地数据如token id等数据 构造成Retrofit2的Cookie,然后组装成List<Cookie>,在loadForRequest时传给后台服务器。

5.通过拦截器实现get及post请求的公共参数及Header的统一添加

公共参数和Header的统一添加,是通过OKHttp的拦截器实现。拦截器是OKHttp提供的一种强大的机制,可以监视、重写和重试调用。很多功能比如缓存数据,接口请求的加密解密等,均可以通过拦截器实现。其基础概念和用法可以参考:Okhttp-wiki 之 Interceptors 拦截器

new OkHttpClient.Builder().addInterceptor(new CommonAppInterceptor());

public static class CommonAppInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            String token = null;
            try {
                token =   
                SharedPrefsManager.getInstance(BaseApplication.getContext()).getString(SharedPre
                fsManager.TOKEN);
            } catch (BaseException e) {
                e.printStackTrace();
            }
            Request request = chain.request();
            Request.Builder newBuilder = request.newBuilder();
            // get请求
            if (request.method().equals("GET")) {
                // GET 请求
                HttpUrl.Builder builder = request.url().newBuilder();
                builder.setQueryParameter("t", StringUtil.random());
                if (token != null) {
                    builder.setQueryParameter(AuthProxy.Token, token);
                }
                HttpUrl httpUrl = builder.build();
                newBuilder.url(httpUrl);
               
            } // post请求
            else if (request.method().equals("POST")) {
                //Form表单
                if (request.body() instanceof FormBody) {
                    FormBody.Builder bodyBuilder = new FormBody.Builder();

                    FormBody oldFormBody = (FormBody) request.body();
                    //把原来的参数添加到新的构造器,(因为没找到直接添加,所以就new新的)
                    for (int i = 0; i < oldFormBody.size(); i++) {
                        bodyBuilder.addEncoded(oldFormBody.encodedName(i), oldFormBody.encodedValue(i));
                    }
                    bodyBuilder.addEncoded("t", StringUtil.random());
                    if (token != null) {
                        bodyBuilder.addEncoded(AuthProxy.TOKEN, token);
                    }
                    newBuilder.post(bodyBuilder.build());
                }
                //MultipartBody
                else if (request.body() instanceof MultipartBody) {
                    MultipartBody.Builder multipartBuilder = new 
                    MultipartBody.Builder().setType(MultipartBody.FORM);
                    List<MultipartBody.Part> oldParts = ((MultipartBody) 
                    request.body()).parts();
                    if (oldParts != null && oldParts.size() > 0) {
                        for (MultipartBody.Part part : oldParts) {
                            multipartBuilder.addPart(part);
                        }
                    }

                    multipartBuilder.addFormDataPart("t", StringUtil.random());
                    if (token != null) {
                        multipartBuilder.addFormDataPart(AuthProxy.TOKEN, token);
                    }
                    newBuilder.post(multipartBuilder.build());
                }
            }

            //公共Header的统一添加
            Header[] headers = new Header[]{HeaderManager.getUAHeader(sContext),
                    HeaderManager.getModifiedUAHeader(sContext)};
            for (Header head : headers) {
                newBuilder.addHeader(head.getName(), head.getValue());
            }

            request = newBuilder.build();

            //The network interceptor's Chain has a non-null Connection that can be used to interrogate
            // the IP address and TLS configuration that were used to connect to the webserver.
            //应用拦截器的chain.connection(), request.headers() 为空,网络拦截器不为空
            long t1 = System.nanoTime();
            Log.d("OKHttpClientUtils", String.format("CommonAppInterceptor---->Sending request 
            %s on %s%n%s",request.url(), chain.connection(), request.headers()));

            Response response = chain.proceed(request);

            long t2 = System.nanoTime();
            Log.d("OKHttpClientUtils", String.format("CommonAppInterceptor---->Received response
            for %s in %.1fms%n%s",response.request().url(), (t2 - t1) / 1e6d, 
            response.headers()));

            return response;
        }
    }

get请求比较简单,就是将公共请求参数加入到请求的url中,这里是通过request.url().newBuilder().setQueryParameter(key,value)的方式添加,而不是addQueryParameter,add的话,如果外部调用时也有加这个参数,就会出现请求参数添加了多个的情况,而set的话,可以直接替换(替换是不会造成问题的)。

Post请求需要区分几种情况,看是以表单提交方式FormBody(目前项目post请求基本是这种),还是以MultipartBody(上传文件,图片等比较常用),当然如果还有其他提交方式,比如流数据提交,也是可以在拦截器统一处理的,因为项目暂未用到,这里不再赘述(当然这种情况比较少见,也可以在外部调用时由调用者自行添加而不是在拦截器中统一添加)。

添加公共Hearder Request.newBuilder().addHeader(key,value);

6.如何优雅地取消网络请求回调的全局处理

作为Android开发者比较容易碰到的一个问题就是,在一个页面比如Actiivty,如果这个页面还在进行网络请求,但是用户又要退出这个页面,那么该如何取消这个网络请求呢,其实一般来说,异步操作一旦进行,是无法取消的,所以我们这里只是取消网络请求回调,而不是取消网络请求。RxJava的订阅机制可以通过Subscription.unsubscribe取消订阅,来取消网络请求回调,这样就不会出现网络请求正在进行,页面销毁,请求完成回调到OnNext或onError(UI线程),造成空指针或内存泄漏的问题。

基本思路就是,全局单例中,有个Map<Tag, List<Subscription>> Tag可以理解为各个页面,List<Subscription>为每个页面里网络请求的订阅关系,在该页面销毁时,遍历List<Subscription>,如果Subscription还未被取消订阅,就执行取消订阅操作

取消网络请求回调的全局处理.png

上文提到过的BaseSubscriber

public abstract class BaseSubscriber<T> extends Subscriber<T> {

public BaseSubscriber(CustomContext tag) {
    SubscriptionManager.getInstance().add(tag, this);
}

@Override
public void onCompleted() {
}

@Override
public void onError(Throwable e) {
    if (e instanceof ApiException) {
        ...相关处理
        }
        onError((ApiException) e);

    } else {
        onError(new ApiException(e, ERROR.UNKNOWN));
        Log.i("network", "onError-otherError->" + e.toString());
    }
    Crashlytics.logException(e);
    Log.e("network", "exception-->" + e.toString());
}
/**
 * 错误回调
 */
protected abstract void onError(ApiException ex);

}

构造函数中 添加 SubscriptionManager.getInstance().add(tag, this);

public interface ISubscription<T> {

void add(T tag, Subscription subscription);

void remove(T tag);

void removeAll();

void cancel(T tag);

void cancelAll();

String getName(T tag);

}

public class SubscriptionManager<T> implements ISubscription<T> {
    
    private Map<Object, List<Subscription>> mMap = new HashMap<>();
    
    private static SubscriptionManager sSubscriptionManager;
    
    public SubscriptionManager() {
    }

public static synchronized SubscriptionManager getInstance() {
    if (sSubscriptionManager == null) {
        sSubscriptionManager = new SubscriptionManager();
    }
    return sSubscriptionManager;
}

@Override
public void add(T tag, Subscription subscription) {
    List<Subscription> perPageList = mMap.get(tag);
    if (perPageList == null) {
        perPageList = new ArrayList<>();
        mMap.put(tag, perPageList);
    }

    perPageList.add(subscription);
    mMap.put(tag, perPageList);

}

@Override
public void remove(T tag) {
    if (!mMap.isEmpty()) {
        List<Subscription> perPageList = mMap.get(tag);
        if (perPageList != null && perPageList.size() > 0) {
            mMap.remove(tag);
        }
    }

}

@Override
public void removeAll() {
    if (!mMap.isEmpty()) {
        mMap.clear();
    }
}

@Override
public void cancel(T tag) {
    if (!mMap.isEmpty()) {
        List<Subscription> perPageList = mMap.get(tag);
        if (perPageList != null && perPageList.size() > 0) {
            for (Subscription subscription : perPageList) {
                if (subscription != null && !subscription.isUnsubscribed()) {
                    subscription.unsubscribe();
                }
            }
            Log.d("SubscriptionManager","tag--->"+tag);
            Log.d("SubscriptionManager","perPageList--->"+perPageList.size());
            mMap.remove(tag);
        }
    }

}

@Override
public void cancelAll() {
    if (!mMap.isEmpty()) {
        Set<Object> keys = mMap.keySet();
        for (Object apiKey : keys) {
            cancel((T)apiKey);
        }
    }
}

@Override
public String getName(T tag) {
    return tag.getClass().getName();
}

}

网络请求调用即为

public static Observable<StatusResponse<BaseListResponse<ResultListEntity>>> 
    getResultList(String offset, String pageSize) {
            Map<String, String> params = new HashMap<String,String>();
            params.put("offset", offset);
            params.put("pageSize", pageSize);
            return API.ACTION.getResultList(params)
                    .compose(new MapTransformer<BaseListResponse<ResultListEntity>>());
        }
        
getResultList(mOffset, String.valueOf(DEFAULT_PAGE_SIZE)).subscribe(
new BaseSubscriber<StatusResponse<BaseListResponse<ResultListEntity>>>(this) {
             @Override
              protected void onError(ApiException ex) {
                   onDataFail(ex);
              }
             @Override
             public void onNext(StatusResponse<BaseListResponse<ResultListEntity>> data) {
                   onDataSuccess(data.getResult());
             }
       });

在BaseActivity的onDestroy(),BaseFragment的OnDestroyView()中调用SubscriptionManager.getInstance().cancel(this);即可。
其中,上文中的CustomContext 可以理解为任意的一个接口,BaseActivity BaseFragment BaseContentView(自定义View)等,所有需要全局取消网络请求的类,均需要实现这个接口。实现该接口的类,需要在其生命周期结束时,执行SubscriptionManager.getInstance().cancel(this);进行订阅关系的判断和取消订阅操作。

结语:

本文主要讲述在使用Retrofit和RxJava做网络请求库时,从基础网络配置,通用实体定义,Cookie相关处理,调用方式优化,服务器错误码及自定义异常的全局处理,公共请求参数Header的统一添加,全局取消网络请求回调等项目实践中容易遇到的问题的一些解决方案。还有其他如添加缓存,接口加密解密等比较常见的场景后续可以扩展。

因时间关系文章难免有疏漏,欢迎提出指正,谢谢。同时对RxJava和Retrofit感兴趣的童鞋可以参考以下链接:

1、Retrofit用法详解
2、给 Android 开发者的 RxJava 详解
3、Okhttp-wiki 之 Interceptors 拦截器

上一篇下一篇

猜你喜欢

热点阅读