07_Android协程

手机软件开发 2024-9-18 21:17:59 15 0 来自 中国
Android协程

    本文以网络哀求为例,由浅入深,来阐明协程在Android中的利用方式。后半部门先容一些协程概念。
(1)添加依靠项

    如下:
dependencies {    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")}(2)网络哀求函数

    这是一个同步的壅闭函数,调用它的线程会壅闭。如下:
sealed class Result<out R> {    data class Success<out T>(val data: T) : Result<T>()    data class Error(val exception: Exception) : Result<Nothing>()}class LoginRepository(private val responseParser: LoginResponseParser) {    private const val loginUrl = "https://example.com/login"    // Function that makes the network request, blocking the current thread    fun makeLoginRequest(        jsonBody: String    ): Result<LoginResponse> {        val url = URL(loginUrl)        (url.openConnection() as? HttpURLConnection)?.run {            requestMethod = "OST"            setRequestProperty("Content-Type", "application/json; utf-8")            setRequestProperty("Accept", "application/json")            doOutput = true            outputStream.write(jsonBody.toByteArray())            return Result.Success(responseParser.parse(inputStream))        }        return Result.Error(Exception("Cannot open HttpURLConnection"))    }}(3)触发网络哀求

    用户点击时,触发网络哀求,如下:
class LoginViewModel(    private val loginRepository: LoginRepository): ViewModel() {    fun login(username: String, token: String) {        val jsonBody = "{ username: \"$username\", token: \"$token\"}"        loginRepository.makeLoginRequest(jsonBody)    }}    此时,会壅闭主线程。通过利用协程将它移出主线程,如下:
class LoginViewModel(    private val loginRepository: LoginRepository): ViewModel() {    fun login(username: String, token: String) {        // Create a new coroutine to move the execution off the UI thread        viewModelScope.launch(Dispatchers.IO) {            val jsonBody = "{ username: \"$username\", token: \"$token\"}"            loginRepository.makeLoginRequest(jsonBody)        }    }}    先阐明一下viewModelScope属性。它是ViewModel的扩展属性,在androidx.lifecycle.ViewModelKt中界说的。viewModelScope可以对协程进行管理。Dispatchers.IO阐明该协程是运行在IO线程中的。
    现在根本满足了要求。但是,对于(2)中的makeLoginRequest()方法来讲,假如调用方忘记了把它从主线程中移出,那么就会出标题。固然可以通过注释等方式提示调用者,但总有忘记的可能。下面就是杜绝这种可能的方式。
(4)主线程安全

    假如函数不会壅闭主线程的UI革新,那么该函数是主线程安全的。(2)中的makeLoginRequest()不是主线程安全的,利用它时必须移出主线程。下面的方式将它改为主线程安全的:
class LoginRepository(...) {    ...    suspend fun makeLoginRequest(        jsonBody: String    ): Result<LoginResponse> {        // Move the execution of the coroutine to the I/O dispatcher        return withContext(Dispatchers.IO) {            // Blocking network request code        }    }}    调用方:
class LoginViewModel(    private val loginRepository: LoginRepository): ViewModel() {    fun login(username: String, token: String) {        // Create a new coroutine on the UI thread        viewModelScope.launch {            val jsonBody = "{ username: \"$username\", token: \"$token\"}"            // Make the network call and suspend execution until it finishes            val result = loginRepository.makeLoginRequest(jsonBody)            // Display result of the network request to the user            when (result) {                is Result.Success<LoginResponse> -> // Happy path                else -> // Show error in UI            }        }    }}    由于makeLoginRequest()现在是suspend函数,以是必须在协程中调用。上面的示例通过viewModelScope.launch 启动一个协程来调用它。留意,该协程是运行在主线程中的,但不会壅闭主线程。根据状态机的改变,在得当时间再在主线程中实行when部门。
(5)非常处置惩罚

    通过try-catch来处置惩罚非常部门,如下:
class LoginViewModel(    private val loginRepository: LoginRepository): ViewModel() {    fun makeLoginRequest(username: String, token: String) {        viewModelScope.launch {            val jsonBody = "{ username: \"$username\", token: \"$token\"}"            val result = try {                loginRepository.makeLoginRequest(jsonBody)            } catch(e: Exception) {                Result.Error(Exception("Network request failed"))            }            when (result) {                is Result.Success<LoginResponse> -> // Happy path                else -> // Show error in UI            }        }    }}(6)哀求相应处置惩罚

    上面的内容并没有涉及哀求相应的处置惩罚。但一个正常的哀求,处置惩罚哀求相应是必须的。下面就来展示这一点。
when (result) {    is Result.Success<LoginResponse> -> showData(result.data)    else -> showError()}suspend fun showData(data : LoginResponse){    withContext(Dispatchers.Main){        val name = data.name        nameText.setText(name)    }}suspend fun showError(){    withContext(Dispatchers.Main){        errorView.show()    }}    showData()和showError()中都利用了withContext(Dispatchers.Main)来进行线程切换。这是基于调用它的协程运行在未知线程上思量的。假如可以确定运行在主线程,那么不必要进行切换,suspend修饰符也不再必要。如下:
fun showData(data : LoginResponse){    val name = data.name    nameText.setText(name)}fun showError(){    errorView.show()}    这里并没有利用JetPack Compose UI的更新方式,而是利用了原View体系。固然,利用Compose方式也是可以的,这里只是为了方便。
(7)launch和async

    协程的启动方式有两种:launch和async。launch启动新协程,但不会把效果返回调用方。async启动的协程答应利用await()函数返回效果。通常,launch用于从通例函数启动新协程,而async是在suspend函数或者其他协程内利用。
    一个示比方下:
suspend fun fetchDocs() {                          val result = get("developer.android.com")      show(result)                              }suspend fun fetchTwoDocs() =    coroutineScope {        val deferredOne = async { fetchDoc(1) }        val deferredTwo = async { fetchDoc(2) }        deferredOne.await()        deferredTwo.await()    }(8)协程范围CoroutineScope

     CoroutineScope用于跟踪它利用launch和async创建的协程。可以利用scope.cancel()来取消正在运行的协程。有一些类有自己的Scope,如ViewModel有viewModelScope,Lifecycle有lifecycleScope。自界说一个CoroutineScope也是可以的,示比方下:
class ExampleClass {    // Job and Dispatcher are combined into a CoroutineContext which    // will be discussed shortly    val scope = CoroutineScope(Job() + Dispatchers.Main)    fun exampleMethod() {        // Starts a new coroutine within the scope        scope.launch {            // New coroutine that can call suspend functions            fetchDocs()        }    }    fun cleanUp() {        // Cancel the scope to cancel ongoing coroutines work        scope.cancel()    }}(9)作业Job

     Job是协程的句柄,可以对协程进行管理。示例:
class ExampleClass {    ...    fun exampleMethod() {        // Handle to the coroutine, you can control its lifecycle        val job = scope.launch {            // New coroutine        }        if (...) {            // Cancel the coroutine started above, this doesn't affect the scope            // this coroutine was launched in            job.cancel()        }    }}(10)协程上下文CoroutineContext

     CoroutineContext利用下面几个类来界说协程的举动:

  • Job :控制协程的生命周期;
  • CoroutineDispatcher:将工作分配到得当的线程;
  • CoroutineName:协程名称;
  • CoroutineExceptionHandler:非常处置惩罚。
         当在一个Scope内创建一个协程时,一个Job instance随之分配。其他的CoroutineContext干系元素则从该Scope继承。覆写继承的元素也是可以的,只必要通报一个新的CoroutineContext。如下:
class ExampleClass {    val scope = CoroutineScope(Job() + Dispatchers.Main)    fun exampleMethod() {        // Starts a new coroutine on Dispatchers.Main as it's the scope's default        val job1 = scope.launch {            // New coroutine with CoroutineName = "coroutine" (default)        }        // Starts a new coroutine on Dispatchers.Default        val job2 = scope.launch(Dispatchers.Default + "BackgroundCoroutine") {            // New coroutine with CoroutineName = "BackgroundCoroutine" (overridden)        }    }}    Over !
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 22:32, Processed in 0.141268 second(s), 32 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表