- 協程
- 如何使用協程?
- 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修飾
- 開啓協程,示例代碼:
- 1.啓動協程的方式
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 內部全部子任務執行完畢。減少使用!!!