In-depth introduction to RxJava 4-using responsive programming in Android, rxjavaandroid
Original article link
In Articles 1, 2 and 3, I introduced how RxJava is used. Next I will introduce how to use RxJava in Android.
RxAndroid
RxAndroid is an extension of RxJava for the Android platform. It contains some tools that simplify Android development.
First, AndroidSchedulers provides a scheduler for the Android thread system. Do I need to run some code in the UI thread? Very simple. You only need to use AndroidSchedulers. mainThread ():
myImageView.setImageBitmap(bitmap));" data-snippet-id="ext.3a908fae292922ee747bf3812b96776b" data-snippet-saved="false" data-csrftoken="S209BuyF-Lns6GQjjtUA1GXU7g2AjoeSbBLo" data-codota-status="done">retrofitService.getImage(url) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));
If you have created your own Handler, you can use HandlerThreadScheduler1 to link a scheduler to your handler.
Next we will introduce AndroidObservable, which provides multiple functions to work with the Android lifecycle. The bindActivity () and bindFragment () methods use AndroidSchedulers. mainThread () by default to execute the observer Code. These two methods will notify the observer to stop sending new messages when the Activity or Fragment ends.
myImageView.setImageBitmap(bitmap);" data-snippet-id="ext.5c0de8f9c2ae22479f3d20dab3f49a93" data-snippet-saved="false" data-csrftoken="pa7T2xOV-_9wQlmPqxddxNWED8_HVfRfbm_s" data-codota-status="done">AndroidObservable.bindActivity(this, retrofitService.getImage(url)) .subscribeOn(Schedulers.io()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap);
I also like the AndroidObservable. fromBroadcast () method. It allows you to create an Observable object similar to BroadcastReceiver. The following example shows how to be notified when the network changes:
handleConnectivityChange(intent));" data-snippet-id="ext.e011f49bd631e648f381276e70924f95" data-snippet-saved="false" data-csrftoken="vcuSp9tp-dX4aB9Q8q3vr91Z2enxvDqF1b_A" data-codota-status="done">IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);AndroidObservable.fromBroadcast(context, filter) .subscribe(intent -> handleConnectivityChange(intent));
Finally, we will introduce ViewObservable, which can be used to add some bindings to the View. If you want to receive an event every time you click view, you can use ViewObservable. clicks () or you want to listen to the content changes of TextView, you can use ViewObservable. text ().
handleClick(view));" data-snippet-id="ext.54eeae84d71d086888c0cb38e7023cd7" data-snippet-saved="false" data-csrftoken="mOlBEDl3-Xr-9fLzCU1zcZ7hhlBIw2a3j7Rw" data-codota-status="done">ViewObservable.clicks(mCardNameEditText, false) .subscribe(view -> handleClick(view));
Retrofit
The well-known Retrofit library has built-in support for RxJava. Generally, calling and sending can obtain asynchronous results by using a Callback object:
cb);" data-snippet-id="ext.7a31d4059ecdeb90f2f566f1b8c62f15" data-snippet-saved="false" data-csrftoken="m19MGMH8-hYztJoNLtvDZVVl1m8IALT4MZW0" data-codota-status="done">@GET("/user/{id}/photo")void getUserPhoto(@Path("id") int id, Callback<Photo> cb);
With RxJava, you can directly return an Observable object.
getUserPhoto(@Path("id") int id);" data-snippet-id="ext.37f2570f60d4a415a1fb28d477d3f29a" data-snippet-saved="false" data-csrftoken="aQNpWi6E-5bod-zx4ssrYSATHwDlaHqZFMlI" data-codota-status="done">@GET("/user/{id}/photo")Observable<Photo> getUserPhoto(@Path("id") int id);
Now you can use the Observable object at will. You can not only obtain data, but also perform transformations.
Retrofit's support for Observable makes it easy to combine multiple REST requests. For example, if we have a request for obtaining photos and a request for obtaining metadata, we can send these two requests concurrently and wait until both results are returned before processing:
createPhotoWithData(photo, metadata)) .subscribe(photoWithData -> showPhoto(photoWithData));" data-snippet-id="ext.48b9d99e151ee7e659a7a26dfa7a78ec" data-snippet-saved="false" data-csrftoken="47xhRNOo-2pL0oPWdScqONWfr_gmYoz0nt9k" data-codota-status="done">Observable.zip( service.getUserPhoto(id), service.getPhotoMetadata(id), (photo, metadata) -> createPhotoWithData(photo, metadata)) .subscribe(photoWithData -> showPhoto(photoWithData));
In the second article, I demonstrated a similar example (using flatMap ()). Here I just want to show how simple it is to combine multiple REST requests using RxJava + pull fit.
Legacy Code, extremely slow code
Retrofit can return Observable objects, but what if other libraries you use do not support this? Or an internal code, do you want to convert them into Observable? Is there any simple solution?
In most cases, Observable. just () and Observable. from () can help you create an Observable object from the legacy code:
newMethod() { return Observable.just(oldMethod());}" data-snippet-id="ext.1d3b99e0bb2fdd136e8b043becb6f68a" data-snippet-saved="false" data-csrftoken="I1Xi5q3Z-wZUtCbDkDzCnRt_SvWLOmQfWzOk" data-codota-status="done">private Object oldMethod() { ... }public Observable<Object> newMethod() { return Observable.just(oldMethod());}
In the above example, if oldMethod () is fast enough, there is no problem, but what if it is slow? Calling oldMethod () will block the thread where it is located.
To solve this problem, refer to the method I have been using-use defer () to wrap slow code:
newMethod() { return Observable.defer(() -> Observable.just(slowBlockingMethod()));}" data-snippet-id="ext.45b2205f0083a19731df7194d3298699" data-snippet-saved="false" data-csrftoken="A4ULhuPG-ikgrpuFDeO8PtIUgUhKGUjXtVl0" data-codota-status="done">private Object slowBlockingMethod() { ... }public Observable<Object> newMethod() { return Observable.defer(() -> Observable.just(slowBlockingMethod()));}
Currently, newMethod () is not blocked unless you subscribe to the returned observable object.
Lifecycle
I leave the most difficult part at the end. How to deal with the lifecycle of an Activity? There are two main problems:
1. After configuration changes (such as screen rotation), continue the previous subtasks.
For example, if you send a REST request using fit, then you want to display the result in listview. What if the user rotates the screen during a network request? Of course you want to continue the request, but what should you do?
2. Memory leakage caused by Context held by Observable
This problem occurs because the context Reference is held in some way during subpartition creation, especially when you interact with the view! If the Observable does not end in time, the memory usage will increase.
Unfortunately, there is no silver bullet to solve these two problems, but here are some guiding schemes for your reference.
The solution to the first problem is to use the built-in cache mechanism of RxJava, so that you can execute unsubscribe/resubscribe on the same Observable object without repeatedly running the Observable code. Cache () (or replay () will continue to execute network requests (even if you call unsubscribe, it will not stop ). This means that you can create a new Observable object from the return value of cache () when the Activity is re-created.
request = service.getUserPhoto(id).cache();Subscription sub = request.subscribe(photo -> handleUserPhoto(photo));// ...When the Activity is being recreated...sub.unsubscribe();// ...Once the Activity is recreated...request.subscribe(photo -> handleUserPhoto(photo));" data-snippet-id="ext.c38cefa0d5c2cd722eecf1ec2bf8b914" data-snippet-saved="false" data-csrftoken="WgDIcGsX-xtXrcOg8kNgWCiPSx8wU-XBUJIE" data-codota-status="done">Observable<Photo> request = service.getUserPhoto(id).cache();Subscription sub = request.subscribe(photo -> handleUserPhoto(photo));// ...When the Activity is being recreated...sub.unsubscribe();// ...Once the Activity is recreated...request.subscribe(photo -> handleUserPhoto(photo));
Note that two sub requests use the same cache. Of course, where to store the request results, you still need to do it yourself. solutions related to all other lifecycles must be stored somewhere outside the lifecycle. (Retained fragment or Singleton ).
The solution to the second problem is to cancel the subscription at a certain time in the lifecycle. A common mode is to use compositesubtions to hold all Subscriptions, and then cancel all Subscriptions in onDestroy () or onDestroyView.
System.out.println(s)));}@Overrideprotected void onDestroy() { super.onDestroy(); mCompositeSubscription.unsubscribe();}" data-snippet-id="ext.d775ddce16082cd2478fc901fd87fca9" data-snippet-saved="false" data-csrftoken="xf55M4dO--mTGTk5nw0ID2YfonmCoL34o5uI" data-codota-status="done">private CompositeSubscription mCompositeSubscription = new CompositeSubscription();private void doSomething() { mCompositeSubscription.add( AndroidObservable.bindActivity(this, Observable.just("Hello, World!")) .subscribe(s -> System.out.println(s)));}@Overrideprotected void onDestroy() { super.onDestroy(); mCompositeSubscription.unsubscribe();}
You can create a CompositeSubscription object in the base class of Activity/Fragment and use it in the subclass.
Note! Once you call CompositeSubscription. unsubscribe (), this CompositeSubscription object is not available. If you want to use CompositeSubscription, you must create a new object.
You need to add additional code for both solutions. If anyone has a better solution, please let me know.
Summary
RxJava is still a very new project, and RxAndroid is even more. RxAndroid is still under active development, and there are not many good examples. I bet some of my suggestions will become obsolete in a year.