协程基于线程,是轻量级的线程
coroutine = cooperation+routine
难度在哪里?
1,java中不曾出现,新概念
2,概念不清晰
3,Kotlin基础不扎实
4,多线程基础太薄弱
1,处理耗时任务,这种任务常常会阻塞主线程
2,保证主线程安全,确保安全的从主线程调用任何suspend函数(挂起函数)
异步任务已经过时,google建议我们使用协程取代异步任务。
android studio基本上帮我们创建好了,我们不需要添加什么
先来个界面,只有一个TextVIew和一个Button,代码就不贴了。点击按钮访问网络,结果显示到TextView上。我们先用异步任务实现。
增加retrofit的支持
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
增加访问网络的权限
package com.example.testkotlin1030.api
import android.util.Log
import okhttp3.OkHttpClient
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query
//lazy () 是一个接收一个lambda表达式,并返回一个Lazy 实例的函数。
// 返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果,
// 后续调用 get() 只是返回记录的结果。 默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):
// 该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,
// 那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。
// 而如果你确定初始化将总是发生在单个线程,那么你可以使用 LazyThreadSafetyMode.NONE 模式,
// 它不会有任何线程安全的保证以及相关的开销。
val userServiceApi: UserServiceApi by lazy {
val retrofit: Retrofit = Retrofit.Builder()
.client(OkHttpClient.Builder().addInterceptor {
it.proceed(it.request()).apply {
Log.d("wangxuegang", "request${code()}")
}
}.build())
//我自己搭建的服务器,返回name和address
.baseUrl("http://192.168.1.4:8080/myserver")
.addConverterFactory(MoshiConverterFactory.create())
.build()
retrofit.create(UserServiceApi::class.java)
}
interface UserServiceApi {
@GET("user")
fun loadUser(@Query("name") name:String): Call
}
package com.example.testkotlin1030.api
//数据类,默认实现了toString()
//支持运算符重载,例如两个user对象相加,就是把user属性相加
data class User(var name:String,var address:String)
异步任务的写法
package com.example.testkotlin1030
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import com.example.testkotlin1030.api.User
import com.example.testkotlin1030.api.userServiceApi
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var tvShow : TextView = findViewById(R.id.tvShow)
tvShow.text = "王学岗"
//also 把接收者作为值参传给Lambda,返回接收者对象
var buSubmit : Button = findViewById
问题:更新UI与访问网络(阻塞操作)在不同的方法里,阅读比较困难。人类阅读文章是从上向下陈述式的(串行思维)。
onPostExecute是一个回掉函数,容易出现回调地狱。
首先修改RetrofitServiceApi,协程里只能使用挂起函数
interface UserServiceApi {
//直接返回User对象
@GET("user")
suspend fun loadUser(@Query("name") name:String):User
}
引入协程的依赖
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-RC-native-mt'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0-RC-native-mt'
package com.example.testkotlin1030
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import com.example.testkotlin1030.api.User
import com.example.testkotlin1030.api.userServiceApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import retrofit2.Call
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var tvShow : TextView = findViewById(R.id.tvShow)
tvShow.text = "王学岗"
//also 把接收者作为值参传给Lambda,返回接收者对象
var buSubmit : Button = findViewById
协程让异步逻辑同步化,杜绝回调监狱,协程最核心的特点是,函数或者一段程序能够挂起,稍后再挂起的位置恢复。
常规函数的操作包括invoke和return(调用和返回),协程新增加了suspend和resume(挂起与恢复)
suspend:挂起或暂停,用于暂停执行当前协程,并保存所有局部变量
resume:用于让已暂停的协程,从其暂停处继续执行
重构下代码
package com.example.testkotlin1030
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import com.example.testkotlin1030.api.User
import com.example.testkotlin1030.api.userServiceApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import retrofit2.Call
class MainActivity : AppCompatActivity() {
private var tvShow:TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvShow = findViewById(R.id.tvShow)
tvShow?.text = "王学岗"
//also 把接收者作为值参传给Lambda,返回接收者对象
var buSubmit: Button = findViewById
当执行getUser()函数的时候,其会出现在主线程的堆栈当中。
执行get()的时候,getUser会被挂起,get()函数来到堆栈当中。
get()函数继续执行的时候,它也会被挂起。get()被挂起之后就会有异步任务。启动异步协程,做些耗时操作。
异步任务执行完后,get()函数返回到堆栈中。至此get()函数执行完毕,从堆栈中移除。
继续执行show(user)方法,getUser函数也会恢复,回到堆栈中。至此其执行完毕。
注意:1,使用suspend关键字修饰的函数叫做挂起函数,挂起函数只能在协程体内或其他挂起函数内调用。
2,withContext必须在协程或者suspend函数中调用,否则会报错。它必须显示指定代码块所运行的线程,它会阻塞当前上下文线程,有返回值,会返回代码块的最后一行的值。如果最后一行是无返回值的函数,则它的返回值是最后一行函数的返回值
阻塞
package com.example.testkotlin1030
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private var tvShow:TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvShow = findViewById(R.id.tvShow)
tvShow?.text = "王学岗"
//also 把接收者作为值参传给Lambda,返回接收者对象
var buSubmit: Button = findViewById
可以看到12秒后会打印输出main—after delay(说明在主线程),界面中的按钮按下会立刻抬起。多次点击也没有任何问题,12秒后会多次打印,因为启动了多个协程,每个协程打印了
阻塞
package com.example.testkotlin1030
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private var tvShow:TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvShow = findViewById(R.id.tvShow)
tvShow?.text = "王学岗"
//also 把接收者作为值参传给Lambda,返回接收者对象
var buSubmit: Button = findViewById
12秒后打印输出 main—after sleep(同样在主线程),但是按钮不会马上弹起,12秒后才会弹起(因为阻塞了主线程)。
多次点击会出现
android在主线程中更新UI,16毫秒刷新一次界面,如果主线程干了很多事,主线程就不能刷新,会出现漏帧现象(Skipped 720 frames)。
kotlin协程实现分为两个层次
基础设施层和业务框架层。前者是标准库的协程API,主要对协程提供了概念和语义上最基本的支持。后者,是协程的上层框架支持。GlobalScope,launch,delay都属于业务框架层
下面我们看下基础设施层的API
package com.example.testkotlin1030
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.coroutines.*
class MainActivity : AppCompatActivity() {
private var tvShow: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//协程的挂起点就是通过Continuation中的协程上下文保存起来的。
val continuation:Continuation =
//suspend(挂起函数)就是协程体,耗时操作可以写在里面
suspend {
5
//createCoroutine需要传入接口(我们这里传入对象表达式),协程体返回的是Int类型,所以这里的泛型就是Int
}.createCoroutine(object : Continuation{
//协程上下文,
override val context: CoroutineContext = EmptyCoroutineContext
override fun resumeWith(result: Result) {
//协程执行完后,执行结果返回这里,这个resumewith实际上就是回调
Log.i("zhang_xin","$result")
}
})
//上面的代码只是创建了协程,并未执启动协程。下面这句代码启动协程
continuation.resume(Unit)
}
}
打印输出I/zhang_xin: Success(5)
调度器属于基础设施层
所有协程必须在调度器中运行,即使它们在主线程运行也是如此
当某个协程任务丢失,无法追踪,会导致内存,CPU,磁盘等资源浪费,甚至发送一个无用的网络请求,这种情况称为任务泄露。
比如:在Activity中发送网络请求,响应未完成,这时快速按下返回键。Activity虽然被销毁了,但是占用内存的网络请求还在。
为了避免协程任务泄露,Kotlin引入了结构化并发机制。
结构化并发可以做到:
取消任务(当某项任务不再需要时取消它)
追踪任务(当任务执行时,追踪它)
发出错误信号(当协程失败时候,发出错误信号表明有错误发生)
注:CoroutineScope就是协程作用域
GlobalScope:除非进程被杀死,否则协程一直存在。
代码中增加依赖
implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
package com.example.testkotlin1030
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.example.testkotlin1030.api.User
import com.example.testkotlin1030.api.userServiceApi
import kotlinx.coroutines.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.createCoroutine
class MainActivity : AppCompatActivity() {
//MainScope()是一个大写开头的普通函数(普通函数大写开头的是工厂模式),不是一个类
private val mainScope = MainScope()
private var tvShow:TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvShow = findViewById(R.id.tvShow)
findViewById
可以用加延迟点击返回键的方法来验证异常
/*mainScope.launch {
val user = userServiceApi.getUser("xx")
nameTextView?.text = "address:${user?.address}"
*//*try {
delay(10000)
}catch (e:Exception){
e.printStackTrace()
}*//*
}*/
会抛出如下异常
委托方式的写法
package com.example.testkotlin1030
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.example.testkotlin1030.api.User
import com.example.testkotlin1030.api.userServiceApi
import kotlinx.coroutines.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.createCoroutine
/**
* CoroutineScope是一个借口,继承接口要覆盖它的属性
* public val coroutineContext: CoroutineContext
* 但我们现在不覆盖它,MainScope返回了一个CoroutineScope对象,
* 并把该对象的上下文赋值给coroutineContext,MainActivity
* 实际上是继承了MainScope返回的对象
*/
class MainActivity : AppCompatActivity(),CoroutineScope by MainScope() {
private var tvShow:TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvShow = findViewById(R.id.tvShow)
findViewById
首先我们来看下,如何使用ViewModelScope。
1,gradle中增加对dataBinding的支持
dataBinding {
enabled = true
}
2,布局文件
3,代码
package com.dongnaoedu.kotlincoroutine.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dongnaoedu.kotlincoroutine.api.User
import com.dongnaoedu.kotlincoroutine.repository.UserRepository
import kotlinx.coroutines.launch
class MainViewModel() : ViewModel() {
val userLiveData = MutableLiveData()
private val userRepository = UserRepository()
fun getUser(name: String) {
//使用viewModelScope启动协程进行网络请求。
viewModelScope.launch {
userLiveData.value = userRepository.getUser(name)
}
}
}
创建ViewModel
package com.dongnaoedu.kotlincoroutine.repository
import com.dongnaoedu.kotlincoroutine.api.User
import com.dongnaoedu.kotlincoroutine.api.userServiceApi
/**
*
* @author ningchuanqi
* @version V1.0
*/
class UserRepository {
suspend fun getUser(name: String): User {
return userServiceApi.getUser(name)
}
}
UserRepository 既可以在网络请求,也可以在数据库中请求
package com.dongnaoedu.kotlincoroutine.activity
import android.annotation.SuppressLint
import android.os.AsyncTask
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.dongnaoedu.kotlincoroutine.R
import com.dongnaoedu.kotlincoroutine.api.User
import com.dongnaoedu.kotlincoroutine.api.userServiceApi
import com.dongnaoedu.kotlincoroutine.databinding.ActivityMainBinding
import com.dongnaoedu.kotlincoroutine.viewmodel.MainViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
*
* @author ningchuanqi
* @version V1.0
*/
class MainActivity07 : AppCompatActivity() {
private val mainViewModel: MainViewModel by viewModels()
@SuppressLint("StaticFieldLeak","SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
binding.viewModel = mainViewModel
binding.lifecycleOwner = this//即当前的Activity
binding.submitButton.setOnClickListener {
mainViewModel.getUser("xxx")
}
}
}