Retrofit+Rxjava 服务器 IP 轮询重试机制实现

本贴最后更新于 3196 天前,其中的信息可能已经天翻地覆

app在启动时会请求一些配置信息,其中就包括ip的路由表,将这份路由表存储到本地,至于是sp还是对象持久化抑或是其他方式,可根据实际情况自行选择。

因为项目网络层由Retrofit+Rxjava+Okhttp实现,Retrofit运行时无法改变baseUrl,即使可以通过反射的方式来改变baseUrl,也无法对已经生成的service对象起作用,而且我的项目中所有service对象都通过Dagger2注入,所以最终使用了这样一种方式。

url交给UrlManager来管理

public class UrlManager { 
   public static final String[] HOST_SITE = {"https://xxx/api/",xxx}; 
   public static final String[] HOST_WEB = {"https://xxx/",xxx};  
   public static final String HOST_SITE_DEBUG = "https://xxx/api/";  
   public static final String HOST_WEB_DEBUG = "https://xxx/"; 
   public static List<String> list;   
   public static Random random = new Random();  
   public static String getHostSite() {   
      if(BuildConfig.IS_DEBUG) {        
         return processUrl(HOST_SITE_DEBUG);
      } else {
        String host = getDynamicHost(); 
        if (!TextUtils.isEmpty(host)) return host;  
        return HOST_SITE[random.nextInt(HOST_SITE.length)];
      }
  } 
               
  @Nullable
  private static String getDynamicHost() {
      int index = (int) SPUtils.get(NeutronApplication.getContext(), Constants.URL, 0);
      if (list != null && list.size() > 0 && index < list.size())  
          return list.get(index);    
          return null;
  }
  public static String getHostWeb() { 
     if (BuildConfig.IS_DEBUG) {         
         return processUrl(HOST_WEB_DEBUG);
     } else { 
         String host = getDynamicHost();  
         if (!TextUtils.isEmpty(host)) return host;    
         return HOST_WEB[random.nextInt(HOST_WEB.length)];
     }
  } 
   public static void setHosts(List<String> list) {
        UrlManager.list = list;
        RxHelper.setCounterAttempts(list.size());
  }  
  public static void updateUrlIndex(int i) {
       if (list != null && i >= list.size())
        i = 0;
      SPUtils.put(NeutronApplication.getContext(),Constants.URL, i);
  }   
   public static void updateUrlIndex() {  
      int o = (int) SPUtils.get(NeutronApplication.getContext(), Constants.URL, 0);
       updateUrlIndex(o + 1);
 }
}

app启动时拉取到配置后设置UrlManager中的路由表,然后每次根据索引去路由表动态拿请求地址,那路由索引由谁来控制呢?
因为我将项目中的rxjava抽取了一层RxHelper,所以这件事就交给RxHelper来干了,可以覆盖所有的网络请求。

public class RxHelper {  
  private static final int COUNTER_START = 0;  
  private static int COUNTER_ATTEMPTS = 3;  
  public static void setCounterAttempts(int counterAttempts) {
        COUNTER_ATTEMPTS = counterAttempts;
  } 
  public static <T> rx.Observable.Transformer<T, T> handleResult() { 
         return tObservable -> tObservable
                .flatMap(RxHelper::createData)
                .retryWhen(observable -> observable.compose(zipWithFlatMap()))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io());
  }   
  public static <T> rx.Observable.Transformer<T,T> handleResultWithOutRetryPolicy(){
          return tObservable -> tObservable.flatMap(RxHelper::createData)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io());
  }
  public static <T> Observable.Transformer<T, Long> zipWithFlatMap() { 
         return observable ->
                observable.zipWith(Observable.range(COUNTER_START, COUNTER_ATTEMPTS),
                        (t, repeatAttempt) -> repeatAttempt)
                        .flatMap(new Func1<Integer, Observable<Long>>() {  
                            @Override
                            public Observable<Long> call(Integer repeatAttempt) {
                                UrlManager.updateUrlIndex(repeatAttempt);  
                                return Observable.timer(repeatAttempt * 200, TimeUnit.MILLISECONDS);
                            }
                        });
  }   
  private static <T> Observable<T> createData(final T t) { 
         return Observable.create(new Observable.OnSubscribe<T>() {  
            @Override
            public void call(Subscriber<? super T> subscriber) {  
                try {
                    subscriber.onNext(t);
                    subscriber.onCompleted();
                } catch (Exception e) {
                    LogUtils.logw("Rxhelper: " + e.toString());
                    subscriber.onError(e);
                }
            }
        });
    }
}

这样在每次请求错误时,会递增路由表索引,继续下次请求,轮询的间隔为Observable.timer(repeatAttempt * 200, TimeUnit.MILLISECONDS);
对RxJava的retryWhen不理解的同学请移步对RxJava中.repeatWhen()和.retryWhen()操作符的思考

之前也说了,retrofit不能修改baseUrl,反射的方式也不适合我的项目,至于利用builder生成新的retrofit对象的方式更不考虑了,那我是怎么实现运行时修改请求地址的呢?别忘了okhttp是可以添加拦截器的,在OkHttpIntercepter中:

public class OkHttpInterceptor implements Interceptor {   
    @Override
    public Response intercept(Chain chain) throws IOException {     
       //配置request
        Request request = chain.request();
        Request.Builder requestBuilder = request.newBuilder();
        String url = UrlManager.getHostSite();
        Uri parse = Uri.parse(url);
        String host = parse.getHost();
        HttpUrl httpUrl = request.url().newBuilder().host(host).build();
        requestBuilder.url(httpUrl);
        Response.Builder responseBuilder = chain.proceed(requestBuilder.build()).newBuilder();
        Response response = responseBuilder.build();       
        return response;
    }
}

拦截请求的url,修改其host,这样整个流程就ok了,http的各种错误码的处理也是可以在拦截器中统一处理的,至于其他健壮性的考虑此处就不做过多阐述了。

有同学问我,如果想处理最后一次error通知怎么办呢?可以这样做,修改过的RxHelper如下:

public class RxHelper {  
     private static final int COUNTER_START = 0; 
     private static int COUNTER_ATTEMPTS = 3;   
     public static void setCounterAttempts(int counterAttempts) {
        COUNTER_ATTEMPTS = counterAttempts;
     }   
    public static <T> rx.Observable.Transformer<T, T> handleResult() { 
           return tObservable -> tObservable
                .flatMap(RxHelper::createData)
                .retryWhen(error -> delayedRetry(error))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io());
    }    
    //猫腻主要在这个方法
    private static Observable<Object> delayedRetry(Observable<? extends Throwable> error) {
            return error.zipWith(Observable.range(COUNTER_START, COUNTER_ATTEMPTS + 1),
                (i, repeatAttempt) -> repeatAttempt)
                .flatMap(o -> {
                    UrlManager.updateUrlIndex(o);
                    LogUtils.logw("repeat: " + o);    
                    return o < COUNTER_ATTEMPTS ? Observable.timer(o * 200, TimeUnit.MILLISECONDS)
                            : error.flatMap(Observable::error);
                });
    }  
    public static <T> rx.Observable.Transformer<T, T> handleResultWithOutRetryPolicy() {
            return tObservable -> tObservable.flatMap(RxHelper::createData)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io());
    }   
    private static <T> Observable<T> createData(final T t) {   
         return Observable.create(new Observable.OnSubscribe<T>() {    
            @Override
            public void call(Subscriber<? super T> subscriber) {  
                try {
                    subscriber.onNext(t);
                    subscriber.onCompleted();
                } catch (Exception e) {
                    subscriber.onError(e);
                }
            }
        });
    }
}

转自:https://gold.xitu.io/post/584e6d5961ff4b0058e9240a



  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1062 引用 • 3455 回帖 • 139 关注
  • 郑禄秀
    9 引用 • 8 回帖
  • istarvip
    9 引用 • 2 回帖
  • 猿码阁
    19 引用 • 14 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
idxiu
show me the code, change the world! 北京

推荐标签 标签

  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    12 引用 • 5 回帖 • 643 关注
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    293 引用 • 4496 回帖 • 664 关注
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 453 关注
  • 反馈

    Communication channel for makers and users.

    120 引用 • 906 回帖 • 290 关注
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖 • 2 关注
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 192 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 616 关注
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    79 引用 • 431 回帖
  • 区块链

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法 。

    92 引用 • 752 回帖
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 114 关注
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 695 关注
  • 微服务

    微服务架构是一种架构模式,它提倡将单一应用划分成一组小的服务。服务之间互相协调,互相配合,为用户提供最终价值。每个服务运行在独立的进程中。服务于服务之间才用轻量级的通信机制互相沟通。每个服务都围绕着具体业务构建,能够被独立的部署。

    97 引用 • 155 回帖 • 1 关注
  • 脑图

    脑图又叫思维导图,是表达发散性思维的有效图形思维工具 ,它简单却又很有效,是一种实用性的思维工具。

    32 引用 • 100 回帖
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 10 关注
  • 印象笔记
    3 引用 • 21 回帖 • 1 关注
  • Eclipse

    Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

    76 引用 • 258 回帖 • 641 关注
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    29 引用 • 202 回帖 • 39 关注
  • OAuth

    OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。oAuth 是 Open Authorization 的简写。

    36 引用 • 103 回帖 • 39 关注
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 114 关注
  • CodeMirror
    2 引用 • 17 回帖 • 184 关注
  • 分享

    有什么新发现就分享给大家吧!

    249 引用 • 1799 回帖
  • Outlook
    1 引用 • 5 回帖 • 2 关注
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 658 关注
  • Unity

    Unity 是由 Unity Technologies 开发的一个让开发者可以轻松创建诸如 2D、3D 多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

    25 引用 • 7 回帖 • 111 关注
  • 支付宝

    支付宝是全球领先的独立第三方支付平台,致力于为广大用户提供安全快速的电子支付/网上支付/安全支付/手机支付体验,及转账收款/水电煤缴费/信用卡还款/AA 收款等生活服务应用。

    29 引用 • 347 回帖 • 1 关注
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 565 关注
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖 • 2 关注