業務需求
有個業務需求是這樣的。需要上傳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;
}