协程学习(一)一个最简单的协程例子

从这篇文章开始,就进入了协程的学习阶段了,但是由于协程的学习是一个比较系统性的东西,他有很多需要了解的知识点 ,比如 suspend挂起 协程作用域Scope 协程体 调度器Dispatchers 等,当知识积累到一定程度再去阅读源码可能效果会更好一点,所以从协程这个板块,我打算从最简单的例子说起

下面上代码

kotlin 复制代码
object TsmTest {
    @JvmStatic
    fun main(arge: Array<String>) {
        GlobalScope.launch(Dispatchers.IO) {
            Thread.sleep(100)
            println("--------result----------${Thread.currentThread().name}")
        }
        println("--------方法结束----------")
        Thread.sleep(1000)
    }
}

这个是一个特别简单的方法,可以直接Run ,不必去打包android 就可以直接运行, 调试起来也比较简单

在这里有几个比较重要的知识点

CoroutineScope 作用域

在 android 中非常多的应用场景都有生命周期的这个概念,稍微不注意就会造成内存泄露的麻烦,但是如果去维护生命周期就会造成代码的臃肿和难以阅读,在这种场景下作用域的功能就体现出来了,

下面列举一下比较常用的作用域

1:如果你是在ViewModule 中使用协程可以使用 androidx.lifecycle:lifecycle-viewmodel-ktx 包下面为我们提供的 viewModelScope ,

viewModelScope 的默认调度器是 Dispatchers.Main

kotlin 复制代码
public 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)
        )
    }

2:如果你是在Activity 或者Fragment 中使用可以使用 androidx.lifecycle:lifecycle-runtime-ktx 为我们提供的 lifecycleScope,让协程中的任务自动感知他们所处的环境,当宿主被回收后,任务自动与宿主隔离,

lifecycleScope 的默认调度器是 Dispatchers.Main

kotlin 复制代码
public 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
            }
        }
    }

3:GlobalScope 他的作用域就是 App 的整个生命周期,所以也可以说他没有作用域

GlobalScope 的默认调度器是 Dispatchers.Default

我们协程体中是可以获取到CoroutineScope的,在协程体中可以直接使用原因我们就需要看一下 launch 方法

kotlin 复制代码
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

CoroutineScope.launch 方法一共有3个参数,我们先不管前面2个参数,直接看第三个参数 ,他是一个被 suspend 关键字修饰的 block,同样的这个block 还是 CoroutineScope扩展的一个匿名函数,

根据上面的分析我们就可以将 上面代码改写成

kotlin 复制代码
var job= GlobalScope.launch(block = {
    Thread.sleep(100)
    println("--------result----------${Thread.currentThread().name}")

    this.cancel()
    this.isActive
})

图片中的两个this 代表的是样的含义,都是 CoroutineScope,既然 block 是 CoroutineScope 的匿名扩展方法,我们就可以使用这个 this 来取消或者得到当前协程的结果

关于 CoroutineScope 还有一些知识点,比如异常的传播,取消机制 等等,我们后边继续分析

launch async

GlobalScope.launch 会开启一段协程,但是他会返回一个 Job , 我们可以根据这个job 来获取和操控任务的状态,但是他没有结果,也就是无法通过 job获取结果,但是如果你要结果该如何使用呢. 没错 就是使用 async 方法

GlobalScope.async 会返回一个 Deferred 的对象,我们可以用 Deferred.await 方法来获取 协程的返回结果,需要了解到的是 Deferred 他是 Job 的子类

Dispatchers.IO 调度器

调度器决定了协程体中的代码的执行时所挂载到的线程, 默认是 Dispatchers.Default ,但是这里面有一个问题,那就是 Dispatchers.IO 与 Dispatchers.Default 有什么不同吗,下面就把 源码中 Dispatchers.IO 的注释拿出来

vbnet 复制代码
/**
 * The [CoroutineDispatcher] that is designed for offloading blocking IO tasks to a shared pool of threads.
 *
 * Additional threads in this pool are created and are shutdown on demand.
 * The number of threads used by tasks in this dispatcher is limited by the value of
 * "`kotlinx.coroutines.io.parallelism`" ([IO_PARALLELISM_PROPERTY_NAME]) system property.
 * It defaults to the limit of 64 threads or the number of cores (whichever is larger).
 *
 * Moreover, the maximum configurable number of threads is capped by the
 * `kotlinx.coroutines.scheduler.max.pool.size` system property.
 * If you need a higher number of parallel threads,
 * you should use a custom dispatcher backed by your own thread pool.
 *
 * ### Implementation note
 *
 * This dispatcher shares threads with a [Default][Dispatchers.Default] dispatcher, so using
 * `withContext(Dispatchers.IO) { ... }` does not lead to an actual switching to another thread &mdash;
 * typically execution continues in the same thread.
 * As a result of thread sharing, more than 64 (default parallelism) threads can be created (but not used)
 * during operations over IO dispatcher.
 */
@JvmStatic
public val IO: CoroutineDispatcher = DefaultScheduler.IO

这段话的意思就是 DefaultScheduler.IO 是为了加载一些 IO 任务的共享线程池,他确定了最大线程数, 但是从线程意义与 Dispatchers.Default 上来说是没有区别的,如果当前协程体的调度器是 Dispatchers.Default ,如果你使用 withContext(Dispatchers.IO) ,那么他不会进行线程调度,

用比较通俗的话来讲就是 Dispatchers.Default 是运算密集型, 而 Dispatchers.IO 是任务密集型 ,但是这两个调度器之间不能来回切换,没有什么太大的意义

相关推荐
烬奇小云3 小时前
认识一下Unicorn
android·python·安全·系统安全
顾北川_野15 小时前
Android 进入浏览器下载应用,下载的是bin文件无法安装,应为apk文件
android
CYRUS STUDIO15 小时前
Android 下内联汇编,Android Studio 汇编开发
android·汇编·arm开发·android studio·arm
右手吉他15 小时前
Android ANR分析总结
android
PenguinLetsGo17 小时前
关于 Android15 GKI2407R40 导致梆梆加固软件崩溃
android·linux
杨武博19 小时前
音频格式转换
android·音视频
音视频牛哥21 小时前
Android音视频直播低延迟探究之:WLAN低延迟模式
android·音视频·实时音视频·大牛直播sdk·rtsp播放器·rtmp播放器·android rtmp
ChangYan.21 小时前
CondaError: Run ‘conda init‘ before ‘conda activate‘解决办法
android·conda
二流小码农21 小时前
鸿蒙开发:ForEach中为什么键值生成函数很重要
android·ios·harmonyos
夏非夏1 天前
Android 生成并加载PDF文件
android