如果优雅地处理多个请求并在请求结束后统一处理

转载请注明出处: http://blog.csdn.net/brucehurrican/article/details/73793288

如何优雅地处理多个请求并在请求结束后统一处理

前不久我接到一个需求,首页更新的数据是从3个接口获取的,三个接口获取到的数据后再刷新界面,大家可以脑补X东,X宝的app首页,屏幕从上到下,上面是banner区,用来展示促销商品之类的广告,中间是几个按钮区,方便用户分类进入相应的模块,如XX超市,XX家电,XX生鲜,话费充值之类的,下面是推荐商品展示区。

我司app也是这种大众脸。开发时,后台童鞋针对首页数据提供了三个接口,分别对应banner区简称A区,按钮区简称B区,展示区简称C区。从事android开发的都知道,这种通过网络获取数据的耗时操作是不能放在UI线程中进行的,耗时的操作必需放在非UI线程中进行。

下面是示例代码

public class Demo {

    public static final long TIMEOUT = 5L;

    private String getBannerInfo() {
        try {
            System.out.println("开始获取banner区数据");
            TimeUnit.SECONDS.sleep(TIMEOUT);
            System.out.println("获取banner区数据请求结束");
            return "一条广告";
        } catch (InterruptedException e) {
            e.printStackTrace();
            return "数据异常";
        }
    }

    private int getButtonVisibleCount() {
        try {
            System.out.println("开始获取按钮区可显示个数");
            TimeUnit.SECONDS.sleep(TIMEOUT);
            System.out.println("获取按钮区可显示个数请求结束");
            return 8;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return -1;
        }
    }

    private String getShowAreaImgUrl() {
        try {
            System.out.println("开始获取展示区图片地址");
            TimeUnit.SECONDS.sleep(TIMEOUT);
            System.out.println("获取展示区图片地址请求结束");
            // 测试图片
            return "http://xxx.1112222.jpg";
        } catch (InterruptedException e) {
            e.printStackTrace();
            return "";
        }
    }

    private void updateUI(String bannerInfo, int buttonCount, String imgUrl) {
        System.out.println(String.format("更新banner区: %s, 可显示按钮个数: %d, 展示区图片地址: %s", bannerInfo, buttonCount, imgUrl));
    }

    public static void main(String[] args) {
        final Demo demo = new Demo();
        System.out.println("开始获取服务器数据-------");
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                String bannerInfo = demo.getBannerInfo();
                int buttonCount = demo.getButtonVisibleCount();
                String imgURL = demo.getShowAreaImgUrl();
                System.out.println("banner区信息: " + bannerInfo);
                System.out.println("可以显示的按钮个数: " + buttonCount);
                System.out.println("展示图片URL: " + imgURL);
                demo.updateUI(bannerInfo, buttonCount, imgURL);
            }
        });
    }
}

结果如下:

开始获取服务器数据——-
开始获取banner区数据
获取banner区数据请求结束
开始获取按钮区可显示个数
获取按钮区可显示个数请求结束
开始获取展示区图片地址
获取展示区图片地址请求结束
banner区信息: 一条广告
可以显示的按钮个数: 8
展示图片URL: http://xxx.1112222.jpg
更新banner区: 一条广告, 可显示按钮个数: 8, 展示区图片地址: http://xxx.1112222.jpg

这样的请求看似没有问题,实际上有个问题,当数据量小时,3个请求在一个线程里顺次执行,所有请求结束后,更新主界面UI。理想很完美,现实很残酷。实际中,这些接口对应的数据表都是非常庞大的,如果3个网络请求的响应各是5s,那么更新操作将在15s之后才能进行。或者,请求第一个接口时,服务器那边异常了,但是没有数据返回,后面两个接口是正常的,因为第一个请求没有结束,后面的操作会一直阻塞,这会影响用户体验的。也许有人会说,可以将三个接口作一个接口提供给移动端同学调用,这样移动端人员调用也方便。这种接口设计针对小数据量是没有问题的,但是数据量多的话,请求响应数据就会很多,移动端解析时就会很慢。

针对3接口响应不能顺次请求来刷新UI,那么有没有办法来解决呢,答案是肯定有的。

方法1:通过单个线程请求每1个接口,最后在更新UI时,判断3个接个请求有没有成功响应。为了达成这个目的,可以通过接口回调+标志flag的方式来完成,简言之,线程1请求A区接口,线程2请求B区接口,线程3请求C区接口,每个接口成功响应后设置一个标志flag,当更新UI时,判断flag,如果3个请求的flag均为true时,则更新UI。

这样做的方式有个缺点,极难维护,flag满天飞,后期增加其他接口,必定导致flag数量增多,增加后期维护难度。

方法2:java在1.5时就已经提供了一个类帮助解决这种问题——CountDownLatch。该类接受一个int的参数,是一个计数器表示可以通过的线程数,当调用avait表示当前线程处于等待状态,调用countDown递减计数器,当计数器减为0时,执行avait后面的逻辑。
示例代码如下

public class Demo {

    public static final long TIMEOUT = 5L;

    private String getBannerInfo() {
        try {
            System.out.println("开始获取banner区数据");
            TimeUnit.SECONDS.sleep(TIMEOUT);
            System.out.println("获取banner区数据请求结束");
            return "一条广告";
        } catch (InterruptedException e) {
            e.printStackTrace();
            return "数据异常";
        }
    }

    private int getButtonVisibleCount() {
        try {
            System.out.println("开始获取按钮区可显示个数");
            TimeUnit.SECONDS.sleep(TIMEOUT);
            System.out.println("获取按钮区可显示个数请求结束");
            return 8;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return -1;
        }
    }

    private String getShowAreaImgUrl() {
        try {
            System.out.println("开始获取展示区图片地址");
            TimeUnit.SECONDS.sleep(TIMEOUT);
            System.out.println("获取展示区图片地址请求结束");
            // 测试图片
            return "http://xxx.1112222.jpg";
        } catch (InterruptedException e) {
            e.printStackTrace();
            return "";
        }
    }

    private void updateUI(String bannerInfo, int buttonCount, String imgUrl) {
        System.out.println(String.format("更新banner区: %s, 可显示按钮个数: %d, 展示区图片地址: %s", bannerInfo, buttonCount, imgUrl));
    }

    String bannerInfo;
    int buttonCount;
    String imgURL;
    public static void main(String[] args) {
        final CountDownLatch countDownLatch = new CountDownLatch(3);
        final Demo demo = new Demo();
        System.out.println("开始获取服务器数据-------");

        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.bannerInfo = demo.getBannerInfo();
                countDownLatch.countDown();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.buttonCount = demo.getButtonVisibleCount();
                countDownLatch.countDown();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.imgURL = demo.getShowAreaImgUrl();
                countDownLatch.countDown();
            }
        }).start();
        try {
            countDownLatch.await();
            demo.updateUI(demo.bannerInfo, demo.buttonCount, demo.imgURL);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

//        Executors.newSingleThreadExecutor().execute(new Runnable() {
//            @Override
//            public void run() {
//                String bannerInfo = demo.getBannerInfo();
//                int buttonCount = demo.getButtonVisibleCount();
//                String imgURL = demo.getShowAreaImgUrl();
//                System.out.println("banner区信息: " + bannerInfo);
//                System.out.println("可以显示的按钮个数: " + buttonCount);
//                System.out.println("展示图片URL: " + imgURL);
//                demo.updateUI(bannerInfo, buttonCount, imgURL);
//            }
//        });
    }
}

运行后结果如下:

开始获取服务器数据——-
开始获取banner区数据
开始获取按钮区可显示个数
开始获取展示区图片地址
获取按钮区可显示个数请求结束
获取banner区数据请求结束
获取展示区图片地址请求结束
更新banner区: 一条广告, 可显示按钮个数: 8, 展示区图片地址: http://xxx.1112222.jpg

『获取按钮区可显示个数请求结束
获取banner区数据请求结束
获取展示区图片地址请求结束』这三条日志会因不同的机器,不同的执行次数顺序会不一样

这样就能保证了三个线程分别调用接口后再更新UI。这样设计的好处是,未来新增其他的接口时,可以很方便的修改计数器即可,不需要维护N个flag。

参考资料:
Java CountDownLatch应用
Java 并发专题 :闭锁 CountDownLatch 之一家人一起吃个饭
Java并发编程:CountDownLatch、CyclicBarrier和Semaphore

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值