- 协程
- 如何使用协程?
- 1.启动协程的方式
- launch 启动一个协程,返回一个Job,可用来取消协程
- async 启动一个带返回结果的协程Deferred,通过Deferred.await()获取结果;
有异常并不会直接抛出,只会在调用 await 的时候抛出
- public interface Deferred : Job {}
- withContext 启动一个协程,传入CoroutineContext改变协程运行的上下文 - 2.协程运行的线程
- Dispatchers.Main -Android中的主线程
- Dispatchers.IO -针对磁盘和网络IO进行了优化,适合IO密集型的任务,比如:读写文件,操作数据库以及网络请求
- Dispatchers.Default -适合CPU密集型的任务,比如解析JSON文件,排序一个较大的list
- Dispatchers.Unconfined - 3.协程执行的函数或者方法使用suspend修饰
- 开启协程,示例代码:
fun main() {
val job = GlobalScope.launch { // launch a new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // wait until child coroutine completes
}
- 协程的取消
- 调用cancel()或者job.cancelAndJoin()方法取消协程,运行中的协程任务并不会立即结束,需要配合Job的【isActive】属性判断。
- 正常情况下,父协程被取消,它的子协程都会被取消。
- 同一个CoroutineScope作用域取消了,其下面所有的子协程也会被取消
- 这种情况下,子协程不会被取消
try {
repeat(10) {
Log.d(TAG, "test: $it")
delay(500L)
}
} finally {
withContext(NonCancellable) {
Log.d(TAG, "test: withContext NonCancellable")
}
launch(NonCancellable) {
Log.d(TAG, "test: launch NonCancellable")
}
}
delay(1300L)
launch.cancel()
-------------------------------
结果如下:
test: 0
test: 1
test: 2
test: withContext NonCancellable
test: launch NonCancellable
- 超时取消
- withTimeout(2000L) 取消失败会抛出异常信息:kotlinx.coroutines.TimeoutCancellationException
- withTimeoutOrNull(2000L) 取消失败会返回 null
- SupervisorJob —— Job是有父子关系的,如果存在多个子job,任一个子Job失败了,父Job也就会失败。
SupervisorJob是一个特殊的Job,里面的子Job不会相互影响,其中一个子Job失败了,不影响其他子Job的执行。 - 协程作用域?
- GlobalScope 单例的scope
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
- MainScope()
@Suppress(“FunctionName”)
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
作用域嵌套的情况,MainScope作用域下开启的协程不会被取消
val scope = GlobalScope.launch {
MainScope.async {
Log.d(TAG, "async。。。。")
}
}
scope.cancel()
- 协程间数据同步
- 使用线程安全的数据类型,如:AtomicInteger。volatile在协程间不能保证数据的一致性
- Mutex 类似于线程中使用的 synchronized or ReentrantLock
- Mutex.withLock{ … }
public suspend inline fun <T> Mutex.withLock(owner: Any? = null, action: () -> T): T {
lock(owner)
try {
return action()
} finally {
unlock(owner)
}
}
- actor 使用方法有点复杂
- 异常处理 CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
val job = GlobalScope.launch(handler) {
throw AssertionError()
}
- java代码如何使用kotlin的协程?
- java中不能直接启动协程,需要在Kotlin类中使用协程,实现逻辑功能,然后在java代码中调用该类。
- 接口改造示例代码:
- ViewModel 基类 EasyVM.kt
val TAG: String? = EasyVM::class.java.canonicalName
abstract class EasyVM(application: Application) : AndroidViewModel(application) {
private val mainScope = MainScope()
fun launch(
context: CoroutineContext = Dispatchers.Main,
block: suspend CoroutineScope.() -> Unit
) =
mainScope.launch(context) {
try {
block()
} catch (e: Exception) {
Log.e(TAG, "[launch error info]:" + e.message)
}
}
/**
* viewmodel scope async
*/
suspend fun <T> async(
context: CoroutineContext = Dispatchers.Default,
block: suspend CoroutineScope.() -> T
) =
withContext(context) {
val result: T? = try {
block()
} catch (e: Exception) {
Log.e(TAG, "[async error info]:" + e.message)
null
}
result
}
override fun onCleared() {
super.onCleared()
mainScope.cancel()
}
}
- 生成VM对象与之前相同。ViewModelProviders.of(this).get(VM.class);
- 简单封装retrofit网络库 EasyNet.kt
object EasyNet {
val EMPTY_CONFIG = NetConfig()
//全局默认配置
var gConfigMap = LinkedHashMap<String, NetConfig>()
inline fun <reified T> init(config: NetConfig = EMPTY_CONFIG) {
if (T::class.java.canonicalName?.isEmpty()!!) {
throw IllegalArgumentException("illegal argument T error !!!")
}
checkNonnull(config)
gConfigMap[T::class.java.canonicalName!!] = config
}
inline fun <reified T> create(config: NetConfig? = gConfigMap[T::class.java.canonicalName!!]): T {
config?.let {
checkNonnull(config)
return Retrofit.Builder()
.baseUrl(config.baseUrl)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.client(providerClient(config))
.build()
.create(T::class.java)
}
throw IllegalArgumentException("illegal argument error:config must be init!")
}
fun providerClient(config: NetConfig): OkHttpClient =
OkHttpClient.Builder().apply {
addInterceptor(providerHeaderInterceptor(config))
addInterceptor(providerLogInterceptor(config))
config.interceptors.forEach { addInterceptor(it) }
config.dns?.let { dns(it) }
}.build()
private fun providerHeaderInterceptor(config: NetConfig): Interceptor = Interceptor { chain ->
val origin = chain.request()
val newBuilder = origin.newBuilder().headers(Headers.of(config.headers)).build()
chain.proceed(newBuilder)
}
private fun providerLogInterceptor(config: NetConfig): Interceptor =
HttpLoggingInterceptor().apply { level = config.logLevel }
fun checkNonnull(config: NetConfig) {
if (config.baseUrl.isEmpty()) {
throw IllegalArgumentException("illegal argument error:Expected baseUrl scheme 'http' or 'https' but no colon was found!!!")
}
}
}
class NetConfig {
/**
* 请求dns
*/
var dns: Dns? = null
/**
* 请求基地址
*/
var baseUrl = ""
/**
* 拦截器
*/
val interceptors = LinkedList<Interceptor>()
/**
* 头部参数
*/
val headers = LinkedHashMap<String, String>()
/**
* 网络请求日志level
*/
var logLevel = HttpLoggingInterceptor.Level.HEADERS
}
- 协程的支持情况
- retrofit 2.6.0
- LiveData
- Room
- WorkManager - 协程优缺点
- 优点:
- 1.协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
- 2.单线程内就可以实现并发的效果,最大限度地利用cpu
- 3.可以按照同步思维写异步代码,即用同步的逻辑,写由协程调度的回调
- 缺点:
- 1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
- 2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
注意事项:
- 1. runBlocking 是会阻塞主线程的,直到 runBlocking 内部全部子任务执行完毕。减少使用!!!