互连网库营造及项目实施亚洲城网页版

原来的书文地址:
http://www.jianshu.com/p/2919bdb8d09a

前言:

Retrofit是Square公司费用的1款针对Android网络请求的框架,Retrofit二底层基于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
上运用可观望的行列来整合异步的、基于事件的先后的库)。OdysseyxJava在处理异步操作时,可以让异步代码卓殊简洁,且不会趁机程序逻辑的复杂扩张而丢掉其简洁性。同时科雷傲xjava在关系到操作的线程切换时也相当的简短和造福。

那篇作品重要针对已对Retrofit
和SportagexJava有核心精通的Developer,在OkHttp和奥迪Q7xJava结合使用时,项目接纳中的广泛存在的局地标题标消除方案张开介绍。Retrofit和KoleosxJava
基本用法这里不再介绍,感兴趣的童鞋请自行检索或点击作品最终的推荐介绍链接查阅。项目中用到的Retrofit
和ENVISIONxjava版本和布署如下:

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
必要再行经过互连网请求获得网络图片,上述回调的艺术就要求再开线程来拍卖,而使用TiguanxJava的话,其亮点在于异步操作和线程切换,大家就能够相比较优雅和轻巧的消除上述难点。

网络库架构图如下:

亚洲城网页版 1

互联网框架结构.png

前言

Hello,我是 JessYan,作为三个爱好探究新型消除方案的本人,在
上篇小说
中,向大家介绍了哪些通过壹行代码就能够兑现上传下载以及 Glide
进程监听,今后又给大家带来了另1项大家都很期待的难题的缓解方案,那几个主题素材源点于
MVPArms
的一个
Issues
,当然使用 Retrofit 时,四个 BaseUrl 以及动态切换 BaseUrl
那多少个供给,在别的地点也每每被研究,那么上边就来讲讲笔者的笔触和平解决决方案

Github : 你的 Star 是自己坚定不移的动力✊

亚洲城网页版 2

gif

先简要看下互联网请求配置:

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般支出中大约人曾经碰到了那七个须要的风貌,但为了让有个别从前没境遇那个情状的爱人,也能看懂那篇文章,所以先在前头提一提

本文将根本透过以下几个地点开始展览介绍:

  • #### 通用实体定义

  • #### 怎样优雅地拍卖服务器重回错误码及自定义相当

  • #### 简便的调用方式(满意微服务多域名BaseUrl等)

  • #### Cookie本地保存及请求时增添统壹处理

  • #### 通过拦截器完结get及post请求的共用参数及国有Header的联结增多

  • #### 如何优雅地撤废互连网请求回调的全局处理

七个 BaseUrl 的供给意况

要是项目是聚合型 App
,比如像某些信息资源信息类客户端,或许数据源来自于几个平台,比如说新浪啊,豆瓣啊,新浪啊,所以这样就会涉及到四个
BaseUrl

倘若项目应用到三个叁方服务提供商,比如图片的读取使用到一个服务商,文件的仓储又利用到另1个服务商,这些也会设有2个
App 出现五个 BaseUrl

一、通用实体定义:

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>的样式重临数据。

动态改动 BaseUrl 的必要处境

若是项指标 BaseUrl 会在 App
运维时,请求服务器,依据服务器的回到结果,来分明项目最后的
BaseUrl,就会涉及到运维时动态切换 BaseUrl

一经项指标有些3方服务提供商,并不是永久的,也许会并发转移的事态,比如存款和储蓄服务从7牛迁移至别的云存储,那我们为了制止改动代码导致重新包装以及发版,就会从服务器获取叁方服务提供商的
BaseUrl ,然后在运行时动态退换那几个 BaseUrl

二、怎样优雅地拍卖服务器再次回到错误码及自定义卓绝

因为地点提到客户端须求联合处理code<0的十分境况,所以想要用一种比较优雅的方法来全局处理。查阅了有关材料,发现中央是将code
<0 作为一种自定义万分情况来处理。不过报出格外的点子有三种。

1种做法是由此重写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
等相关类,在运用时依然有不安全性和艰辛捷性。所以依然选取经过本田CR-Vxjava的Map格局达成的code码推断和相当抛出。

亚洲城网页版 3

服务器错误码统1处理及自定义极度.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());
     }
}

驷比不上舌不外乎那多少个类:

缓解方案

实质上官方 Api 早早已提供了消除方案来支撑多个 BaseUrl
以及运行时动态改换 BaseUrl ,民间也同样有很多缓解方案

<a name=”1″></a>

1)ServerResultFunc:

进展Map操作的类,主假如在开始展览转载的时候,通过推断tStatusResponse.getCode()
是否<0 来调节是还是不是抛出自定义的ServerException 万分。

此间和谐也思索了很久,首要包涵多少个难题。
1个标题是code >0
是不是应当作为那些处理,第二个难题是在实行转向的时候,是还是不是相应将StatusResponse去
掉,即 ServerResultFunc<T> implements
Func一<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;
    }
}

法定静态化解方案

熟悉 Retrofit 的开荒者应该领会 @Get , @Post
这一个标注到种种接口方法上的评释不仅能够传相对路线,还足以传全路线,那样我们就能够完结不等的接口使用分化的
BaseUrl ,从而完成使用几个 BaseUrl 的要求,不过注解上的值只好是
Final 的常量,无法动态退换,所以笔者称那个化解方案为静态消除方案

<a name=”2″></a>

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

合法动态消除方案

熟悉 Retrofit 的开拓者也同样清楚 @Url
那几个标注到种种接口方法参数上的表明,它能够将全路径作为参数字传送进接口作为每一趟请求的
Url 地址,每一趟请求接口都能够将差别的全路线作为参数,从而达成协理五个
BaseUrl 以及在运作时动态改动 BaseUrl
,所以广大请求图片等财富的接口都以选择那么些方案(咦,看样子这一个官方消除方案不是还要缓解自个儿关系的这一个难题吗,别急,先往前面看!)

<a name=”3″></a>

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

民间常用化解方案

事先也看过无数开源的聚合类 App 源码,像某些整合 知乎 , 豆瓣 ,
Gank 等八个阳台数据的 App ,因为个别平台的域名不一样,所以大多数那类
App 会给各样平台都分别创设1个 Retrofit 对象,即分歧的
BaseUrl 使用差异的 Retrofit 对象来创立 ApiService
举办呼吁,那样只要新添叁个两样的 BaseUrl ,那就须要再行成立二个新的
Retrofit 对象

诸如此类也得以而且完结,扶助多个 BaseUrl 以及运转时动态改动 BaseUrl
那多个必要,不过以个体的思想,成立多少个别的布署属性1模同样,只是
BaseUrl 不平等的 Retrofit 对象,太过度浪费能源

<a name=”4″></a>

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

民间大咖消除方案

在此以前偶然看到了一个 Retrofit 维护者, Square 集团的大牌的
缓解方案,用来缓解运维时动态改变
BaseUrl ,其实也算半合法的化解方案

提到那一个消除方案时,不得不讲三个佳话,其实前边 Retrofit
暗中同意是协助运营时动态退换 BaseUrl 的,从前是有五个名字为 BaseUrl
的接口,而 Retrofit.Builder#baseUrl(BaseUrl)
方法当时传的参数就是以此 BaseUrl ,而不是明日的 HttpUrl
,这么些接口内部就有3个办法重返 HttpUrl ,那时候如若实现 BaseUrl
后,动态改动那些方法的重回值,就足以兑现动态改变 BaseUrl

唯独那位大腕以为那样的做法不安全,所以提了三个 Pull
Requests

,删掉了那一个 BaseUrl 接口,并用地点的消除方案代替之,而密切的
JakeWharton 同意了她的观念,并联合了那一个 PR 于是才有了未来的
Retrofit.Builder#baseUrl(HttpUrl) 那些不能够动态更换 BaseUrl
Api

Retrofit 相比较早的老司机,应该理解之前有3个那些
Api,笔者是说后来的本子怎么没了,原来毁在了这位兄台手上

以此方案也等于使用 Interceptor 拦截器,动态更改种种 Request
Url 从而完成动态改造 BaseUrl,但她那几个消除方案不能支撑多
BaseUrl ,只要 host 1设置,直到下2次变动 Host
以前,前边的全体 Request 都必须选拔同3个 Host
,还有壹部分害处前面一起分析

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

淘汰含有强烈瑕疵的方案

多少个方案中,小编第一淘汰的正是
民间常用消除方案
,在前面已经众所周知了自个儿的见解,因为自个儿个人以为制造多个其余安顿属性一模同样,只是
BaseUrl 分化的 Retrofit
对象,太过头浪费财富,所以即便他能满足自小编的具备须求,除非真的没有更加好的消除方案,不然我是不会选拔它的

剩下的多个方案中,
合法静态消除方案
只好化解,三个须求中的补助多少个 BaseUrl ,而对于动态改换 BaseUrl
,由于表明的 Value
只可以为常量,所以对这几个须要也是无法的(多个须要都满足,才代表可行)

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。

何人是最优方案?

实则在日前早已说了
合法动态消除方案
就已经得以而且完成多 BaseUrl 和平运动转时动态更改 BaseUrl
,那怎么小编不直接选取这些方案,还要继续分析呢?

答案也异常粗略,小编以为这一个方案,尽管灵活,然而灵活却给它带来了利用上的繁琐,每一个接口每趟调用都不能够不传入全路径作为参数,不仅麻烦而且接口一多还倒霉管理

民间大拿化解方案
可行? 可是自己在头里早已说了这一个不可行啊?

以此方案尽管能够支撑运营时动态切换 BaseUrl
可是它是大局处理,1经使用更换的是有着请求的 Url ,所以它并不支持多
BaseUrl

并且更吓人的是,这些方案不仅不协理多 BaseUrl ,还会影响
法定静态化解方案
合法动态化解方案
那四个帮助多 BaseUrl
的方案,因为不论是您申明里面注明的是什么全路径,它的 Interceptor
拦截器,都会严酷将那些请求的 Url 改成它的 BaseUrl
,所以那一个方案注定只适合唯有三个 BaseUrl 但须要动态改造的品种

那岂不是 4 个缓解方案都不可行?说这么久说个毛线啊?

3、简便的调用格局(满意微服务多域名BaseUrl等):

因为品种后台采取微服务,种种模块的接口域名都不平等,即BaseUrl有五个,所以这边须要成立三个Retrofit对象,并通过注脚的方法,获得develop(开垦条件)
alpha(测试环境)online(正式环境下安排的域名)

方案总体淘汰?散会?

等等别急啊,就算自个儿站在自作者的角度, Pass 了文中提到的兼具已存在的化解方案

但是大家细心讨论,要是网上早就存在完美的消除方案,那小编还写那篇小说有如何含义?必定是从未有过自个儿乐意的消除方案,作者才会融洽出手去消除并享受啊,毕竟小编是三个不乐意写重复内容的成材青年,只尽管本人写的剧情自然是会让大家学到差异的学识3✊,不然不是砸本人招牌

好了,不逗我们了,开整!

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

哪些革新原有方案?

上边的辨析已经说了
民间大拿消除方案 ,可以在
Interceptor 拦截器中设置二个大局的 Host(Host 能够清楚为 BaseUrl)
,拦截器会严酷将以此 Host 应用到持有的呼吁上,改换该请求原有的
Url,那样产生了只会同时设有1个 Host

由此本身在想,将以此唯一的 Host 变量改为汇聚,以存款和储蓄四个 Host
,在将不一致的 Host 应用到分歧的伸手上,不就能够支持多 BaseUrl

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;

    ....

     }
}

create瑟维斯中所做操作:

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());

推行想法

说干就干,于是自身要好建了三个大局的容器来储存几个 Host,那样笔者就能够在
App 运转时的其他时间,任哪个地方方随意新添,修改,删除 Host

四.Cookie地方保存及请求时增加统一处理

亚洲城网页版 4

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保存在本地,并每便换代(客户端本地加了2个cookie的白名单列表,只有在白名单中,才会将对应cookie存款和储蓄在本地)
loadForRequest(HttpUrl
url)中,调用CookieHelper.getCookieHeader(url.uri()),那里最主假如将地面数据如token
id等数据
构形成Retrofit2的Cookie,然后组装成List<Cookie>,在loadForRequest时传给后台服务器。

相遇标题

只是难题来了,小编想要将不一致的 Host
应用到不一致的呼吁上,但自小编怎么知道哪些请求必要什么样的 Host
,每一个请求总要有个标记,让自己精晓她必要哪些的 Host

于是乎小编就在想 Retrofit
有怎样点子,能够在乞请在此以前给种种请求加上分歧的字符串标志,于是作者很自然的想到了
Header ,Retrofit 正好有 @Headers
那几个证明,能够给各类接口方法上参加自定义 Header

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(上传文件,图片等相比常用),当然假若还有任何提交格局,比如流数据交由,也是足以在拦截器统1处理的,因为项目暂未用到,那里不再赘述(当然那种景况相比少见,也得以在外表调用时由调用者自行加多而不是在拦截器中联合增加)。

足够公共Hearder Request.newBuilder().addHeader(key,value);

再也解决难点

作者给急需差别 BaseUrl 的接口方法上参预了自定义的 Header
,以标明各样接口供给的 HostName ,而这个 Name 对应的值便是
Host,但这几个值不是在 @Headers 中被内定的,它是足以动态改换的

存储 Host 的容器是二个 Map, key 就是那个 Name ,value 才是
Host ,拦截器每趟拦截到请求时,会咬定这些请求是还是不是有那么些自定义
Header,
有的话,获得那么些 Header 中标注的 Name,然后用这么些 Name
,去那么些存款和储蓄 Host 的全局 Mapget(name),得到对应的 Host
再应用到请求上不是就直达援救七个 BaseUrl 了?

只要想动态改换有些 Host 也简单,将新的 Host 以同样的 Name
put(name) 进那几个大局 Map ,到时候拦截器,使用这些 Name
get(name) 出来的值,就已经是改动后新星的 Host ,在将那个 Host
应用到请求上不是就高达动态改变 BaseUrl 了?

那不,四个供给同时满足!

6.什么样优雅地收回网络请求回调的大局处理

作为Android开垦者相比便于蒙受的一个主题素材尽管,在贰个页面比如Actiivty,假设那个页面还在进展网络请求,可是用户又要剥离那个页面,那么该怎样撤销那一个互联网请求呢,其实壹般的话,异步操作1旦进行,是无力回天收回的,所以我们那里只是注销互联网请求回调,而不是撤除互联网请求。福特ExplorerxJava的订阅机制得以透过Subscription.unsubscribe撤废订阅,来撤废网络请求回调,那样就不会现出互连网请求正在张开,页面销毁,请求实现回调到OnNext或onError(UI线程),产生空指针或内部存款和储蓄器泄漏的题目。

基本思路正是,全局单例中,有个Map<Tag, List<Subscription>>
Tag能够驾驭为顺序页面,List<Subscription>为各种页面里网络请求的订阅关系,在该页面销毁时,遍历List<Subscription>,假使Subscription还未被撤回订阅,就实施打消订阅操作

亚洲城网页版 5

收回网络请求回调的大局处理.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 能够知道为私自的2个接口,BaseActivity
BaseFragment
BaseContentView(自定义View)等,全数要求全局撤销互联网请求的类,均供给达成那几个接口。完结该接口的类,供给在其生命周期截止时,实践SubscriptionManager.getInstance().cancel(this);实行订阅关系的决断和注销订阅操作。

优化方案

以此方案就两步,给急需差别 BaseUrl 的请求设置 Header (想用
Retrofit 默认 BaseUrl 的接口,或然利用
官方静态消除方案,
法定动态消除方案
就不必要安装),在经过全局容器来管理 BaseUrl

本着于那种唯有七个 BaseUrl 但必要动态改换的档次,本框架提供了多少个
GlobalDomain 来优化那一个现象,不须要给接口加 Header
,只要求一步,向全局容器 put(GlobalDomain) 你想要更改的 BaseUrl
就足以了

法定动态解决方案
给各种接口传全路线作为参数,要简明的多,
合法动态化解方案
注定只适合那种只有1多少个须求动态改动 BaseUrl 的接口

结语:

正文主要描述在行使Retrofit和奥迪Q7xJava做网络请求库时,从基础网络计划,通用实体定义,库克ie相关处理,调用格局优化,服务器错误码及自定义卓殊的全局处理,公共请求参数Header的会合增多,全局裁撤网络请求回调等品类试行中易于遇到的标题标局地缓解方案。还有任何如增多缓存,接口加密解密等比较普遍的景况后续能够扩大。

因时间涉及小说难免有遗漏,欢迎建议指正,多谢。同时对MuranoxJava和Retrofit感兴趣的童鞋能够参考以下链接:

一、Retrofit用法详解
二、给 Android 开垦者的 RAV四xJava
详解

3、Okhttp-wiki 之 Interceptors
拦截器

总结

以上提到的解决方案,已经优化并封装成了三方库并上传至
Jcenter,方便大家利用

本化解方案首要适合,要求同时负有多 BaseUrl 以及动态改动 BaseUrl
的连串,可能唯有一个 BaseUrl ,但供给动态退换 BaseUrl 的项目

比方对于只必要多 BaseUrl 不要求动态更换 BaseUrl 的项目,其实用
合法静态化解方案
就已经充足了,但自个儿依旧引入用自家的那些消除方案,因为急需都是会变的,假如只要要进入动态改造
BaseUrl 的供给,如须要动态切换 生产环境 和 开拓环境
,那那时如何做,3个个改掉每一种接口注脚里面包车型大巴全路线?

Github : 切切实实使用看 德姆o ,记得 Star
!


Hello 笔者叫Jessyan,借使您喜爱作者的小说,能够在以下平台关心我

— The end

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图