It has been half a year since the last article, although there is no output during this period, but I still pay attention to RxJava and some domestic trends/articles, and so on. I feel that many people still have some misunderstanding and “wrong” understanding of RxJava. So today we’re going to start with the basics and take a look at RxJava.
Let’s take a step back, forget about RxJava, and talk about one of the thorny issues that we encounter in Android development, and many of our developers, is the issue of asynchronous development. For example, we need to get a set of data from the Model layer in MVP/MVVM for our front page presentation, like this:
A simple Dribbble page, because there are many images, we do not want to get all the images in the first request, we prefer to get some image MetaData, such as URL and so on. Then in the respectively asynchronous loading, to achieve a better user experience. Here we do not consider the RecyclerView plus Glide combination, with simple pseudo code to show.
Our Model layer needs two methods, one is to get the MetaData of our home page image, and the other is to get the Bitmap based on the MetaData.
If everything were synchronous, the code would be very simple:
interface Model{
fun getList(a) : List<MetaData>
fun getBitmap(metaData : MetaData) : Bitmap
}
Copy the code
Many students like RxJava because chained calls look very comfortable, and chained calls or higher order functions or operators are not exclusive to RxJava. Java8’s Stream API and Kotlin have related operations, such as our code in Kotlin can be called like this
model.getList()
.map { model.getBitmap(it) }
.forEach { showBitMap(it) }
Copy the code
Does it look like what you call elegant, neat RxJava chained calls?
But synchronization means blocking, and network loading of bitmaps is notoriously time consuming. In order not to block the user interface (UI thread), we want it to execute asynchronously in the background and then output to the foreground. So the simplest and most straightforward way for us to implement asynchronous communication in Android is to add callbacks. So our code looks like this
/ / define the CallBack
interface CallBack<T> {
fun onSuccess(t:T)
fun onError(error:Error)
}
interface Model{
fun getList(callback:CallBack<List<MetaData> >)
fun getBitmap(metaData:MetaData, callback:Callback<MetaData>)
}
Copy the code
If you’ve seen a lot of RxJava tutorials, you might think I’m going to talk about Callback Hell, and then show you the code RxJava uses to solve Callback Hell, but that’s not going to make much sense, isn’t it?
Why do we have callback hell? While synchronizing, we can keep our favorite ** “chained calls” ** when synchronizing, what we do can be simplified as follows: Enter the main interface -> getList -> getList -> bitmap -> bitmap -> display bitmap
But what about asynchronous? Go to the home screen -> getList(callback: callback <List>) to pass our callback to the background -> wait for the background to callback our callback
Now, the important thing is, unlike synchronization, we don’t get our List directly here. We’re waiting for the other side to notify us asynchronously. When synchronizing, we pull the data directly:
When asynchronous, the intuitive thing is that we are “waiting” for data, and the asynchronous object is pushing data to us.
So, from our perspective, we’re reactive. Reactive
Let’s go back to our example:
This is what we do when we sync
interface Model{
fun getList(a) : List<MetaData>
fun getBitmap(metaData : MetaData) : Bitmap
}
Copy the code
With asynchrony, our method has no return value and takes an argument, so we can’t use nice “chained calls”. That’s because List, by itself, is a synchronous type. Every time we operate on the List, we’re going to pull ** data from the List. Don’t believe it? Let’s take a look:
As you all know, lists are not the most basic sets, but there are also common sets like HashMap,Set,Table, Vector, and so on. They all have a common parent class: Iterable
interface 可迭代<out T> {
fun iterator(a): Iterator<T>
}
Copy the code
Iterator is an iterator, and it looks like this
interface Iterator<out T> {
fun next(a): T
fun hasNext(a): Boolean
}
Copy the code
This is our most troublesome iteration:
val i = iterator()
while(i.hasNext()){
val value = i.next()
}
Copy the code
So we have foreach in Java, and then the Stream API and all that syntactic sugar. So what we see here is that we do ask the List first each time, does it have a value, and if it does we get that value, and if it doesn’t, we break out of the loop, and the List is done. Read finished.
Imagine if we had an AsyncList where all the AsyncList reads us, and then we use higher-order functions like Map /foreach and so on, just like we do with synchronization. Such as
interface Model{
fun getList(a) : AsyncList<MetaData>
fun getBitmap(metaData : MetaData) : Bitmap
}
Copy the code
So we can synchronize,
model.getList()
.map { model.getBitmap(it) }
.forEach { showBitMap(it) }
Copy the code
Now let’s design our AsyncList based on Iterable. We know that Iterable is synchronous and pulls data. We need AsyncList to be asynchronous and push data to us. Like List, we give a superclass to all asynchronous collections to design an AsyncIterable. We know that Iterable provides Iterator by actively asking next,hasNext and other methods of Iterator and we actively pull data. So our AsyncIterable, in theory, should be that we register AsyncIterator, pass our AsyncIterator to AsyncIterable, and let it notify us, implement asynchron and push data. So our implementation of AsyncIterable should look something like this
interface AsyncIterable<T> {
fun iterator(iterator : AsyncIterator<T>)
}
Copy the code
(Look familiar?)
We’re going to design AsyncIterator in a synchronous way, two methods, one is hasNext, which means we’re going to ask the iterable if there’s a value after that, if it’s asynchronous, it’s going to be our AsyncIterable that tells us, He has no value after the next. So it’s like this:
fun hasNext(has : Boolean)
Copy the code
Yes, this is a CallBack that tells us if there is a value. Once false is received, the iteration is over and our AsyncIterable has finished iterating. The other approach to next() is to actively ask what the current value is. So our AsyncIterable uses this method to tell us what the current value is, again using a CallBack like this:
fun onNext(current:T)
Copy the code
(Look familiar? (Manual funny))
There are two problems: the first question: we are here to hide a mistake, because hasNext () method returns false of time is not necessarily no the next value, it is possible that when dealing with the current value to some error or abnormal, so that he can’t handle the next value, our app would collapse at this moment. So in the case of asynchrony, we want our AsyncIterable to tell us if it’s wrong, so we don’t do anything else. So here we have:
fun onError(e:Throwable)
Copy the code
(Does it look familiar? (Manual funny))
The second problem is that the hasNext method is obviously redundant, because at the time of synchronization, we don’t know whether it will have a value next, so every time we access the List, we ask if there is a value next, and then we proceed to the next step. When AsyncIterable is asynchronous, our AsyncIterable must know if it has a value next. We only want it to notify us when it has no value at the end, which means our previous hasNext(true) is redundant. We really only care when hasNext(true) is called. So let’s simplify it to a method that’s only called at the end:
fun onComplete(a)
Copy the code
So we have our AsyncIterator
interface AsyncIterator<T> {
fun onNext(current:T):
fun onComplete(a)
fun onError(e:Throwable)
}
Copy the code
That’s right, it’s our Observer in RxJava, and our Asynciterable corresponds to our Observable.
interface Observable<T> {
fun subscribe(observer : Observer<T>)
}
Copy the code
From this, I define an Observable:
An Observable is a collection of asynchronous data
Yes, it’s a Set, just like List,Set, or Vector. It is a set of data. A Collection can contain 0,1, many or even unlimited data. So an Observable can contain 0,1, n, or even an infinite amount of data.
When we handle a Collection exception (such as NullPointerException), our program crashes and there is no further processing. So no more data will be pushed to us by our Observable after receiving an onError.
Collection can be combined, transformed and so on through High Oroder Function, so An Observable as one of the Collection can also be combined and transformed.
To operate Iterable, we use the getIterator method to obtain the Iterator for active inquiry and to pull data to achieve iteration.
To operate an Observable, we register an Observer through the subscribe method to acquire data passively, and Obseravble pushes data to achieve iteration.
After all this effort, we finally abstracted an asynchronous set. So what are his benefits?
-
First of all, this way of pushing data is our intuitive method of asynchronous operation. As we have learned above, when asynchronous operation is performed, we should act as the receiver. We are passive, we have no way to ask the production side whether the asynchronous task has been completed. Only the producer knows if he has finished production, so he informs us when he has finished production and gives us the results, which is an intuitive solution. Java or any other high-level language does not provide such a solution. We just custom CallBack to implement the CallBack.
-
When using the CallBack solution, you know too much. Take, for example, our example above
fun getList(callback:CallBack<List<MetaData>>)
Copy the code
This way. We know from the callback that this should be an asynchronous operation. It might be time consuming, so we might need a thread to execute it, and then it’ll give me a List, which is synchronous. There are so many things you need to care about. As the saying goes, grasp the present and look to the future! It’s good that we’re doing what we’re doing, and an Observable solves that. This is what we should look like when we’re done with the above method
fun getList() : Observable<List<MetaData>>
Copy the code
The most correct probably should be:
fun getList() : Observable<MetaData>
Copy the code
That’s right, because an Observable is itself a collection and no longer needs to be nested with a synchronized List. However, it is common to use Observable
because of server design. In An Observable we don’t care how the method was generated. We iterate over the data as usual, and all we need to know is that he will let me know when he produces the data. And how exactly are you going to produce the data for me? In what thread? Is it synchronous or asynchronous? Is there a block?
I don’t really care!
- Operator, right because An Observable is a mathematical set. The set can perform a series of transformations, using higher-order functions that we define, such as map,filter, and so on. These operators are not exclusive to RxJava; they are our common operations on collections. We should be able to do this for lists, vectors, etc., which Java itself doesn’t provide. Since Java 8, these methods have been supplemented through the Stream API, and one of the advantages of RxJava is that it provides more than just an asynchronous collection class, Observable. Hundreds of commonly used operators are also provided.
# # to summarize
With this article, my goal is to give you an understanding of what an Observable is, why it’s designed the way it is, what benefits it provides, and what problems it solves. And the answer is obvious. An Observable is a collection of asynchronous data. Because there is a fundamental difference between asynchronous and synchronous operations (push and pull), we design an Observable in reverse of Iterable. The advantage is that the mathematical definition of the set is preserved, and the Callback is removed, and the operator (higher-order function) allows you to perform some transformation operations on the set. Solve the usual situation asynchronous operation is not intuitive, complex, callback hell and so on.
#### References (some links may require ladders)
- A Playful Introduction to Rx by Erik Meijer
- Expert to Expert: Brian Beckman and Erik Meijer – Inside the .NET Reactive Framework (Rx)
- ReactiveX