1. 程式人生 > >如果優雅地處理多個請求並在請求結束後統一處理

如果優雅地處理多個請求並在請求結束後統一處理

如何優雅地處理多個請求並在請求結束後統一處理

前不久我接到一個需求,首頁更新的資料是從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。