zoukankan      html  css  js  c++  java
  • android中的协程

    参考

    https://developer.android.com/kotlin/coroutines

    https://www.bennyhuo.com/2019/05/27/coroutine-android/

    https://juejin.im/post/6854573211418361864

    导入依赖

    除了要导入kotlin协程依赖外,还需要导入Android主线程协程库:

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'

    Retrofit的支持

    https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter

    implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'

    此项目已经被deprecated了,因为在Retrofit 2.6.0+已经支持kotlin的suspend了。

    MainScope

    public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

    通过MainScope()方法可以创建一个主线程的CoroutineScope,而主线程的dispatcher在Android中是用handler实现的,那么就可以使用协程的相关方法了,比如launch等。

    val mainScope = MainScope()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        launchButton.setOnClickListener {
            mainScope.launch {
                log(1)
                textView.text = async(Dispatchers.IO) {
                    log(2)
                    delay(1000)
                    log(3)
                    "Hello1111"
                }.await()
                log(4)
            }
        }
    }
    
    
    override fun onDestroy() {
        super.onDestroy()
        mainScope.cancel()
    }

    记得在onDestory中对这个mainScope进行cancel,防止内存泄露。

    通过委托来实现

    让activity实现CoroutineScope 接口,再by MainScope()

    abstract class ScopedActivity: Activity(), CoroutineScope by MainScope(){
        override fun onDestroy() {
            super.onDestroy()
            cancel()
        }
    }

    将 Kotlin 协程与Architecture组件一起使用

    https://developer.android.google.cn/topic/libraries/architecture/coroutines

    除了MainScope()外,Android官方还提供了几个Architecture组件的协程/suspend的支持。

    lifecycleScope

    lifecycle可能是最常用到的一个架构组件,android官方提供了个kotlin协程的支持库

        dependencies {
            implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
        }

    然后我们就可以直接在activity/fragment中直接通过lifecycleScope来在合适的生命周期启动协程,并在activity/fragment销毁时也自动取消lifecycleScope中启动的协程。

    lifecycleScope是LifecycleCoroutineScope的对象,在LifecycleCoroutineScope定义了几个便捷的方法,让我们在合适的生命周期时机运行代码:

    abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
        internal abstract val lifecycle: Lifecycle
    
        /**
         * Launches and runs the given block when the [Lifecycle] controlling this
         * [LifecycleCoroutineScope] is at least in [Lifecycle.State.CREATED] state.
         *
         * The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
         * @see Lifecycle.whenCreated
         * @see Lifecycle.coroutineScope
         */
        fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
            lifecycle.whenCreated(block)
        }
    
        /**
         * Launches and runs the given block when the [Lifecycle] controlling this
         * [LifecycleCoroutineScope] is at least in [Lifecycle.State.STARTED] state.
         *
         * The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
         * @see Lifecycle.whenStarted
         * @see Lifecycle.coroutineScope
         */
    
        fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
            lifecycle.whenStarted(block)
        }
    
        /**
         * Launches and runs the given block when the [Lifecycle] controlling this
         * [LifecycleCoroutineScope] is at least in [Lifecycle.State.RESUMED] state.
         *
         * The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
         * @see Lifecycle.whenResumed
         * @see Lifecycle.coroutineScope
         */
        fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
            lifecycle.whenResumed(block)
        }
    }

    来跟一下源码

    val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
        get() = lifecycle.coroutineScope
    val Lifecycle.coroutineScope: LifecycleCoroutineScope
        get() {
            while (true) {
                val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
                if (existing != null) {
                    return existing
                }
                val newScope = LifecycleCoroutineScopeImpl(
                    this,
                    SupervisorJob() + Dispatchers.Main.immediate
                )
                if (mInternalScopeRef.compareAndSet(null, newScope)) {
                    newScope.register()
                    return newScope
                }
            }
        }

    当创建完newScope 后就会调用其register()进行添加生命周期的观察者。

    internal class LifecycleCoroutineScopeImpl(
        override val lifecycle: Lifecycle,
        override val coroutineContext: CoroutineContext
    ) : LifecycleCoroutineScope(), LifecycleEventObserver {
        init {
            // in case we are initialized on a non-main thread, make a best effort check before
            // we return the scope. This is not sync but if developer is launching on a non-main
            // dispatcher, they cannot be 100% sure anyways.
            if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
                coroutineContext.cancel()
            }
        }
    
        fun register() {
            launch(Dispatchers.Main.immediate) {
                if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
                    lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
                } else {
                    coroutineContext.cancel()
                }
            }
        }
    
        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
            if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
                lifecycle.removeObserver(this)
                coroutineContext.cancel()
            }
        }
    }

    当生命周期发生变化时会判断当前状态是否已经DESTROYED,是的话就把协程job取消。

    扩展函数

    除了上边的方法外,还有几个扩展函数能达到上边方法的效果。

    suspend fun <T> LifecycleOwner.whenCreated(block: suspend CoroutineScope.() -> T): T =
        lifecycle.whenCreated(block)
    
    /**
     * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.CREATED] state.
     *
     * @see Lifecycle.whenStateAtLeast for details
     */
    suspend fun <T> Lifecycle.whenCreated(block: suspend CoroutineScope.() -> T): T {
        return whenStateAtLeast(Lifecycle.State.CREATED, block)
    }
    
    /**
     * Runs the given block when the [LifecycleOwner]'s [Lifecycle] is at least in
     * [Lifecycle.State.STARTED] state.
     *
     * @see Lifecycle.whenStateAtLeast for details
     */
    suspend fun <T> LifecycleOwner.whenStarted(block: suspend CoroutineScope.() -> T): T =
        lifecycle.whenStarted(block)
    
    /**
     * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.STARTED] state.
     *
     * @see Lifecycle.whenStateAtLeast for details
     */
    suspend fun <T> Lifecycle.whenStarted(block: suspend CoroutineScope.() -> T): T {
        return whenStateAtLeast(Lifecycle.State.STARTED, block)
    }
    
    /**
     * Runs the given block when the [LifecycleOwner]'s [Lifecycle] is at least in
     * [Lifecycle.State.RESUMED] state.
     *
     * @see Lifecycle.whenStateAtLeast for details
     */
    suspend fun <T> LifecycleOwner.whenResumed(block: suspend CoroutineScope.() -> T): T =
        lifecycle.whenResumed(block)
    
    /**
     * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.RESUMED] state.
     *
     * @see Lifecycle.whenStateAtLeast for details
     */
    suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T {
        return whenStateAtLeast(Lifecycle.State.RESUMED, block)
    }

    上边的方法都调用到了一个方法whenStateAtLeast,看下这个方法的实现:

    suspend fun <T> Lifecycle.whenStateAtLeast(
        minState: Lifecycle.State,
        block: suspend CoroutineScope.() -> T
    ) = withContext(Dispatchers.Main.immediate) {
        val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")
        val dispatcher = PausingDispatcher()
        val controller =
            LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
        try {
            withContext(dispatcher, block)
        } finally {
            controller.finish()
        }
    }
    internal class LifecycleController(
        private val lifecycle: Lifecycle,
        private val minState: Lifecycle.State,
        private val dispatchQueue: DispatchQueue,
        parentJob: Job
    ) {
        private val observer = LifecycleEventObserver { source, _ ->
            if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
                // cancel job before resuming remaining coroutines so that they run in cancelled
                // state
                handleDestroy(parentJob)
            } else if (source.lifecycle.currentState < minState) {
                dispatchQueue.pause()
            } else {
                dispatchQueue.resume()
            }
        }
    
        init {
            // If Lifecycle is already destroyed (e.g. developer leaked the lifecycle), we won't get
            // an event callback so we need to check for it before registering
            // see: b/128749497 for details.
            if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
                handleDestroy(parentJob)
            } else {
                lifecycle.addObserver(observer)
            }
        }
    
        @Suppress("NOTHING_TO_INLINE") // avoid unnecessary method
        private inline fun handleDestroy(parentJob: Job) {
            parentJob.cancel()
            finish()
        }
    
        /**
         * Removes the observer and also marks the [DispatchQueue] as finished so that any remaining
         * runnables can be executed.
         */
        @MainThread
        fun finish() {
            lifecycle.removeObserver(observer)
            dispatchQueue.finish()
        }
    }

    可以看到当执行一次完成后,就直接结束。也就是说当在onResume时执行一段代码,然后onPause再onResume时是不会再执行一次的。

    viewModelScope

    在viewModel和Activity/fragment的生命周期是不一致的,所以是不能直接使用lifecycleScope的,其实是可以使用MainScope的,只不过要进行手动管理取消。

    所以Android官方又贴心的为我们开发了一个viewModel的协程支持库。

        dependencies {
            implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
        }

    然后在viewModel中就可以通过viewModelScope调用协程相关方法来启动协程,比如launch。

    例如,以下 viewModelScope会启动一个协程,用于在后台线程中发出网络请求。该库会处理所有设置和相应的范围清除:

    class MainViewModel : ViewModel() {
        // Make a network request without blocking the UI thread
        private fun makeNetworkRequest() {
            // launch a coroutine in viewModelScope
            viewModelScope.launch  {
                remoteApi.slowFetch()
                ...
            }
        }
    
        // No need to override onCleared()
    }

    源码

    val ViewModel.viewModelScope: CoroutineScope
            get() {
                val scope: CoroutineScope? = this.getTag(JOB_KEY)
                if (scope != null) {
                    return scope
                }
                return setTagIfAbsent(JOB_KEY,
                    CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
            }
    internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
        override val coroutineContext: CoroutineContext = context
    
        override fun close() {
            coroutineContext.cancel()
        }
    }

    官方说viewModelScope会自动的取消viewModelScope中的协程,那么官方是怎么实现的呢?

    还得回到viewModel中:

    private final Map<String, Object> mBagOfTags = new HashMap<>();
    private volatile boolean mCleared = false;
    
    <T> T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {
            // It is possible that we'll call close() multiple times on the same object, but
            // Closeable interface requires close method to be idempotent:
            // "if the stream is already closed then invoking this method has no effect." (c)
            closeWithRuntimeException(result);
        }
        return result;
    }
    
    @MainThread
    final void clear() {
        mCleared = true;
        // Since clear() is final, this method is still called on mock objects
        // and in those cases, mBagOfTags is null. It'll always be empty though
        // because setTagIfAbsent and getTag are not final so we can skip
        // clearing it
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }
    
    protected void onCleared() {
    }
    
    private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    可以看到当viewmodel被clear时,会先把mBagOfTags 这个map中的值一个个的取出来去close(如果值是Closeable的话),

    而在viewModelScope的定义处会创建一个CloseableCoroutineScope加入到mBagOfTags 中,而CloseableCoroutineScope是实现了Closeable接口的,

    所以当viewmodel被clear时,viewModelScope启动的协程会自动cancel。

    将协程与 LiveData 一起使用

    这个不常使用。

    使用 LiveData 时,您可能需要异步计算值。例如,您可能需要检索用户的偏好设置并将其传送给界面。在这些情况下,LiveData KTX 可提供一个 liveData 构建器函数,该函数会调用 suspend 函数,并将结果作为 LiveData 对象传送。

    要使用此模块,请将以下内容添加到应用的 build.gradle 文件中:

        dependencies {
            implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
        }

    然后就可以调用liveData函数来异步计算值。

    当 LiveData 变为非活动状态onInactive()时,代码块会在可配置的超时过后自动取消。如果代码块在完成前取消,则会在 LiveData 再次变为活动状态onActive()后重启;如果在上次运行中成功完成,则不会重启。请注意,代码块只有在自动取消的情况下才会重启。如果代码块由于任何其他原因(例如,抛出 CancelationException)而取消,则不会重启。

    在以下示例中,loadUser() 是在其他地方声明的 suspend 函数。 您可以使用 liveData 构建器函数异步调用 loadUser(),然后使用 emit() 来发出结果:

    val user: LiveData<User> = liveData {
        val data = database.loadUser() // loadUser is a suspend function.
        emit(data)
    }

    源码

    fun <T> liveData(
        context: CoroutineContext = EmptyCoroutineContext,
        timeoutInMs: Long = DEFAULT_TIMEOUT,
        @BuilderInference block: suspend LiveDataScope<T>.() -> Unit
    ): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)

    其中的lambda会有一个接收者是LiveDataScope,

    同时可以指定一个timeoutInMs,超过指定时间后

    interface LiveDataScope<T> {
        /**
         * Set's the [LiveData]'s value to the given [value]. If you've called [emitSource] previously,
         * calling [emit] will remove that source.
         *
         * Note that this function suspends until the value is set on the [LiveData].
         *
         * @param value The new value for the [LiveData]
         *
         * @see emitSource
         */
        suspend fun emit(value: T)
    
        /**
         * Add the given [LiveData] as a source, similar to [MediatorLiveData.addSource]. Calling this
         * method will remove any source that was yielded before via [emitSource].
         *
         * @param source The [LiveData] instance whose values will be dispatched from the current
         * [LiveData].
         *
         * @see emit
         * @see MediatorLiveData.addSource
         * @see MediatorLiveData.removeSource
         */
        suspend fun emitSource(source: LiveData<T>): DisposableHandle
    
        /**
         * References the current value of the [LiveData].
         *
         * If the block never `emit`ed a value, [latestValue] will be `null`. You can use this
         * value to check what was then latest value `emit`ed by your `block` before it got cancelled.
         *
         * Note that if the block called [emitSource], then `latestValue` will be last value
         * dispatched by the `source` [LiveData].
         */
        val latestValue: T?
    }
  • 相关阅读:
    svn command line tag
    MDbg.exe(.NET Framework 命令行调试程序)
    Microsoft Web Deployment Tool
    sql server CI
    VS 2010 One Click Deployment Issue “Application Validation did not succeed. Unable to continue”
    mshtml
    大厂程序员站错队被架空,只拿着五折工资!苟活和离职,如何选择?
    揭秘!Windows 为什么会蓝屏?微软程序员竟说是这个原因...
    喂!千万别忘了这个C语言知识!(~0 == -1 问题)
    Linux 比 Windows 更好,谁反对?我有13个赞成理由
  • 原文地址:https://www.cnblogs.com/muouren/p/13948988.html
Copyright © 2011-2022 走看看