事情是这样的,起初对CompositeSubscription比较陌生的情况下没有将其使用到项目上,但当了解它的作用并测试之后,便替换之前一直使用的Subscription因为对它有误用。简单来说就是一个Subscription只能对应一个subscriber,如果有多个subscriber需要解绑的话就只能依次将他们的subscription进行判断并解绑。这样一来冗余代码就会很多而且很没有必要,而这时CompositeSubscription就完美解决了这个问题。
比如在activity中这样声明:
private final CompositeSubscription subscriptions = new CompositeSubscription();
那么它的优势就可以很好体现,一个是不用声明subscriber对应的subscription的变量,二是既然没有这个变量就不可能对所有的subscriber进行解绑,而是直接使用上面那个变量进行解绑,最后是在解绑时不用进行任何判断。
使用方式如下:
subscriptions.add(dataPool.getUserCenterProvider()
.userOrderCenter()
.subscribe(new HandleErrorSubscriber(app) {
@Override public void onEnd() {
refreshLayout.setRefreshing(false);
dismissLoadingDialog();
}
@Override public void onSuccess(BaseModel model) { }
}));
@Override public void onDestroy() {
super.onDestroyView();
subscription.unsubscribe();
}
而在实际使用过程中,它遇到了缓存fragment的FragmentManager,问题就出来了,即一旦解绑了之后该CompositeSubscription就不能继续使用了。danlew在其博客中这样写道:A warning! Once you call CompositeSubscription.unsubscribe() the object is unusable, as it will automatically unsubscribe anything you add to it afterwards! You must create a new CompositeSubscription as a replacement if you plan on re-using this pattern later.而这个问题其实也很难以检查得到,所以要定位到这个问题也很花时间。
先说解决的办法,解决办法有两种,一种就是上面danlew说的方式,即不初始化为全局final变量,而是将其作为一个普通成员变量声明并在onViewcreated方法内进行初始化,也就是说当fragment被manager从缓存里取出来时,会重新调用onViewcreated方法,因而就可以达到每次重新创建一个CompositeSubscription的目的。第二种就是使用CompositeSubscription.clear()方法,它与unsubscribe方法几乎一模一样,都会对添加的元素进行解绑,而唯一不同的地方就是clear掉了之后该CompositeSubscription还可以继续使用。
这是clear的源码:
/**
* Unsubscribes any subscriptions that are currently part of this {@code CompositeSubscription} and remove
* them from the {@code CompositeSubscription} so that the {@code CompositeSubscription} is empty and
* able to manage new subscriptions.
*/
public void clear() {
if (!unsubscribed) {
Collection unsubscribe = null;
synchronized (this) {
if (unsubscribed || subscriptions == null) {
return;
} else {
unsubscribe = subscriptions;
subscriptions = null;
}
}
unsubscribeFromAll(unsubscribe);
}
}
/**
* Unsubscribes itself and all inner subscriptions.
* After call of this method, new {@code Subscription}s added to {@link CompositeSubscription}
* will be unsubscribed immediately.
*/
@Override
public void unsubscribe() {
if (!unsubscribed) {
Collection unsubscribe = null;
synchronized (this) {
if (unsubscribed) {
return;
}
unsubscribed = true;
unsubscribe = subscriptions;
subscriptions = null;
}
// we will only get here once
unsubscribeFromAll(unsubscribe);
}
}
/**
* Adds a new {@link Subscription} to this {@code CompositeSubscription} if the
* {@code CompositeSubscription} is not yet unsubscribed. If the {@code CompositeSubscription} is
* unsubscribed, {@code add} will indicate this by explicitly unsubscribing the new {@code Subscription} as
* well.
*
* @param s
* the {@link Subscription} to add
*/
public void add(final Subscription s) {
if (s.isUnsubscribed()) {
return;
}
if (!unsubscribed) {
synchronized (this) {
if (!unsubscribed) {
if (subscriptions == null) {
subscriptions = new HashSet(4);
}
subscriptions.add(s);
return;
}
}
}
// call after leaving the synchronized block so we're not holding a lock while executing this
s.unsubscribe();
}
那么我不禁想问,这是bug吗?事实上并不是,而是设计者故意为之,那么我们会遇到这个问题其实也说明我们自身对于unsubscribe的理解还不够。通过查看源码,我们可以找到与CompositeSubscription长得很像的SubscriptionList同样是subscription容器,它也有unsubscribe状态值,而他们都有一旦解绑就不能使用的设定,这个设定的确可以避免很多问题,而重新创建开销并不大,所以可以说是最优解:
RxJava containers are designed with a terminal state in mind. When a container gets into its terminal state via unsubscribe() atomically, all previous contained elements are unsubscribed and all subsequent add/set of a Subscription is immediately unsubscribed and never added to the container. Since unsubscription is highly asynchronous, checking isUnusbscribed() is not sufficient to avoid starting tasks or creating resources in case of a concurrent unsubscribe call.
The clear method removes all contained subscription and unsubscribes them, but you can add new subscriptions to the composite after that.
In android terms, imagine you have a background task which wants to add a Subscription to the app composite just after your app is signalled to pause. Without a guaranteed terminal state, your async task would succeed and now you have a resource leak. With the guaranteed terminal state, the resource is immediately unsubscribed and no leak happens.