This is the 11th day of my participation in the August More Text Challenge. For details, see:August is more challenging
RxJava and coroutines
Web requests typically use Retrofit, rxJava, OKhttp, and so on. Retrofit is basically the same as OKhttp. You can think of retrofit as a network proxy. You tell it which interface to call, and it returns you a result. As for RxJava, it can be understood as a toolkit for managing concurrent multi-threaded tasks, as well as event flows. How to implement the network request with RxJava first?
interface GitHubApi {
fun listReposRx(@Path("user")user:String): Single<List<Repo>>
}
Copy the code
Change the return value to Single, and you’re done with the API.
private fun initRetrofit(a) {
val okHttpClient = OkHttpClient.Builder().sslSocketFactory(
TrustAllSSLSocketFactory.newInstance(),
TrustAllSSLSocketFactory.TrustAllCertsManager()
)
retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(okHttpClient.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))// Use rxJava to add this sentence
.build()
api = retrofit.create(GitHubApi::class.java)
}
Copy the code
And to initialize Retrofit, I need to add addCallAdapterFactory, which IS what I’m default to for all network requests to be executed in the IO thread.
private fun requestByRx(a) {
if (::retrofit.isInitialized && ::api.isInitialized) {
api.listReposRx("TonyDash")
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<List<Repo>> {
override fun onSuccess(t: List<Repo>) {
textView.text = "Rx${t[0].name}"
}
override fun onSubscribe(d: Disposable) {
textView.text = "onSubscribe"
}
override fun onError(e: Throwable){ textView.text = e.message? :"onError"}}}})Copy the code
The last part is the request part, which is roughly similar to the traditional callback method. The most important part is to specify the thread switch. The result is specified by observeOn and then executed on the main thread, which is also handled in the callback method. That doesn’t seem to change much, but what if there’s a requirement for two interfaces, and the results of two interfaces are displayed? If you don’t need RxJava to do this, you can do it, which is result nesting, where the logic of the next interface continues within the result callback of the previous interface. This way of writing, it seems relatively troublesome, and not clear. If I use a coroutine, what do I write?
private fun requestByKtAsync(a){
if (::retrofit.isInitialized && ::api.isInitialized) {
GlobalScope.launch(Dispatchers.Main) {
try {
val async1 = async { api.listReposKt("TonyDash")}val async2 = async { api.listReposKt("TonyDash") }
textView.text = "${async1.await()[0].name} requestByKtAsync ${async2.await()[0].name}"
}catch(e:Exception){ textView.text = e.message ? :"error"}}}}Copy the code
As you can see, async is used, which is also a coroutine, while await() suspends the method until both requests have results and then assigns the value to the TextView. As you can see, coroutines have the advantages of simplicity and the elimination of callbacks; There is also the code writing method, relatively simple. The same thing: you can switch threads; There is no need for nested calls.
Disadvantages of coroutines
My personal view, all the easier to use, is equal to others to help you do more things, that is to say, the performance loss will be larger, suspend keyword, for example, is how to find the corresponding thread processing logic, is a complex process, relatively will have the performance of the loss, but the loss compared to the benefits of coroutines, I think we can ignore it. Coroutines must use try catch to catch exceptions, which I personally consider a minor nuisance, not a drawback.
Coroutines leak
Another common scenario we need to be aware of is that our time-consuming operations are maintained for a period of time, so if the user closes the activity during this period, because the network request is an active thread that holds the activity object, then this will cause a memory leak. So we should get rid of coroutines when we’re not using them.
private fun requestByKtAsync(a){
if (::retrofit.isInitialized && ::api.isInitialized) {
jobKtAsync = GlobalScope.launch(Dispatchers.Main) {
try {
val async1 = async { api.listReposKt("TonyDash")}val async2 = async { api.listReposKt("TonyDash") }
textView.text = "${async1.await()[0].name} requestByKtAsync ${async2.await()[0].name}"
}catch(e:Exception){ textView.text = e.message ? :"error"}}}}Copy the code
override fun onDestroy(a) {
super.onDestroy()
if (::jobKt.isInitialized){
jobKt.cancel()
}
if (::jobKtAsync.isInitialized){
jobKtAsync.cancel()
}
}
Copy the code
CoroutineScope
We can create a globally managed coroutine, and when onDestroy is used, we can use a unified elimination coroutine.
class PracticeActivity2 : AppCompatActivity() {...private val mainScope = MainScope()
...
}
Copy the code
private fun requestByKtAsync(a){
if (::retrofit.isInitialized && ::api.isInitialized) {
mainScope.launch(Dispatchers.Main) {
try {
val async1 = async { api.listReposKt("TonyDash")}val async2 = async { api.listReposKt("TonyDash") }
textView.text = "${async1.await()[0].name} requestByKtAsync ${async2.await()[0].name}"
}catch(e:Exception){ textView.text = e.message ? :"error"}}}}Copy the code
override fun onDestroy(a) {
super.onDestroy()
mainScope.cancel()
}
Copy the code
In addition, we can even use Jetpack to make it easier to use, taking advantage of KTX’s extended attributes.
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
Copy the code
private fun requestByKt(a) {
if (::retrofit.isInitialized && ::api.isInitialized) {
lifecycleScope.launch(Dispatchers.Main) {
try {
val repos = api.listReposKt("TonyDash")
textView.text = "KT${repos[0].name}"
} catch(e: Exception) { textView.text = e.message ? :"error"}}}}Copy the code
Using lifecycleScope to launch the coroutine, we don’t even need to manually fetch the coroutine from onDestroy, because Kotlin does it for us. In addition, KTX has some convenient API methods for us to use, such as launchWhenCreated, launchWhenResumed, launchWhenStarted, etc.
Conclusion:
What are coroutines and threads? For Kotlin for Android, coroutines are a threaded framework for handling concurrent tasks. What are the pros and cons of coroutines and threads? Advantages: easy to use, concise, clear logic without callbacks, and automatic thread switching. Cons: Relatively new, needs to learn cost