[Think] kotlin goods coroutine elegant and lingering Retrofit - text

KotlinHas become AndroidGoogle's first recommendation language development, the project has also been used for a long time kotlin, plus Kotlin1.3 release, kotlin coroutine has stabilized, it is inevitable there will be some of their own thinking.

For the project network request function, we are constantly reflect on how to write elegant, simple, fast and safe. I believe this is a developer you constantly think about. Since our projects are used Retrofitas a network library, so all thoughts are based Retrofitdeployment.

This article will start with the evolution of my thinking. Kotlin involved coroutine, extension methods, DSL, there is no basis for small partners, first understand these three things, this article will not explain. DSL can see as I write this Introduction

A network request, the implicit question we need to focus on is this: page lifecycle bindings, pages need to be closed after the close network of outstanding requests. To do this, you seniors, Eight Immortals, recount. I also learn from, imitate their predecessors, the transition to self-understanding.

1. Callback

In the first study to use, the Callbackasynchronous method is Retrofitthe most basic use, as follows:

interface:

interface DemoService {

    @POST("oauth/login")
    @FormUrlEncoded
    fun login(@Field("name") name: String, @Field("pwd") pwd: String): Call<String>
}
复制代码

use:

val retrofit = Retrofit.Builder()
    .baseUrl("https://baidu.com")
    .client(okHttpClient.build())
    .build()

val api = retrofit.create(DemoService::class.java)
val loginService = api.login("1", "1")
loginService.enqueue(object : Callback<String> {
    override fun onFailure(call: Call<String>, t: Throwable) {

    }

    override fun onResponse(call: Call<String>, response: Response<String>) {

    }
})
复制代码

Here not elaborate.

In the closed network requests when in need onDestroycall cancelthe method:

override fun onDestroy() {
    super.onDestroy()
    loginService.cancel()
}
复制代码

In this way, prone to forget to call cancelmethod, and network operations and closed the requested operation is separate, is not conducive to management.

This is certainly not an elegant way. With Rx hot, the way network requests of our project, is gradually turning into a way of Rx

2. RxJava

Such use, Baidu, full of tutorials explain, at least in this way can be seen is a program we are more accepted.

In use Rx, we also tried a variety of packages, such as custom Subscriber, will onNext, OnCompleted the onError split combination, to meet different needs.

First, Retrofitadd Rx converter inside RxJava2CallAdapterFactory.create():

addCallAdapterFactory(RxJava2CallAdapterFactory.create())
复制代码

The use substantially as follows RxJava, first interface Callto Observable:

interface DemoService {

    @POST("oauth/login")
    @FormUrlEncoded
    fun login(@Field("name") name: String, @Field("pwd") pwd: String): Observable<String>
}
复制代码

:( use with RxAndroid binding declaration period)

api.login("1","1")
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread()) //RxAndroid
    .subscribe(object :Observer<String> {
        override fun onSubscribe(d: Disposable) {

        }

        override fun onComplete() {

        }

        override fun onNext(t: String) {

        }

        override fun onError(e: Throwable) {
        }
    })
复制代码

This use is indeed a lot of convenience, reactive programming idea is also very good, everything is the flow of events. By RxAndroidswitched UI thread binding and page life cycle, the page is closed, automatically cut off the flow of events passed down.

RxJavaThe biggest risk is that that is a memory leak, but RxAndroiddoes avoid some of the risk of leakage. And by looking at the RxJava2CallAdapterFactorysource code and found that indeed call a cancelmethod, ah ...... it looks good too. But always felt RxJava too large, overkill.

3. LiveData

With the release of advancing the project and Google's family bucket. A lightweight version of RxJavaentering into our line of sight, that is LiveData, LiveDatadrawing a lot RxJavaof design ideas, also belong to the category of reactive programming. LiveDataThe biggest advantage of that is that the response Acitivtylifecycle, do not like RxJavato go to binding declaration period.

Similarly, we first need to add LiveDataCallAdapterFactory (link is written in the official google, you can copy directly to the project) for the retrofit of Callbackconvert LiveData:

addCallAdapterFactory(LiveDataCallAdapterFactory.create())
复制代码

Interface with the following:

interface DemoService {

    @POST("oauth/login")
    @FormUrlEncoded
    fun login(@Field("name") name: String, @Field("pwd") pwd: String): LiveData<String>
}
复制代码

transfer:

api.login("1", "1").observe(this, Observer {string ->
    
})
复制代码

These are the most basic use, when to use in the project, usually the custom Observer, used to distinguish between various types of data.

Call the above observemethod, we passed a this, this thisrefers to the statement cycle, we generally AppCompatActivitywhen in use, direct transfer themselves on it.

Following is a brief description jump performed at source. By looking at the source code can be found:

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer)
复制代码

Which thisitself is passed LifecycleOwner.

Then we jump in layers AppCompatActivity, you will find AppCompatActivityis inherited from SupportActivitythe parent class:

public class SupportActivity extends Activity implements LifecycleOwner, Component
复制代码

On its own LifecycleOwnerinterface implementation. That is, unless special requirements, generally we just need to pass itself on it. LiveDataAutomatically monitor data flow and processing unbind.

Generally speaking: the onCreatedata is a one-time binding, the back do not need to re-bound.

When the life cycle come onStartand onResumetime, LiveDatait will automatically receive an event stream;

When the page is inactive, the pause will receive event stream to recover data when receiving the page resume. (E.g. A jump to B, then A will be suspended received back from B to A as later to restore the data stream received)

When the page onDestroywhen the viewer is automatically deleted, thereby interrupting the flow of events.

It can be seen LiveDataas the official kit, easy to use, response life cycle is very intelligent, and generally do not require the extra processing.

(More advanced usage, you can refer to the official Demo , you can wait to have carried out a set of database cache response package, very nice. I recommend learning package under official ideology, even if not in use, but also for their great benefit)

4. Kotlin coroutine

It says so much, here entered the topic. Under careful observation you will find that the above are used by Retrofitthe enqueueasynchronous method, and then use Callbackthe network callback, even RxJava and Livedata converter, inside is actually used Callback. Prior to this, Retrofitthe author also wrote a coroutine converter, addresses this , but the interior is still using Callback, in essence, are the same. (At present, the library was only abandoned, in fact I think that the use of coroutines no sense, Retrofitin the latest version 2.6.0, directly supported kotlin coroutine suspendsuspend function),

Before looking at Retrofitthe small partners you should know that Retrofitthere is a synchronous and asynchronous invocation methods.

void enqueue(Callback<T> callback);
复制代码

This is way above an asynchronous call, a pass Callback, this is our most most commonly used method.

Response<T> execute() throws IOException;
复制代码

This is a synchronous call the above method, the thread will block, is returned directly to the network data Response, it is rarely used.

Later, I was thinking, can not be combined with kotlin coroutine, abandoned Callbackthe direct use of Retrofitsynchronization method, when the asynchronous write synchronization code sequence written, logical, high efficiency, synchronous wording is even more convenient to manage objects.

He went ahead.

First, write an extension method of a coroutine:

val api = ……
fun <ResultType> CoroutineScope.retrofit() {
    this.launch(Dispatchers.Main) {
        val work = async(Dispatchers.IO) {
            try {
                api.execute() // 调用同步方法
            } catch (e: ConnectException) {
                e.logE()
                println("网络连接出错")
                null
            } catch (e: IOException) {
                println("未知网络错误")
                null
            }
        }
        work.invokeOnCompletion { _ ->
            // 协程关闭时,取消任务
            if (work.isCancelled) {
                api.cancel() // 调用 Retrofit 的 cancel 方法关闭网络
            }
        }
        val response = work.await() // 等待io任务执行完毕返回数据后,再继续后面的代码

        response?.let {

            if (response.isSuccessful) {
                println(response.body()) //网络请求成功,获取到的数据
            } else {
                // 处理 HTTP code
                when (response.code()) {
                    401 -> {
                    }
                    500 -> {
                        println("内部服务器错误")
                    }
                }
                println(response.errorBody()) //网络请求失败,获取到的数据
            }

        }
    }
}
复制代码

The above is the core code, the main meaning of the written comments. Ui entire workflow coroutine out, it is possible to operate freely UI controls, followed io thread to call the network synchronization request, and waits io finished thread, followed by processing the results to get the entire process is based on writing synchronization code, a step in the process, did not back off and the code lead to a sense of fragmentation. So to continue, we think of ways to get data back out.

Here we use the DSL method, first customize a class:

class RetrofitCoroutineDsl<ResultType> {
    var api: (Call<ResultType>)? = null

    internal var onSuccess: ((ResultType?) -> Unit)? = null
        private set
    internal var onComplete: (() -> Unit)? = null
        private set
    internal var onFailed: ((error: String?, code, Int) -> Unit)? = null
        private set

    var showFailedMsg = false

    internal fun clean() {
        onSuccess = null
        onComplete = null
        onFailed = null
    }

    fun onSuccess(block: (ResultType?) -> Unit) {
        this.onSuccess = block
    }

    fun onComplete(block: () -> Unit) {
        this.onComplete = block
    }

    fun onFailed(block: (error: String?, code, Int) -> Unit) {
        this.onFailed = block
    }

}
复制代码

Such exposure outside the three methods: onSuccess, , onComplete, onFailedused to classify data is returned.

Then, we have to transform our core code, the method will be passed:

fun <ResultType> CoroutineScope.retrofit(
    dsl: RetrofitCoroutineDsl<ResultType>.() -> Unit //传递方法,需要哪个,传递哪个
) {
    this.launch(Dispatchers.Main) {
        val retrofitCoroutine = RetrofitCoroutineDsl<ResultType>()
        retrofitCoroutine.dsl()
        retrofitCoroutine.api?.let { it ->
            val work = async(Dispatchers.IO) { // io线程执行
                try {
                    it.execute()
                } catch (e: ConnectException) {
                    e.logE()
                    retrofitCoroutine.onFailed?.invoke("网络连接出错", -100)
                    null
                } catch (e: IOException) {
                    retrofitCoroutine.onFailed?.invoke("未知网络错误", -1)
                    null
                }
            }
            work.invokeOnCompletion { _ ->
                // 协程关闭时,取消任务
                if (work.isCancelled) {
                    it.cancel()
                    retrofitCoroutine.clean()
                }
            }
            val response = work.await()
            retrofitCoroutine.onComplete?.invoke()
            response?.let {
                    if (response.isSuccessful) {
                        retrofitCoroutine.onSuccess?.invoke(response.body())
                    } else {
                        // 处理 HTTP code
                        when (response.code()) {
                            401 -> {
                            }
                            500 -> {
                            }
                        }
                        retrofitCoroutine.onFailed?.invoke(response.errorBody(), response.code())
                    }
            }
        }
    }
}
复制代码

Here using DSL delivery method, can be more needs to be passed, for example, only you need onSuccess, then just pass this way does not have to pass all three, on-demand use.

Use:

According to the official document kotlin first need to transform the next activity:

abstract class BaseActivity : AppCompatActivity(), CoroutineScope {

    private lateinit var job: Job // 定义job

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job // Activity的协程

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel() // 关闭页面后,结束所有协程任务
    }
}
复制代码

ActivityImplement CoroutineScopeinterface according to the current can directly contextusing the acquired coroutine.

Then there is the real use, you can call this extension method in any position:

retrofit<String> {
    api = api.login("1","1")

    onComplete {
    }

    onSuccess { str ->
    }

    onFailed { error, code ->
    }
}
复制代码

In sometimes, we just need to deal with onSuccessthe situation, we do not care about the other two. Then write directly to:

retrofit<String> {
    api = api.login("1","1")

    onSuccess { str ->
    }
}
复制代码

Which you need to write what code is very clean.

As can be seen, then we do not need a separate network requests to bind to the life cycle, when the page is destroyed, jobit was closed, when the coroutine is turned off, it will cancel the calling method Retrofit of the closed network.

5. Section

Coroutine is less than the cost of Threadmulti-threaded, fast response, ideal for lightweight workflow. For coroutine, as well as to take me deeper thinking and learning. Coroutine is not Threada substitute for, or more asynchronous tasks to add one more, we can not follow the inertia of thinking to understand the coroutine, but to its own characteristics and more start to develop it more comfortable use. And with the Retrofit 2.6.0release, it comes with a new coroutine program, as follows:

@GET("users/{id}")
suspend fun user(@Path("id") long id): User
复制代码

Increased suspendpending function support, visible coroutine applications will become increasingly popular.

All of the above mentioned network processing methods, either Rxor LiveData, it is a very good package, no good or bad art. I coroutine packages, perhaps not the best, but we can not lack of thinking, exploration, practice three elements, think about do it.

The best answer is always given himself.

The first written record of this type of article, the process of serious, rigorous record is not, forgive me. Thank you for reading

Guess you like

Origin blog.csdn.net/weixin_33896069/article/details/91362166