RxJava 使用不当的一些情况

3,022 阅读14分钟
原文链接: blog.chengyunfeng.com

Reactive programming 是一种改变游戏规则的技术。如果您正确的使用它,则会改变您的编程方式。一年之前笔者(原文作者,下同)开始接触 RxJava 并尝试使用 RxJava 来处理 UI 事件(并且成为了 RxJavaFX 的管理者)。在使用 RxJava 一段时间后,笔者发现 RxJava 能干很多事。 并且改变了很多编程的方式和方法,从 并发到 IO 以及 业务逻辑和算法。

笔者开始到处使用 RxJava ,只要是可以用的地方就开始使用 RxJava,这样也可以更快的掌握 RxJava 的用法。

一年以后,笔者发现 RxJava 并不总是最佳的解决方案。虽然,现在笔者所写的每个应用都是 reactive 的,但是现在在有些地方可能会选择不使用 reactive 的方式来编程。需要注意的是,把任何东西转换为 Observable 是很容易的。 所以本篇文章主要介绍何时您提供的 API 应该返回非 Observable 的数据类型,如果客户端调用你的 API 的时候想使用 Observable ,则他们自己可以很容易的转换为 Observable。

第一种情况:简单的常量集合数据

这是最简单的一种不适合使用 Observable 的情况。例如有个如下的 enum 类型定义:

public enum EmployeeType {
    FULL_TIME,
    CONTRACTOR,
    INTERN
}

如果你想遍历这个枚举类型,通过下面的方式把它转换成 Observable 是不是适用所有情况呢?

Observable employeeTypes = Observable.from(Employee.values());

如果你已经在使用 Observable 的操作符来操作数据了,这个时候使用 Observable 版本的 EmployeeType 比较合适。但是通常情况下不是这样的。

简单的常量数据并不太适合转换为 Observable 。从 API 的角度来说, 使用传统的方式来返回这种数据类型是比较好的情况。如果调用 API 的人想使用 Observable 则可以很容易的完成转换。

第二种情况:开销比较大的、缓存的对象

假设有个用来执行正则表达式搜索的 ActionQualifier 类,由于编译正则表达式是非常耗时的,所以不停的创建新的 ActionQualifier 对象是很不明智的:

public final class ActionQualifier {
 
    private final Pattern codeRegexPattern;
    private final int actionNumber;
 
    ActionQualifier(String codeRegex, int actionNumber) {
        this.codeRegexPattern = Pattern.compile(codeRegex);
        this.actionNumber = actionNumber;
    }
 
    public boolean qualify(String code) {
        return codeRegexPattern.matcher(code).find();
    }
    public int getActionCode() {
        return actionNumber;
    }
}
 

如果你使用 RxJava-JDBC 并且在操作过程中使用了 ActionQualifier 对象,当有多个订阅者订阅到这个查询数据的 Observable 上的时候,由于每个订阅都会重新查询数据库,所以创建新的 ActionQualifier 是非常耗时的:

Observable actionQualifiers = db
    .select("SELECT CODE_REGEX, ACTION_NUMBER FROM ACTION_MAPPING")
    .get(rs -> new ActionQualifier(rs.getString("CODE_REGEX"), rs.getInt("ACTION_NUMBER")

为了避免每个订阅者订阅的时候都查询一次,你可以选择使用 cache() 来缓存数据。这样的话,actionQualifiers 就没法更新了并且可能长期持有,导致虚拟机无法回收该对象。

Observable actionQualifiers = db
    .select("SELECT CODE_REGEX, ACTION_NUMBER FROM ACTION_MAPPING")
    .get(rs -> new ActionQualifier(rs.getString("CODE_REGEX"), rs.getInt("ACTION_NUMBER")))
    .cache();

Dave Moten 提供了一个巧妙的解决方式,缓存设定过期时间,然后重新订阅到源 Observable。但是最终你可能会问,是否可以把 actionQualifiers 保存在一个 List 中,然后手工的去刷新。

List actionQualifiers = db
    .select("SELECT CODE_REGEX, ACTION_NUMBER FROM ACTION_MAPPING")
    .get(rs -> new ActionQualifier(rs.getString("CODE_REGEX"), rs.getInt("ACTION_NUMBER")))
    .toList().toBlocking().first();

当你需要使用 Observable 的时候,则可以很容易的把 List 转换为 Observable :

Observable.from(actionQualifiers).filter(aq -> aq.qualify("TXB.*"));

不管使用哪种方式,缓存大量的消耗资源的对象都是很不好处理的。并且使用 Observable 的 cache 还会导致内存占用,根据您的具体情况,你可以灵活选择使用哪种方式。

第三种情况:简单的查看和单步的操作(Simple “Lookups” and Single-Step Monads)

RxJava 的优势之一是可以很容易的组合很多操作函数。Take these, then filter that, map to this, and reduce to that.

Observable products = ...
 
Observable totalSoldUnits = products
    .filter(pd -> pd.getCategoryId() == 3)
    .map(pd -> pd.getSoldUnits())
    .reduce(0,(x,y) -> x + y)

如果只是简单的一步操作呢?

Observable category = Category.forId(263);

这种情况是不是滥用 Observable 呢? 直接返回数据是不是更好呢?

Category category = Category.forId(263);

如果返回的结果有多个 Category 、或者你不想处理返回数据为 null 的情况,则会使用 Observable 。但是在下面的示例中会看到,这种过度的使用 Observable 会导致更多的模板代码出现。

如果你非要这样用,则使用的时候,可以很容易的转换为 Observable:

Observable category = Observable.just(Category.forId(263))
    .filter(c -> c != null);
 

第四种情况:经常使用到的属性(Frequently Qualified Properties)

先解释下上面提到的情况,比如有下面一个 Product 类

public final class Product {
    private final int id;
    private final String description;
    private final int categoryId;
 
    public Product(int id, String description, int categoryId) {
        this.id = id;
        this.description = description;
        this.categoryId = categoryId;
    }
    public Observable getCategory() {
        return Category.forId(categoryId);
    }
}

上面的 getCategory() 返回的是 Observable。如果你经常使用这个函数,则用起来可能相当麻烦。假设每个 Category 中有个 getGroup() 函数返回一个整数,代表所在的分组。可以根据这个分组来过滤每个组的分类:

Observable products = ...
 
Observable productsInGroup5 =
    products.flatMap(p -> p.getCategory().filter(c -> c.getGroup() == 5).map(p -> c));

如此简单的一个需求,代码看起来居然这么复杂到使用了 FlatMap 。如果 getCategory() 返回的是 category 对象则用起来就相当简单了:

public Category getCategory() {
    return Category.forId(categoryId);
}
 
Observable productsInGroup5 =
    products.filter(p -> p.getCategory().getGroup() == 5);

所以针对这种经常使用到的函数或者属性,最好不要返回 Observable 的形式。

第五种情况:有状态的对象(Capturing State)

RxJava 中的数据通常是无状态的。这样可以方便并行处理多个操作。但是在实际的业务逻辑中,通常需要保留一些状态。比如打印价格时候,需要保留历史价格信息:


public final class PricePoint {
    private final int id;
    private final int productId;
    private final BigDecimal price;
    private final ImmutableList historicalPricePoints;
 
    public PricePoint(int id, int productId, BigDecimal price) {
        this.id = id;
        this.productId = productId;
        this.price = price;
        historicalPricePoints = HistoricalPricePoints.forProductId(productId);
    }
    public ImmutableList getHistoricalPricePoints() {
        return historicalPricePoints;
    }
}
 

然后通过 Observable 的方式来获取历史信息:

public final class PricePoint {
    private final int id;
    private final int productId;
    private final BigDecimal price;
 
    public PricePoint(int id, int productId, BigDecimal price) {
        this.id = id;
        this.productId = productId;
        this.price = price;
    }
    public Observable getHistoricalPricePoints() {
        return HistoricalPricePoints.forProductId(productId);
    }
}
 

但是如果这个操作是比较消耗资源的,则又回到了第二种情况。

针对这种有状态的数据,还是使用传统的查询方式比较好。

总结

使用了 RxJava,尝到了 Rxjava 的好处,您会到处使用它,但是 RxJava 是用来解决比较复杂或者非常复杂情况的,对于简单的情况还是简单处理吧。凡事要把握好度,过犹不及! 上面的一些情况,对于有经验的 RxJava 开发者可能很容易避免这种情况,对于初学者可能还处于 使用 RxJava 的蜜月期,看问题没这么透彻很容易陷入到过度使用 RxJava 的情况。

再次提醒,以上只是笔者自己的主观意见,如果有不同见解,欢迎一起讨论交流。

原文地址: http://tomstechnicalblog.blogspot.hk/2016/07/when-not-to-use-rxjava.html


Google Code Hosting 由于其宽松的使用方式赢得了很多开发者的欢迎, 很多开发者都把其当做了下载服务器, 最近Google修改了Google Code Hosting服务协议,从新定义了Google Code Hosting的使用范围, 只能用来托管开源项目,否则就是违反了其TOS(Terms of service)

Goodev大部分的项目都在这里(code.google.com/p/goodev)提供下载. 现在由于违反了新的TOS即将被Google删除,该项目的相关内容会逐步移植到本博客站点上.

code.google.com/p/goodev提供下载的内容将会通过金山快盘提供,金山快盘是金山公司出品的一款网络硬盘服务,支持多种设备(PC,Android,IPhone等), 需要先注册金山快盘账户 然后添加admin@goodev.org为联系人,然后发邮件到admin@goodev.org(标题请写为”goodev 金山快盘共享”)来获得下载共享文件的权限.

由于金山快盘不支持匿名共享(国内大环境所致,这个相信大家都知道),所以有点操作有点麻烦, 不过一旦注册了账户,以后Goodev由新的程序发布了, 您的金山快盘就可以直接下载新发布的文件了, 不用再去手工下载了, 这也是金山开盘自动同步的一个优势.

关于谷嘀工作室的内容请点击这里.

新添加115网盘下载链接, 这样不用注册也可以下载了. 详情点击这里.


PhoneGap 是一个HTML5移动平台开发框架,使用HTML5来开发App的同时您还可以使用PhoneGap来访问特定平台的特性,例如:通过PhoneGap你可以在HTML5中使用手机的相机来拍照;还可以通过联系人API来获取联系人信息;通过GPS API来访问手机的位置信息 等。这样通过PhoneGap您的App在所有智能移动平台上表现行为都一样,并且还可以和特定的平台API交互。方便App多平台的移植和发布。

PhoneGap支持多达6种主流智能移动平台:IPhone、Android、WebOS、Windows Phone、Bada、S60。

PhoneGap的使用很简单, 只要使用其提供的API来访问相关的功能就可以把获取的结果在HTML5(JS)中使用了。

下面就使用Android平台来简单做下介绍:

1. 下载PhoneGap: phonegap.googlecode.com/files/phone…

2. 使用Eclipse新建一个Android项目,如果您还不熟悉如何搭建Android开发环境,请点击这里

3. 在Android项目创建对话框中选择“从已有代码中创建项目”,把PhoneGap sdk中android目录下面的示例项目地址输入到 代码地址输入框中, 如下图:

查看图片

4. 点击 Finish 。然后在项目中 把 libs目录中的phonegap.X.X.X.jar 文件添加到项目build路径中. 然后就可以运行该示例程序了.

在0.9.6版本中 该示例项目分别演示了如何在JS中通过PhoneGap API来获取手机加速传感器、相机拍照、GPS 位置信息、拨打电话、控制手机发声和振动、读取联系人信息以及检查手机网络信息功能。

更多平台开发文档:www.phonegap.com/start

PhoneGap Api 文档:docs.phonegap.com/index.html

可以到如下地址下载该演示版APK安装文件:

code.google.com/p/goodev/do…

由于Google Code TOS修改,现文件下载在金山快盘中提供. 详情点击我.


移动平台越来越火,智能设备(IPhone、Android、平板 等…)越来越普及,,各大公司企业纷纷布局移动平台,都想和Apple、Google来瓜分这一块美味的蛋糕。由于Android的开放、开源、免费等众多好处,当然众望所归的成为大家试水的平台,俗话说:站在巨人的肩膀上蹦的更高!

在国内继移动OPhone、联想LePhone、小米MIUI、点心OS之后又有传闻腾讯移动OS、阿里天云OS以及百度秋实OS 等 系统等待诞生以便继续瓜分这片已经混乱的市场。 当大公司、大厂商OS之间这场没有硝烟的战争正在如火如荼的进行着的时候,作为开发者 我们该何去何从! 开发者的出路在何方,为每个OS平台都开发一个单独的版本显然是不现实的;很多想要推出智能移动客户端来扩大自己服务范围的中小企业又该何去何从, 招收一大批开发者来开放各个移动平台客户端显然是成本比较高昂的! 难道开发者和中小企业就无法在这场移动盛宴中分到一口肉嘛(单独开发一个版本就可以分到一杯羹了 \(^o^)/~, 要想吃到一口肉还要多平台并起啊!!!)! 当然有肉吃, 吃肉的工具就是 — HTML5!

HTML5 — 这个全球大量专家制订了N年的产品终于要在移动大战中大放异彩了!现在智能的移动平台不管其开发语言是神马(Java、C、Object-C 等)东西,都无一例外的支持HTML5,走在前列的并且大力推广的就是移动平台的领军人物— Apple的IPhone和Google的Android。用HTML5开发App就像当年的KJava开发手机程序一样, 一次开发 处处获利!由于HTML5的强大,用其开发出来的产品在各个设备上运行足以和该平台上的原生App想媲美!例如 Google日历和百度图片搜索移动版(使用IPhone和Android手机登陆即可体验)。

HTML5做出来的产品在各个智能移动平台上运行起来行为都是一致的,并且还可以和各个平台特殊的API做交互。使用HTML5开发框架也可以让开发者更省力,例如 PhoneGapSencha Touch 等框架。

当然HTML5也不是万能的,HTML5用来开发游戏就很难达到原生App的效果,如果你是游戏App开发者,那就没法了!

让HTML5来的更猛烈些吧!会了HTML5其他神马都是浮云啊 !

小提示:HTML5还要搭配CSS3和JS才能发挥其全部威力(⊙o⊙)哦!

原创文章 转载请注明出处!云在千峰


Android手机和IPhone最不一样的区别之一就是,Android手机可以插sdcard而IPhone不可以。但是这样就对Android守造成了一个缺陷,有些手机厂商为了占领低端市场,就出一些价格比较低的Android手机,当用户购买这些手机后,很快就发现一个问题:手机内部存储空间不够用, 还没安装几个游戏呢,手机存储空间就满了!

虽然,新的Android系统可以把程序安装到SDCard上了, 但是程序安装到Sdcard上有时候会出现一些问题,并且有时候很多功能无法使用。 在Android系统中, 如果您想使用一个app的小工具(Widget)功能, 那么这个app必须安装在手机存储空间中, 对于安装在sdcard中的app来说,里面的widget是不可用的。 所以:对于那些Widget控来说,还是需要购买高端手机(手机内部存储空间大些)。

如果你的手机内部存储空间已经不够用了 而你又不是Widget控, Widget使用的很少, 那么你可以考虑使用豌豆夹来把一些只能安装到手机存储空间的app强制安装到sdcard中来解放一些存储空间。 详情: wandoujia.com/help/entry/…


前几天收到一封Google AdMob 升级的通知邮件, 以前AdMob SDK升级并没有收到邮件, 这次看来更改比较大。 主要改动如下:

此外,新版 SDK 還提供如下功能:

• 提高廣告供應率。我們的系統整合後,新版 SDK 可以在 AdMob 廣告未滿時取得 Google 廣告。這項候補功能根據網站 ID 啟用,所以您需要對每個網站 ID 進行如下操作:
1。登入帳戶。
2。找到需要取得 Google 廣告的應用程式,按一下網站 ID 旁的 [管理設定] 按鈕。
3。按一下 [應用程式設定] 標籤。
4。選擇 [使用 Google AdSense 來提高廣告供應率],然後儲存設定。

請注意,套用此設定後,網站 ID 篩選器將不適用於來自 Google 廣告聯播網的廣告。
新的平板和多媒體廣告單元。新版 SDK 提供更多樣化的平板廣告單元規格(728×90、300×250、468×60)和多媒體廣告單元,方便廣告客戶運用 HTML 多媒體廣告活動。
自動更新。新版 SDK 能自動推送任何附加功能和增強功能,同時自動修正錯誤,從而減少 SDK 更新頻率,省下您在應用程式商店重複提交應用程式的麻煩。

昨天就更新了2个app的SDK到最新版本(4.1), 今天登录AdMob上面一看 。哇 有惊喜哎! 最近2个月AdMob的eCPM(每千次有效展示费用) 都是零点零几(0.02), 而更新后的eCPM居然达到了0.2+, 看来不错哦! 如果你最近感觉AdMob收入很不可观,那么感觉更新下新版本的SDK吧。 相信你会发现惊喜的(⊙o⊙)哦!