当CompositeSubscription遇到了FragmentManager


事情是这样的,起初对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);
        }
    }

这是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); } }


可以发现两者唯一的不同就是对于unsubscribed这个状态值的处理,那么这个状态值的作用是什么呢,其实可以在add方法里面找到:

/**
     * 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();
    }

一旦CompositeSubscription已经解绑了,那么add方法将会对添加进来的subscription直接解绑,而这就是当CompositeSubscription遇到了FragmentManager后会发生的问题。

那么我不禁想问,这是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.


你可能感兴趣的:(android,细节)