kotlin 協程(Coroutine)

  • 協程
  • 如何使用協程?
    • 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 內部全部子任務執行完畢。減少使用!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章