preface
The API layer is the network layer, which is an essential module of App. I started to do Android development in 2012, from these years of experience in the development of API layer practice for some summary, the content is mainly around the choice of HttpClient, response processing programming model and the best way to notify UI data updates.
The following content is only personal opinion, if there is any discrepancy with the actual content, please point out; If spray, please gently.
Http Client in SDK
Http Client in the title is a generic term, probably the same name as an Http request library, which refers to all Http request clients.
The SDK has two clients: HttpURLConnection and Apache’s HttpClient library.
In the earliest days (probably from Android1.x onwards), the SDK copied HttpURLConnection from Java. But HttpURLConnection is low-level and cumbersome to use. You can send a Get request and have to manipulate the stream without 20 lines of code, upload a file and put together a multi-part block by yourself, and this class had a memory leak Bug prior to Android2.2.
Presumably Google didn’t want to use it themselves, so they built Apache’s HttpClient library into the SDK. In terms of ease of use, it is really simple, and it also realizes coding like Marti-part, so we don’t have to spell it manually. But the drawback is that it is too object-oriented and the code is bloated. Send a Post request, add a Header, and you’re creating a lot of objects, and you’re still creating a lot of code. There are many libraries that encapsulate HttpClient, and the ones I use the most are Android-Async-HTTP and Xutil. After Android5.0, the SDK removed Apache’s HttpClient.
There are also libraries that encapsulate HttpURLConnection, such as Google’s Volley. Volley has excellent performance and built-in image loading capabilities. For a while, I still see a lot of tripartite HTTP libraries using Volley. Volley’s disadvantages are that some Http functions are not perfect. For example, Post requests cannot be sent by default and some codes need to be written by hand. Redirection is not supported.
Modern Http Client
We’re not done with the Http Client. Google introduced Volley at IO 2013. But there was a hiccup at the meeting:
When Google’s developers introduced Volley, someone in the audience below shouted:
“I prefer OkHttp。”
At that time, the crowd laughed, and the introduction of the staff was worth a helpless reply: “Yeah, I like OkHttp too.”
Then OkHttp took off, as if Volley’s introduction was meant to let people know about OkHttp.
Why OkHttp fire?
- It has complete features: Http encoding, full support for protocols and Http Verbs, perfect support for Http Cache
- It performs well: it is not based on
HttpURLConnection
And not based onHttpClient
; They re implemented a set of sockets. The built-in connection pool will reuse connections and select the best Host to minimize network latency - It even supports interceptor, a modern networking feature
- Its API is simple
OkHttp is by far the best Http Client available for Android and Java. There are also three-party libraries wrapped around OkHttp, such as Okhttputils and OkGo, which are very simple to use. If you like annotations, try Retrofit from the same team.
By the way, the personnel information:
-
Square: a us payments company, the team behind Okhttp and Retrofit, has a big head named JakeWharton.
-
JakeWharton: Top Android guy, now at Google, working on Kotlin. Many people know that he wrote ButterKnife, OkHttp, Retrofit, but they may not know that everyone used his NineOldAndroid class library to animate properties back when the Google team’s supportable V4 package did not support them. Back in the days before Google’s supportable v7 package, everyone used its ActionBarSherlock for the ActionBar. The real is a person to hold up a piece of sky.
Programming model for response processing
OkHttp is the best choice for clients. However, in terms of the programming model of response processing, all clients currently provide a Callback model to process the response, which is expressed in pseudocode:
XXClient client = new XXClient();
client.url("https://github.com/li-xiaojun")
.header("a"."b")
.params("c"."d")
.post(new HttpCallback<Bean>(){
public void onError(IOException e){
//do something
}
public void onSuccess(Bean bean){
//do something}});Copy the code
The Callback model falls into the problem of Callback Hell when the code is complex. Of course, you can use extraction methods to refactor, or you can use RxJava to level the Callback hierarchy. But it still doesn’t look as good as synchronized code in terms of readability. Take a look at the code for a synchronous model:
Bean bean = client.url("https://github.com/li-xiaojun")
.header("a"."b")
.params("c"."d") .<Bean>post(); // Asynchronous request Result bean = process(baen); saveDB(bean); // Asynchronous operationCopy the code
Obviously the synchronous model is much more readable, no matter how complex your asynchronous logic is, it doesn’t make it any less readable. How can synchronous code make asynchronous requests?
Java can be implemented with futures and, more elegantly, Kotlin’s coroutines. The code that uses the Kotlin coroutine looks like this:
GlobalScope.launch {
Bean bean = client.url("https://github.com/li-xiaojun")
.header("a"."b")
.params("c"."d")
.<Bean>post().await(); // Asynchronous request
Result bean = process(baen);/ / not asynchronous
saveDB(bean).await();// Asynchronous operation
}
Copy the code
Kotlin’s Coroutine, like coroutines in other languages, has two major advantages: better scheduling performance and asynchronous code-change synchronization. I’m not going to talk about how coroutines are used, just coroutines; If you want to learn about coroutines, the best resource is the Kotlin website.
How do I notify the UI of data updates
If your API layer is written in the UI, this isn’t a problem at all, but it certainly doesn’t have any maintainability or scalability. We face this problem when we separate the API from a single layer (typically the P layer for MVP), separating the data acquisition and processing code from the UI.
There are generally three ways to deal with it:
- The custom Callback
- Use the EventBus
- Using LiveData
The code written with a custom Callback would look like this:
class LoginPresenter{
fun login(username: String, psw: String, listener: OnLoginListener){
GlobalScope.launch {
Bean bean = client.url("https://github.com/li-xiaojun")
.header("a"."b")
.params("c"."d")
.<Bean>post().await() // Asynchronous requestbean? .apply{ listener.onLoginSuccess(this)}? : listener.onError(...) }}}Copy the code
This approach requires a custom callback for each logic, and the code is huge, ugly and undesirable.
To use EventBus to notify the UI, write the code like this:
class LoginPresenter{
const EventLoginSuccess = "EventLoginSuccess"
const EventLoginFail = "EventLoginFail"
fun login(username: String, psw: String){
GlobalScope.launch {
Bean bean = client.url("https://github.com/li-xiaojun")
.header("a"."b")
.params("c"."d")
.<Bean>post().await() // Asynchronous request
if(bean! =null){
EventBus.get().post(new Event(EventLoginSuccess, bean))
}else{
EventBus.get().post(new Event(EventLoginFail, null)}}}}Copy the code
As you can see, EventBus’s approach allows us to define a large number of Event identifiers instead of a large number of callbacks. When a project is complex, there may be hundreds of Event identifiers that are not easy to manage. So this way is not the best way.
The LiveData way code is written like this:
class LoginPresenter{
var loginData = MutableLiveData<Bean>()
fun login(username: String, psw: String){
GlobalScope.launch {
Bean bean = client.url("https://github.com/li-xiaojun")
.header("a"."b")
.params("c"."d")
.<Bean>post().await() // Asynchronous request
loginData.postValue(bean)
}
}
}
Copy the code
As can be seen, the LiveData approach allows us to avoid defining callbacks and Event identifiers and write them more succinct. More importantly, LiveData naturally observes changes in the UI lifecycle, avoids memory leaks, and updates the UI at the best moments.
The MVP and MVVM
The client mainly deals with THE UI, and the most efficient architecture must be MVVM; Vue and React on the front end have proven this completely.
There are three main implementations of MVVM on Android:
- LiveData and ViewModel
- DataBinding
- Implement VM layer based on Kotlin agent
DataBinding requires learning certain syntax, much like Vue on the front end, and has a bit of a performance problem in complex, frequently updated interfaces because of reflection; But it’s a good option.
Kotlin naturally supports property brokering, and we can implement dynamic UI updates based on Kotlin’s brokering syntax, but this takes some effort.
Personal favorites are LiveData and ViewModel.
The Presenter layer in the previous section shows that there is no logic to deal with UI lifecycle changes, such as when the UI ends, the Presenter is not aware of them and therefore cannot release some resources. You can write some code manually, but the ViewModel is the best choice because it naturally monitors UI destruction. So the code for changing to ViewMode looks like this:
class LoginViewModel : ViewModel() {var loginData = MutableLiveData<Bean>()
fun login(username: String, psw: String){
GlobalScope.launch {
Bean bean = client.url("https://github.com/li-xiaojun")
.header("a"."b")
.params("c"."d")
.<Bean>post().await() // Asynchronous request
loginData.postValue(bean)
}
}
// execute when UI is destroyed
fun onCleard(a){
// Release the resource code}}Copy the code
Best practices
To sum up, the best practices based on my experience are: choose OkHttp to send requests, use Kotlin Coroutine to handle responses, and use LiveData to notify UI updates; This logic is abstracted into a VM layer, embodied as a ViewModel.
Isn’t a web request essentially getting an entity class from a URL? Isn’t that better?
GlobalScope.launch {
/ / get request
val user = "https://github.com/li-xiaojun".http().get<User>().await()
/ / post request
val user = "https://github.com/li-xiaojun".http()
.headers("token" to "xxaaav34", ...)
.params("phone" to "188888888"."file" to file, // Upload the file...). .post<User>() .await() }Copy the code
The above code can be done using my open source library, AndroidKTX. Some people say, it’s so simple, what about other requests, global headers, custom interceptors, HTTPS? These are the basic functions of a network library, of course.
The Github address for AndroidKTX is: github.com/li-xiaojun/…
So, here’s my API layer practice code for my project:
class LoginViewModel : ViewModel() {varloginData = MutableLiveData<User? > ()fun login(username: String, psw: String){
GlobalScope.launch {
val user = "https://github.com/li-xiaojun".http()
.params("phone" to "188888888"."password" to "111111")
.post<User>()
.await() // Null indicates that the request failed
loginData.postValue(user)
}
}
// execute when UI is destroyed
fun onCleard(a){
// Release the resource code}}Copy the code
The code for the UI layer looks something like this:
class LoginActivity: AppCompatActivity() {
fun loadData(a){
loginVM.loginData.observe(this, Observe { it? .apply{ updateUI(it) } ? : toast("Request error")})// Perform login
loginVM.login(username, password)
}
}
Copy the code