併發任務執行的幾種方法 (同時上傳多張圖片請求)

業務需求

有個業務需求是這樣的。需要上傳1~5張圖片,使用一個專門的上傳圖片的網址。使用這個網址上傳一張圖片後會返回一個該圖片的url。多張圖片上傳後可以獲取多個url,再把這些圖片url加上一些其他參數,發起一個業務請求。

這應該算一個很常見的產品需求。一開始我想着怎麼沒有可以上傳多個圖片的後臺接口提供。後面想一次性上傳多個,弱網環境下,或者總上傳文件體積太大的情況下,一個文件上傳失敗其他文件也都失敗了。所以一次性只能上傳一個也還算合理。




照片實體類
public class UploadImageItem {

    @SerializedName("url_path")
    public String urlPath;//網絡圖片路徑

    @SerializedName("timestamp")
    public long timestamp;

    @SerializedName("location")
    public String location;

    public transient String filePath;//本地圖片路徑

    public UploadImageItem() {
    }

    public UploadImageItem(String filePath, long timestamp, String location) {
        this.timestamp = timestamp;
        this.location = location;
        this.filePath = filePath;
    }

    @Override
    public String toString() {
        return "UploadImageItem{" +
                "urlPath='" + urlPath + '\'' +
                ", timestamp=" + timestamp +
                ", location='" + location + '\'' +
                ", filePath='" + filePath + '\'' +
                '}';
    }
}



方法1.使用線程池、Runnable、final變量當計數器

	public static final int CONCURRENCY_TASK_NUMBER = 5;//併發任務數量
	
	private ExecutorService mExecutorService = Executors.newCachedThreadPool();
	
    /**
     * 使用線程池、Runnable、final變量當計數器
     */
    private void button1(List<UploadImageItem> uploadImageItemList) {
        final int[] finishCount = {0};
        for (UploadImageItem uploadImageItem : uploadImageItemList) {
            mExecutorService.submit(new IoRunnableV1(uploadImageItem, new Callback() {
                @Override
                public void onCallback() {
                    finishCount[0]++;
                    if (finishCount[0] == uploadImageItemList.size()) {
                        //任務全部完成了
                        print(uploadImageItemList);
                    }
                }
            }));
        }
    }
    
    /**
     * 全部完成,打印結果
     */
    public static void print(List<UploadImageItem> uploadImageItemList) {
        Log.e(TAG, "ConcurrentTaskActivity.java - print() ----- 任務全部完成了:" + Thread.currentThread());
        for (UploadImageItem uploadImageItem : uploadImageItemList) {
            Log.e(TAG, "ConcurrentTaskActivity.java - print() ----- 打印實體:" + uploadImageItem);
        }
    }

    private static class IoRunnableV1 implements Runnable {

        public UploadImageItem mUploadImageItem;
        public Callback mCallback;

        public IoRunnableV1(@NonNull UploadImageItem uploadImageItem, @NonNull Callback callback) {
            mUploadImageItem = uploadImageItem;
            mCallback = callback;
        }

        @Override
        public void run() {
            SystemClock.sleep(new Random().nextInt(1000) + 1000);
            mUploadImageItem.urlPath = "url path from " + mUploadImageItem.filePath;
            Log.e(TAG, "IoRunnableV1.java - run() ----- finish! item.filePath:" + mUploadImageItem.filePath);
            mCallback.onCallback();
        }
    }

	interface Callback {
        void onCallback();
    }



方法2.使用線程池、Runnable、CountDownLatch當計數器

    private void button2(List<UploadImageItem> uploadImageItemList) {
        mExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                CountDownLatch countDownLatch = new CountDownLatch(CONCURRENCY_TASK_NUMBER);

                for (UploadImageItem uploadImageItem : uploadImageItemList) {
                    mExecutorService.submit(new IoRunnableV2(uploadImageItem, countDownLatch));
                }

                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //任務全部完成了
                print(uploadImageItemList);
            }
        });
    }

    private static class IoRunnableV2 implements Runnable {

        public UploadImageItem mUploadImageItem;
        public CountDownLatch mCountDownLatch;

        public IoRunnableV2(@NonNull UploadImageItem uploadImageItem, @NonNull CountDownLatch countDownLatch) {
            mUploadImageItem = uploadImageItem;
            mCountDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            SystemClock.sleep(new Random().nextInt(1000) + 1000);
            mUploadImageItem.urlPath = "url path from " + mUploadImageItem.filePath;
            Log.e(TAG, "IoRunnableV2.java - run() ----- finish! item.filePath:" + mUploadImageItem.filePath);
            mCountDownLatch.countDown();
        }
    }



方法3.使用線程池、Callable、使用Future同步獲取結果

    private void button3(List<UploadImageItem> uploadImageItemList) {
        mExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                List<Future<UploadImageItem>> futureList = new ArrayList<>();

                for (UploadImageItem uploadImageItem : uploadImageItemList) {
                    Future<UploadImageItem> future = mExecutorService.submit(new IoCallable(uploadImageItem));
                    futureList.add(future);
                }

                try {
                    for (Future<UploadImageItem> future : futureList) {
                        future.get();
                    }
                } catch (ExecutionException | InterruptedException e) {
                    e.printStackTrace();
                }

                print(uploadImageItemList);
            }
        });
    }

    private static class IoCallable implements Callable<UploadImageItem> {

        public UploadImageItem mUploadImageItem;

        public IoCallable(UploadImageItem uploadImageItem) {
            this.mUploadImageItem = uploadImageItem;
        }

        @Override
        public UploadImageItem call() {
            SystemClock.sleep(new Random().nextInt(1000) + 1000);//模擬網絡請求
            mUploadImageItem.urlPath = "url path from " + mUploadImageItem.filePath;
            Log.e(TAG, "IoCallable.java - run() ----- finish! item.filePath::" + mUploadImageItem.filePath);
            return mUploadImageItem;
        }
    }



方法4.使用Rxjava 的 zip 操作符

如果網絡請求是使用了 Retrofit + Rxjava 那一套,那使用 Rxjava 的 zip 操作符就顯得尤其方便了。

zip的基礎寫法如下。有 N+1 個參數。
N是併發任務數量。這裏併發任務都是相同的話還可以抽取成一個 uploadImageFile 方法以便複用。
1是N個併發執行完後,需要做一些處理操作的函數。

下面5個併發任務 observable0、observable1、observable2、observable3、observable4
有1個處理函數 new Function5<UploadImageItem, UploadImageItem, UploadImageItem, UploadImageItem, UploadImageItem, List>() {…}

    private void button4(List<UploadImageItem> uploadImageItemList) {
        Observable<UploadImageItem> observable0;
        Observable<UploadImageItem> observable1;
        Observable<UploadImageItem> observable2;
        Observable<UploadImageItem> observable3;
        Observable<UploadImageItem> observable4;

        observable0 = uploadImageFile(uploadImageItemList.get(0));
        observable1 = uploadImageFile(uploadImageItemList.get(1));
        observable2 = uploadImageFile(uploadImageItemList.get(2));
        observable3 = uploadImageFile(uploadImageItemList.get(3));
        observable4 = uploadImageFile(uploadImageItemList.get(4));

        Observable.zip(observable0, observable1, observable2, observable3, observable4,
                new Function5<UploadImageItem, UploadImageItem, UploadImageItem, UploadImageItem, UploadImageItem, List<UploadImageItem>>() {
                    @Override
                    public List<UploadImageItem> apply(UploadImageItem uploadImageItem, UploadImageItem uploadImageItem2, UploadImageItem uploadImageItem3, UploadImageItem uploadImageItem4, UploadImageItem uploadImageItem5) throws Exception {
                        return uploadImageItemList;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<List<UploadImageItem>>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(List<UploadImageItem> uploadImageItems) {
                        for (UploadImageItem uploadImageItem : uploadImageItems) {
                            Log.e(TAG, "MultiTaskActivity.java - run() ----- 打印實體:" + uploadImageItem);
                        }
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    public Observable<UploadImageItem> uploadImageFile(UploadImageItem item) {
        return Observable.just(item)//這裏應該寫成網絡請求
                .subscribeOn(Schedulers.io())
                .map(response -> {
                    SystemClock.sleep(new Random().nextInt(1000) + 1000);
                    item.urlPath = "url path from " + item.filePath;
                    Log.e(TAG, "MultiTaskActivity.java - uploadImageFile() finish! item.filePath:" + item.filePath);
                    return item;
                });
    }

以上是基礎寫法,但是我們圖片數量是1~5張的。查看 zip 的重載方法是這樣的。有從2個任務到9個任務的重載方法。
在這裏插入圖片描述
對於可變的任務數量,剛開始我是這麼寫的。實在有點不忍直視。
在這裏插入圖片描述
後面改了一版。主要思路是使用假任務補足任務數量到5個,然後實際執行時候對那些假任務跳過不執行就可以。

	public final UploadImageItem EMPTY_ITEM = new UploadImageItem();

    /**
     * 任務可變的,正確使用zip操作符的方法
     */
    private void button6(List<UploadImageItem> uploadImageItemList) {

        Observable<UploadImageItem> observable0;
        Observable<UploadImageItem> observable1;
        Observable<UploadImageItem> observable2;
        Observable<UploadImageItem> observable3;
        Observable<UploadImageItem> observable4;

        List<UploadImageItem> originUploadImageList = new ArrayList<>(uploadImageItemList);
        //添加null,補全到最多圖片數量
        int validCount = originUploadImageList.size();//原始數量值
        for (int i = 0; i < CONCURRENCY_TASK_NUMBER - validCount; i++) {
            //僞裝操作1:添加值爲null的item。
            originUploadImageList.add(null);
        }

        observable0 = uploadImageFileV2(originUploadImageList.get(0));
        observable1 = uploadImageFileV2(originUploadImageList.get(1));
        observable2 = uploadImageFileV2(originUploadImageList.get(2));
        observable3 = uploadImageFileV2(originUploadImageList.get(3));
        observable4 = uploadImageFileV2(originUploadImageList.get(4));

        Observable<List<UploadImageItem>> observableUploadResult;

        //使用zip操作符併發上傳圖片。
        Observable
                .zip(observable0, observable1, observable2, observable3, observable4,
                        new Function5<UploadImageItem,
                                UploadImageItem,
                                UploadImageItem,
                                UploadImageItem,
                                UploadImageItem,
                                List<UploadImageItem>>() {
                            @Override
                            public List<UploadImageItem> apply(UploadImageItem uploadImageItem0,
                                                               UploadImageItem uploadImageItem1,
                                                               UploadImageItem uploadImageItem2,
                                                               UploadImageItem uploadImageItem3,
                                                               UploadImageItem uploadImageItem4) {

                                List<UploadImageItem> resultUploadImageItemList = new ArrayList<>();
                                resultUploadImageItemList.add(uploadImageItem0);
                                resultUploadImageItemList.add(uploadImageItem1);
                                resultUploadImageItemList.add(uploadImageItem2);
                                resultUploadImageItemList.add(uploadImageItem3);
                                resultUploadImageItemList.add(uploadImageItem4);
                                //僞裝操作3:識別出EMPTY_ITEM,過濾掉。
                                while (resultUploadImageItemList.contains(EMPTY_ITEM)) {
                                    resultUploadImageItemList.remove(EMPTY_ITEM);
                                }

                                return resultUploadImageItemList;
                            }
                        })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<List<UploadImageItem>>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(List<UploadImageItem> uploadImageItems) {
                        for (UploadImageItem uploadImageItem : uploadImageItems) {
                            Log.e(TAG, "MultiTaskActivity.java - run() ----- 打印實體:" + uploadImageItem);
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
						// 如果任意一張圖片上傳出錯,會走到這個方法裏
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    public Observable<UploadImageItem> uploadImageFileV2(UploadImageItem item) {
        //僞裝操作2:如果是null的item,不需要執行網絡請求,而是發送一個EMPTY_ITEM。
        //因爲 Observable.just(null) 會報錯,所以使用 EMPTY_ITEM 這個代表假任務
        if (item == null) {
            return Observable.just(EMPTY_ITEM);
        }

        //圖片已經上傳過,已擁有網絡路徑
        if (item.urlPath != null) {
            return Observable.just(item);
        }

        return Observable.just(item)//這裏應該寫成網絡請求
                .subscribeOn(Schedulers.io())
                .map(response -> {
                    SystemClock.sleep(new Random().nextInt(1000) + 1000);
                    item.urlPath = "url path from " + item.filePath;
                    Log.e(TAG, "MultiTaskActivity.java - uploadImageFile() finish! item.filePath:" + item.filePath);
                    return item;
                });
    }

使用這種方法併發上傳5張圖片之後,完全可以使用 .flatMap 接着發起一個業務請求。
這麼寫就真的非常的流式編程了。代碼如下

/**
     * 任務可變的,正確使用zip操作符的方法,接上業務數據請求
     */
    private void button7(List<UploadImageItem> uploadImageItemList) {

        Observable<UploadImageItem> observable0;
        Observable<UploadImageItem> observable1;
        Observable<UploadImageItem> observable2;
        Observable<UploadImageItem> observable3;
        Observable<UploadImageItem> observable4;

        List<UploadImageItem> originUploadImageList = new ArrayList<>(uploadImageItemList);
        //添加null,補全到最多圖片數量
        int validCount = originUploadImageList.size();//原始數量值
        for (int i = 0; i < CONCURRENCY_TASK_NUMBER - validCount; i++) {
            //僞裝操作1:添加值爲null的item。
            originUploadImageList.add(null);
        }

        observable0 = uploadImageFileV2(originUploadImageList.get(0));
        observable1 = uploadImageFileV2(originUploadImageList.get(1));
        observable2 = uploadImageFileV2(originUploadImageList.get(2));
        observable3 = uploadImageFileV2(originUploadImageList.get(3));
        observable4 = uploadImageFileV2(originUploadImageList.get(4));

        Observable<List<UploadImageItem>> observableUploadResult;

        //使用zip操作符併發上傳圖片。
        Observable
                .zip(observable0, observable1, observable2, observable3, observable4,
                        new Function5<UploadImageItem,
                                UploadImageItem,
                                UploadImageItem,
                                UploadImageItem,
                                UploadImageItem,
                                List<UploadImageItem>>() {
                            @Override
                            public List<UploadImageItem> apply(UploadImageItem uploadImageItem0,
                                                               UploadImageItem uploadImageItem1,
                                                               UploadImageItem uploadImageItem2,
                                                               UploadImageItem uploadImageItem3,
                                                               UploadImageItem uploadImageItem4) {

                                List<UploadImageItem> resultUploadImageItemList = new ArrayList<>();
                                resultUploadImageItemList.add(uploadImageItem0);
                                resultUploadImageItemList.add(uploadImageItem1);
                                resultUploadImageItemList.add(uploadImageItem2);
                                resultUploadImageItemList.add(uploadImageItem3);
                                resultUploadImageItemList.add(uploadImageItem4);
                                //僞裝操作3:識別出EMPTY_ITEM,過濾掉。
                                while (resultUploadImageItemList.contains(EMPTY_ITEM)) {
                                    resultUploadImageItemList.remove(EMPTY_ITEM);
                                }

                                return resultUploadImageItemList;
                            }
                        })
                .flatMap(new Function<List<UploadImageItem>, Observable<HttpResult>>() {
                    @Override
                    public Observable<HttpResult> apply(List<UploadImageItem> uploadImageItems) throws Exception {
                        RequestBody requestBody = new RequestBody();
                        requestBody.uploadImageItemList = uploadImageItemList;
                        //上傳完5張圖片後,使用flatMap接着發一個業務請求。
                        return new ApiService().submitInfo(requestBody);
                    }
                })
                .subscribeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<HttpResult>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(HttpResult httpResult) {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    //假裝這個一個Retrofit interface
    public class ApiService {
        @POST("users/new")
        Observable<HttpResult> submitInfo(@Body RequestBody requestBody) {return null;}
    }

    public static class RequestBody {
        List<UploadImageItem> uploadImageItemList;
        String otherParam1;
        int otherParam2;
    }

    public static class HttpResult {
        int ret;
        String msg;
        String data;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章