簡介
RxJava
RxJava是一個在Java VM上使用可觀測的序列來組成非同步的、基於事件的程式的庫。RxJava本質上是一個實現非同步操作的庫。
專案地址: https://github.com/ReactiveX/RxJava
XTask
XTask是一個擴充性極強的Android任務執行框架。通過它,你可以自由定義和組合任務來實現你想要的功能,尤其適用於處理複雜的業務流程,可靈活新增前置任務或者調整執行順序。
專案的地址: https://github.com/xuexiangjys/XTask
背景
XTask是我基於RxJava的設計思想,並結合實際專案中使用的經驗所創造出來的一個開源專案,其目的就是要代替RxJava在Android中的部分使用場景,提升開發的體驗和可維護性。
相信使用過RxJava的人都知道RxJava有很多硬傷,下面我哦簡單列舉幾個:
- RxJava最初並不是最先在Android中使用的,所以它一開始就設計的相當的複雜且笨重,一個庫常常能達到3M左右,相對於移動端而已,這還是非常佔應用體積的。
- 遠遠超過百種的操作符也常常讓使用者摸不著頭腦,稀裡糊塗的使用很容易帶來一些致命性的問題,例如記憶體洩漏等。
- 由於RxJava是一個基於事件的程式庫,缺少一些關鍵執行任務的日誌資訊,這就導致出了問題後會很難排查出來。
而XTask就是為了能夠解決上述問題而被我開源出來的。
使用對比
首先,RxJava作為一個優秀的開源框架這點是毋庸置疑的,XTask並不是用來代替RxJava的,我沒有這種能力,同樣google也沒有。
但是在某些小且常用的場景下,我們是完全可以替換掉RxJava的使用的。例如如下兩種場景:
- 複雜序列任務處理
- 複雜併發任務處理
下面我就通過兩個小例子來給大家呈現它們的不同。
複雜序列任務
相信我們在平時的開發過程中一定會遇到很多複雜的業務流程,而這些流程很多都是一環套著一環,需要一步一步走下去才行,中間有任何錯誤都將停止執行。
下面我就以 [高仿網紅產品] 的案例流程為例,簡單講解如何通過RxJava
和XTask
去實現這一流程。
案例分析
高仿網紅產品的流程
1.獲取產品資訊 -> 2.查詢可生產的工廠 -> 3.聯絡工廠生產產品 -> 4.送去市場部門評估售價 -> 5.產品上市
實體類設計
這裡主要涉及3個實體類: Product、ProductInfo和ProductFactory。
/**
* 產品
*/
public class Product {
/**
* 產品資訊
*/
private ProductInfo info;
/**
* 產品生產地址
*/
private String address;
/**
* 產品價格
*/
private String price;
/**
* 產品釋出時間
*/
private String publicTime;
}
/**
* 產品資訊
*/
public class ProductInfo {
/**
* 編號
*/
private String id;
/**
* 品牌
*/
private String brand;
/**
* 質量
*/
private String quality;
}
/**
* 產品工廠
*/
public class ProductFactory {
/**
* 工廠id
*/
private String id;
/**
* 工廠地址
*/
private String address;
}
案例實現
業務流程處理
上述共有5個業務流程,我們將其簡化分為以下4個處理器進行處理。
- 1.獲取產品資訊: GetProductInfoProcessor (productId -> ProductInfo)
- 2.查詢相關的工廠: SearchFactoryProcessor (ProductInfo -> ProductFactory)
- 3.評估產品,給出價格: GivePriceProcessor (Product -> Product)
- 4.產品釋出: PublicProductProcessor (Product -> Product)
業務流程串聯
- 普通寫法
普通寫法我們直接使用介面回撥的方式, 一層層執行。
AppExecutors.get().singleIO().execute(() -> {
// 1.獲取產品資訊
new GetProductInfoProcessor(logger, productId).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<ProductInfo>() {
@Override
public void onSuccess(final ProductInfo productInfo) {
// 2.查詢可生產的工廠
new SearchFactoryProcessor(logger, productInfo).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<ProductFactory>() {
@Override
public void onSuccess(final ProductFactory factory) {
// 3.聯絡工廠生產產品
log("開始生產產品...");
Product product = factory.produce(productInfo);
// 4.送去市場部門評估售價
new GivePriceProcessor(logger, product).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<Product>() {
@Override
public void onSuccess(Product product) {
// 5.產品上市
PublicProductProcessor publicProductProcessor = new PublicProductProcessor(logger, product);
publicProductProcessor.setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<Product>() {
@Override
public void onSuccess(Product product) {
log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
log("仿冒生產網紅產品完成, " + product);
}
}).process();
}
}).process();
}
}).process();
}
}).process();
});
- RxJava寫法
RxJava中執行序列任務,一般使用map
或者flatMap
,這裡由於是一對一,所以使用map
執行即可。
disposable = Observable.just(productId)
// 1.獲取產品資訊
.map(id -> new GetProductInfoProcessor(logger, id).process())
// 2.查詢可生產的工廠
.map(productInfo -> new Pair<>(new SearchFactoryProcessor(logger, productInfo).process(), productInfo))
.map(productPair -> {
// 3.聯絡工廠生產產品
log("開始生產產品...");
Product product = productPair.first.produce(productPair.second);
// 4.送去市場部門評估售價
return new GivePriceProcessor(logger, product).process();
})
// 5.產品上市
.map(product -> new PublicProductProcessor(logger, product).process())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(product -> {
log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
log("仿冒生產網紅產品完成, " + product);
});
- XTask寫法
與普通寫法和RxJava寫法不同的是,XTask是把所有的業務處理器都封裝在了一個一個的Task中,然後按任務的執行順序依次新增對應的Task即可完成。
XTask.getTaskChain()
.setTaskParam(TaskParam.get(ProductTaskConstants.KEY_PRODUCT_ID, productId))
// 1.獲取產品資訊
.addTask(new GetProductInfoTask(logger))
// 2.查詢可生產的工廠, 3.聯絡工廠生產產品
.addTask(new SearchFactoryTask(logger))
// 4.送去市場部門評估售價
.addTask(new GivePriceTask(logger))
// 5.產品上市
.addTask(new PublicProductTask(logger))
.setTaskChainCallback(new TaskChainCallbackAdapter() {
@Override
public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) {
log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
Product product = result.getDataStore().getObject(ProductTaskConstants.KEY_PRODUCT, Product.class);
log("仿冒生產網紅產品完成, " + product);
}
}).start();
案例執行結果
- 程式執行結果
- XTask執行日誌一覽
複雜並行任務
除了上面我們討論到的常見序列任務,我們在平時的開發過程中也會遇到一些複雜的並行流程。這些流程往往是單獨可執行的,雖說前後關聯不大,但是又是同時為了某個目標去執行的流程。
下面我就以常見的 [展示商品詳細資訊] 的案例流程為例,簡單講解如何通過RxJava
和XTask
去實現這一流程。
案例分析
展示商品詳細資訊的流程
- 1.根據商品的唯一號ID獲取商品簡要資訊
2.獲取商品的詳細資訊:
- 2.1 獲取商品的生產資訊
- 2.2 獲取商品的價格資訊
- 2.3 獲取商品的促銷資訊
- 2.4 獲取商品的富文字資訊
- 3.進行商品資訊的展示
其中步驟2中的4個子步驟是可以同時進行,互不影響的併發流程。
實體類設計
這裡主要涉及6個實體類: BriefInfo、Product、FactoryInfo、PriceInfo、PromotionInfo 和 RichInfo。
/**
* 產品簡要資訊
*/
public class BriefInfo {
private String id;
protected String name;
private String factoryId;
private String priceId;
private String promotionId;
private String richId;
}
/**
* 產品
*/
public class Product extends BriefInfo {
/**
* 生產資訊
*/
private FactoryInfo factory;
/**
* 價格資訊
*/
private PriceInfo price;
/**
* 促銷資訊
*/
private PromotionInfo promotion;
/**
* 富文字資訊
*/
private RichInfo rich;
}
/**
* 工廠生產資訊
*/
public class FactoryInfo {
private String id;
/**
* 生產地址
*/
private String address;
/**
* 生產日期
*/
private String productDate;
/**
* 過期日期
*/
private String expirationDate;
}
/**
* 價格資訊
*/
public class PriceInfo {
private String id;
/**
* 出廠價
*/
private float factoryPrice;
/**
* 批發價
*/
private float wholesalePrice;
/**
* 零售價
*/
private float retailPrice;
}
/**
* 產品促銷資訊
*/
public class PromotionInfo {
private String id;
/**
* 促銷型別
*/
private int type;
/**
* 促銷內容
*/
private String content;
/**
* 生效日期
*/
private String effectiveDate;
/**
* 失效日期
*/
private String expirationDate;
}
/**
* 富文字資訊
*/
public class RichInfo {
private String id;
/**
* 描述資訊
*/
private String description;
/**
* 圖片連結
*/
private String imgUrl;
/**
* 視訊連結
*/
private String videoUrl;
}
案例實現
業務流程處理
上述共有3個大業務流程,4個子業務流程,我們將其簡化分為以下5個處理器進行處理。
- 1.獲取商品簡要資訊: GetBriefInfoProcessor (productId -> BriefInfo)
- 2.獲取商品的生產資訊: GetFactoryInfoProcessor (factoryId -> FactoryInfo)
- 3.獲取商品的價格資訊: GetPriceInfoProcessor (priceId -> PriceInfo)
- 4.獲取商品的促銷資訊: GetPromotionInfoProcessor (promotionId -> PromotionInfo)
- 5.獲取商品的富文字資訊: GetRichInfoProcessor (richId -> RichInfo)
業務流程串聯
- 普通寫法
普通寫法我們需要通過介面回撥+同步鎖的方式, 實現任務的併發和協同。
AppExecutors.get().singleIO().execute(() -> {
new GetBriefInfoProcessor(logger, productId).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<BriefInfo>() {
@Override
public void onSuccess(BriefInfo briefInfo) {
final Product product = new Product(briefInfo);
CountDownLatch latch = new CountDownLatch(4);
// 2.1 獲取商品的生產資訊
AppExecutors.get().networkIO().execute(() -> {
new GetFactoryInfoProcessor(logger, product.getFactoryId()).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<FactoryInfo>() {
@Override
public void onSuccess(FactoryInfo result) {
product.setFactory(result);
latch.countDown();
}
}).process();
});
// 2.2 獲取商品的價格資訊
AppExecutors.get().networkIO().execute(() -> {
new GetPriceInfoProcessor(logger, product.getPriceId()).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<PriceInfo>() {
@Override
public void onSuccess(PriceInfo result) {
product.setPrice(result);
latch.countDown();
}
}).process();
});
// 2.3 獲取商品的促銷資訊
AppExecutors.get().networkIO().execute(() -> {
new GetPromotionInfoProcessor(logger, product.getPromotionId()).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<PromotionInfo>() {
@Override
public void onSuccess(PromotionInfo result) {
product.setPromotion(result);
latch.countDown();
}
}).process();
});
// 2.4 獲取商品的富文字資訊
AppExecutors.get().networkIO().execute(() -> {
new GetRichInfoProcessor(logger, product.getRichId()).setProcessorCallback(new AbstractProcessor.ProcessorCallbackAdapter<RichInfo>() {
@Override
public void onSuccess(RichInfo result) {
product.setRich(result);
latch.countDown();
}
}).process();
});
try {
latch.await();
log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
log("查詢商品資訊完成, " + product);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).process();
});
- RxJava寫法
RxJava中執行並行任務,一般使用merge
或者zip
,這裡由於需要協同,所以使用zip
對任務流進行合併。
disposable = Observable.just(productId)
// 1.獲取商品簡要資訊
.map(id -> new GetBriefInfoProcessor(logger, id).process())
.map(Product::new)
.flatMap(product ->
Observable.zip(
// 2.1 獲取商品的生產資訊
Observable.fromCallable(() -> new GetFactoryInfoProcessor(logger, product.getFactoryId()).process()).subscribeOn(Schedulers.io()),
// 2.2 獲取商品的價格資訊
Observable.fromCallable(() -> new GetPriceInfoProcessor(logger, product.getPriceId()).process()).subscribeOn(Schedulers.io()),
// 2.3 獲取商品的促銷資訊
Observable.fromCallable(() -> new GetPromotionInfoProcessor(logger, product.getPromotionId()).process()).subscribeOn(Schedulers.io()),
// 2.4 獲取商品的富文字資訊
Observable.fromCallable(() -> new GetRichInfoProcessor(logger, product.getRichId()).process()).subscribeOn(Schedulers.io()), (factoryInfo, priceInfo, promotionInfo, richInfo) -> product.setFactory(factoryInfo)
.setPrice(priceInfo)
.setPromotion(promotionInfo)
.setRich(richInfo)
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(product -> {
log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
log("查詢商品資訊完成, " + product);
});
- XTask寫法
XTask是把所有的業務處理器都封裝在了一個一個的Task中,然後並行的任務需要通過一個ConcurrentGroupTask(同步組任務)進行包裹,其他按正常執行順序新增Task即可。
XTask.getTaskChain()
.setTaskParam(TaskParam.get(ProductTaskConstants.KEY_PRODUCT_ID, productId))
// 1.獲取商品簡要資訊
.addTask(new GetBriefInfoTask(logger))
.addTask(XTask.getConcurrentGroupTask(ThreadType.SYNC)
// 2.1 獲取商品的生產資訊
.addTask(new GetFactoryInfoTask(logger))
// 2.2 獲取商品的價格資訊
.addTask(new GetPriceInfoTask(logger))
// 2.3 獲取商品的促銷資訊
.addTask(new GetPromotionInfoTask(logger))
// 2.4 獲取商品的富文字資訊
.addTask(new GetRichInfoTask(logger)))
.setTaskChainCallback(new TaskChainCallbackAdapter() {
@Override
public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) {
log("總共耗時:" + (System.currentTimeMillis() - startTime) + "ms");
Product product = result.getDataStore().getObject(ProductTaskConstants.KEY_PRODUCT, Product.class);
log("查詢商品資訊完成, " + product);
}
}).start();
案例執行結果
- 程式執行結果
- XTask執行日誌一覽
使用對比總結
從上面的使用對比來看,我們可以簡單歸納總結以下幾點:
程式設計方式
1.RxJava遵循的是函式響應式程式設計的原則,處理過程都是基於資料流的處理。這樣的好處就是,我們可以最直觀有效的感受到資料的變化過程,當然缺點就是太過於細化和具體,不符合物件導向的設計模式原則,增加了日後的程式碼維護成本。當然如果資料的結構相對穩定的話,這樣的程式設計方式還可以接受,但如果資料或者業務頻繁發生變動的話,這樣的程式設計方式簡直就是地獄。
2.XTask遵循的是物件導向的程式設計原則,每個處理過程都對應了一個具體或者抽象的Task。這樣的好處就是,減少了業務和資料結構之間的耦合,同時也減少了各個業務之間的耦合。這樣即使你的資料結構或者業務流程出現大的變動,功能實現的主體也不會產生大的改動,更多的只是每個子業務Task內部的改動和調整,真正實現了高複用低耦合。
總結: 兩種不同的程式設計方式,遵循兩種不同的程式設計原則,無法進行對比。
上手難度
如果你是一名RxJava的開發老鳥的話,這樣就沒什麼可比性了,這裡我只是從初學者的角度來說。
1.RxJava擁有龐大複雜的操作符,上百種操作符一定會讓初學者摸不著頭腦,如果在不熟悉的情況下強行使用,很容易導致誤用而產生很多意想不到的問題(比如記憶體洩漏或者OOM等)。
2.XTask作為專為Android設計的任務執行框架,功能相對單一。沒有複雜的操作符,有的只是“任務鏈、任務、組任務、任務引數和執行結果”這五個組成要素,使用起來相對簡單容易上手。
總結: 整體比較下來,XTask要優於RxJava。
開發效率
1.RxJava的開發效率主要取決於開發者對RxJava操作符使用的熟練程度。越是能夠熟練使用操作符,開發效率就越高,出問題的概率也越小。
2.XTask相對而言就平滑了許多,開發效率和使用的熟練程度關係不大(主要還是上手難度不高)。但是由於每個業務子步驟都需要寫一個Task類,對於那些使用RxJava比較熟練的人而言,效率是明顯會低一些。
總結: 整體比較下來,從長期而言,RxJava要優於XTask。
可維護性
1.RxJava遵循的是函式響應式程式設計的原則,本質上還是程式導向式的程式設計。所有的業務流程都和資料有著比較強的耦合,當資料結構或者業務流程發生變動的時候,必然會影響到主幹程式碼的變動。而且對於初入專案的開發人員接手專案的時候,能看到的往往是區域性業務資料流的變動,無法從全域性的視角去理解專案主體業務,很容易產生區域性修改影響全域性的結果。
2.XTask遵循的是物件導向的程式設計原則,設計之初就嚴格遵循物件導向的設計模式原則。充分減少業務與業務、業務與資料流之間的耦合,這樣即使你的資料結構或者業務流程出現重大的變化,主幹程式碼也不會有很大的變動。而且XTask擁有較強的日誌記錄系統,能夠非常清晰的記錄你當前任務鏈的執行過程和所線上程的資訊(自動的),當任務執行出現問題的時候,便能很快地定位出問題產生的位置。而對於初入專案的開發人員來說,也能快速從任務執行過程的日誌中去理解專案的主體業務。待主體業務流程有了清楚的認知後再去仔細看子業務,這樣才能全方位理解專案的業務,也更利於專案的維護。
總結: 整體比較下來,XTask完勝RxJava。
效能
在效能上,XTask為了實現業務與資料之間的隔離,設計了共享資料的結構,相比較RxJava而言,多了資料拷貝以及資料儲存的過程,所以無論是在時間還是空間上而言,RxJava都是較優於XTask的。
最後
綜合以上的論述,XTask和RxJava各有各的優勢。正如我文章開頭所說: XTask並不是用來代替RxJava的。XTask只是作為RxJava在Android任務執行流程上的一種補充,喜歡的朋友可以關注XTask的專案主頁: https://github.com/xuexiangjys/XTask。
我是xuexiangjys,一枚熱愛學習,愛好程式設計,致力於Android架構研究以及開源專案經驗分享的技術up主。獲取更多資訊,歡迎微信搜尋公眾號:【我的Android開源之旅】