Android RxLife A Lightweight RxJava Lifecycle Management Library

Keywords: Android Fragment Gradle Lambda

brief introduction

RxLife is a lightweight RxJava Lifecycle Management Library with minimal code invasiveness, ready-to-use, no preparation required, and support disconnecting pipelines in any life cycle approach of Activity/Fragment.

principle

RxLife feObtain the life cycle changes of Activity/Fragment through Lifecycle under Jetpack and inject self-implemented Observer object (which can sense the life cycle changes of Activity/Fragment) through Observable.lift(ObservableOperator) operator to get Disposable object in onSubscribe(Disposable d) method and subsequently in corresponding life cycle callbackExecute the Disposable.dispose() method to disconnect the pipeline so that all Disposable objects above the lift operator are disconnected.

Why Repeat Wheels

Those who are familiar with RxJava should know that trello/RxLifecycle Project, which in the current 3.0.0 release senses life cycle changes of Activity/Fragment through Lifecycle and interrupts pipelines through BehaviorSubject classes and compose, takeUntil operators. One drawback is that it always sends an onComplete event downstream after the pipeline is disconnected, which has business logic in the onComplete eventIt is undoubtedly fatal for my classmates.Why then?Because the internal implementation mechanism of the takeUntil operator is such, interested students can read the source code of the takeUntil operator, which is not expanded here.This is not the case with RxLifefebecause, in principle, RxLifeis different from trello/RxLifecycle, and RxLifefealso provides some additional api on the basis of lift ing operations, which can effectively avoid memory leaks caused by RxJava internal classes holding references to Activity/Fragment.

gradle dependency

implementation 'com.rxjava.rxlife:rxlife:1.0.4'

Source Download

usage

Observable.timer(10, TimeUnit.SECONDS)
        //Break pipeline by default on onDestroy
        .lift(RxLife.lift(this))
        .subscribe(aLong -> {
            Log.e("LJX", "accept =" + aLong);
        });
//perhaps
Observable.timer(10, TimeUnit.SECONDS)
        //Specify pipeline interruption on onStop
        .lift(RxLife.lift(this,Event.ON_STOP))
        .subscribe(aLong -> {
            Log.e("LJX", "accept =" + aLong);
        });

In Activity/Fragment, use Observable's lift() operator, which passes in RxLife.lift(this), and if you need to specify a life cycle method, pass in an additional Event object.What about?Is it extremely simple, doesn't require any preparation, and code is extremely invasive.

Handling memory leaks

Let's look at a case

public void leakcanary(View view) {
    Observable.timer(100, TimeUnit.MILLISECONDS)
            .map(new MyFunction<>()) //Blocking operation
            .lift(RxLife.lift(this))
            .subscribe(new Consumer<Long>() { //Use an anonymous internal class here, holding a reference to the Activity
                //Note that Lambda expressions cannot be used here, otherwise leakcanary cannot detect memory leaks
                @Override
                public void accept(Long aLong) throws Exception {
                    Log.e("LJX", "accept =" + aLong);
                }
            });
}

//Use static internal classes here, do not hold references to external classes
static class MyFunction<T> implements Function<T, T> {

    @Override
    public T apply(T t) throws Exception {
        //When dispose, the first sleep wakes up, and then the second
        try {
            Thread.sleep(3000);
        } catch (Exception e) {

        }

        try {
            Thread.sleep(30000);
        } catch (Exception e) {

        }
        return t;
    }
}

The code above will cause the Activity to be unrecyclable and cause a memory leak. We use the Leakcannry tool to detect that it did cause a memory leak, as follows

We've already used the RxLife library and automatically disconnected the pipeline, so why does it cause a memory leak?The reason is simple: we just interrupted the pipeline without interrupting upstream references to downstream.Looking at the screenshot above, you can see that the upstream always holds downstream references, while the downstream anonymous internal class Consumer holds a reference to an Activity, which results in an Activity that cannot be recycled.

So why doesn't the upstream and downstream references break when the pipeline is broken?

First of all, we need to make it clear that calling the Disposable.dispose() method to disconnect the pipeline does not really disconnect the upstream and downstream, it just changes the value of one flag bit of each Observer object on the pipeline, as we can see from the source code of the LambdaObserver class

@Override
    public void dispose() {
        DisposableHelper.dispose(this);
    }

Uh, just one line of code, let's continue

public static boolean dispose(AtomicReference<Disposable> field) {
        Disposable current = field.get(); //Here we get the upstream Disposable object
        Disposable d = DISPOSED;
        if (current != d) {
            current = field.getAndSet(d); //Change your flag bit to DISPOSED
            if (current != d) {
                if (current != null) {
                    current.dispose();//Close Upstream Disposable Object
                }
                return true;
            }
        }
        return false;
    }

As you can see, there are only two things to do here, one is to change your flag bit, the other is to call the upstream dispose() method. In fact, you just need to look at it a little more, and you will find that most Observer s in RxJava do both.

So how do we resolve this memory leak?In fact, RxJava has long thought of this, and it provides us with an onTerminateDetach() operator that disconnects upstream references to downstream at three moments: onError (Throwable), onComplete(), dispose(). Let's look at the source code, which is in the ObservableDetach class

@Override
public void dispose() {
    Disposable d = this.upstream;
    this.upstream = EmptyComponent.INSTANCE;//Upstream reassignment
    this.downstream = EmptyComponent.asObserver();//Downstream reassignment
    d.dispose();//Call upstream dispose() method
}

@Override
public void onError(Throwable t) {
    Observer<? super T> a = downstream;
    this.upstream = EmptyComponent.INSTANCE;//Upstream reassignment
    this.downstream = EmptyComponent.asObserver();//Downstream reassignment
    a.onError(t); //Call downstream onError method
}

@Override
public void onComplete() {
    Observer<? super T> a = downstream;
    this.upstream = EmptyComponent.INSTANCE;//Upstream reassignment
    this.downstream = EmptyComponent.asObserver();//Downstream reassignment
    a.onComplete();//Call downstream onComplete method
}

Now we know what to do. It's safe to write

Observable.timer(100, TimeUnit.MILLISECONDS)
        .map(new MyFunction<>())//Blocking operation
        .onTerminateDetach() //Break upstream reference to downstream when pipe is disconnected
        .lift(RxLife.lift(this)) //Disconnect pipeline by default on onDestroy
        .subscribe(aLong -> {
            Log.e("LJX", "accept =" + aLong);
        });

But do I have to write this every time?Is it simpler? Yes, RxLifeoffers the RxLife.compose (Lifecycle Owner) method, which integrates onTerminateDetach and lift operators. Next, see how to use it

Observable.timer(100, TimeUnit.MILLISECONDS)
        .map(new MyFunction<>())//Blocking operation
         //Note that the compose operator is used here
        .compose(RxLife.compose(this))//By default, on Destroy breaks pipelines and interrupts references between downstream and downstream
        .subscribe(aLong -> {
            Log.e("LJX", "accept =" + aLong);
        });

If you need to specify a lifecycle approach, you can

Observable.timer(100, TimeUnit.MILLISECONDS)
        .map(new MyFunction<>())//Blocking operation
         //Note that the compose operator is used here
        .compose(RxLife.compose(this, Event.ON_STOP))//Specify to disconnect pipeline at onStop
        .subscribe(aLong -> {
            Log.e("LJX", "accept =" + aLong);
        });
    }

In most cases, we want the observer to be able to call back from the main thread, which you might write

Observable.timer(100, TimeUnit.MILLISECONDS)
        .map(new MyFunction<>())//Blocking operation
        .observeOn(AndroidSchedulers.mainThread()) //Callback on Main Thread
        .compose(RxLife.compose(this, Event.ON_STOP))//Specifies to break pipeline and upstream and downstream references during onStop callback
        .subscribe(aLong -> {
            Log.e("LJX", "accept =" + aLong);
        });

If you're using RxLife, you can write this using the RxLife.composeOnMain method

Observable.timer(100, TimeUnit.MILLISECONDS)
        .map(new MyFunction<>())//Blocking operation
        //A callback to the main thread process that interrupts pipelines and upstream and downstream references during onStop callbacks
        .compose(RxLife.composeOnMain(this, Event.ON_STOP))
        .subscribe(aLong -> {
            Log.e("LJX", "accept =" + aLong);
        });

The RxLife class has only six static methods, as follows

Attention, high-energy alert ahead!!!!!!!

When using the lift and compose operators of Observable in conjunction with RxLife When using the lift and compose operators of Observable, it is best not to have any other operators in the downstream except the subscribe operator. As mentioned earlier, when calling Disposable.dispose(), it will call the upstream dispose() method one level at a time. If there is a Disposable object downstream, it cannot be called. If there are events downstream that need to be sent, thenThen we can't intercept it.
For example:

Observable.just(1)
        .compose(RxLife.compose(this))
        .flatMap((Function<Integer, ObservableSource<Long>>) integer -> {
            //Send data every second for 10
            return Observable.intervalRange(0, 10, 0, 1, TimeUnit.SECONDS);
        })
        .subscribe(aLong -> {
            Log.e("LJX", "accept =" + aLong);
        });

This way, even if the Activity is closed, the observer can still receive events from the upstream every second because compose cannot cut the downstream pipeline. Let's change the code above

Observable.just(1)
        .flatMap((Function<Integer, ObservableSource<Long>>) integer -> {
            //Send data every second for 10
            return Observable.intervalRange(0, 10, 0, 1, TimeUnit.SECONDS);
        })
        .compose(RxLife.compose(this))
        .subscribe(aLong -> {
            Log.e("LJX", "accept =" + aLong);
        });

That's ok ay. Actually, this is not a problem with RxLifefefefefe, and the same is true with the well-known trello/RxLifecycle library, because RxJava is designed so that downstream Disposable objects are not available upstream, so when using RxLifefefefe, it's important to be aware that there are no other operators downstream of the lift or compose operators, except the subscribe operator.Points must be noted.

The latest version of RxLife has used the as operator to circumvent this problem, see more Android RxLife A Lightweight RxJava Lifecycle Management Library (2)

Small egg

The life and compose methods in the RxLife class are all applicable to the five observed objects Flowable, Observable, Single, Maybe, Completable. The reason is the same. We will not explain them all here.

Ending

Ok, the use of RxLifeis almost finished, so we will find that with the RxLifelibrary, we only need to focus on one class, that is, the RxLifeclass, but the api is simple and powerful.Interested students can read RxLifes Source code If you have any questions, please leave a message and I will answer them in the first time.

extend

RxLife Combination HttpSender Sending requests is hardly cool.

For more information on HttpSender, Click HttpSender OkHttp+RxJava Ultra Useful, Super Powerful Http Request Framework

Posted by kyin on Wed, 08 May 2019 07:48:38 -0700