史上最详Android版kotlin协程入门进阶实战(四)

kotlin协程在Android中的基础应用

通过前面的三个章节,现在我们已经了解了kotlin协程的基本使用和相关基础知识点。如:

  1. 协程的基础使用方式和基本原理。
  2. CoroutineContext:协程上下文中包含的Element以及下上文的作用,传递。
  3. CoroutineDispatcher:协程调度器的使用
  4. CoroutineStart:协程启动模式在不同模式下的区别
  5. CoroutineScope:协程作用域的分类,以及不同作用域下的异常处理。
  6. 挂起函数以及suspend关键字的作用,以及Continuation的挂起恢复流程。
  7. CoroutineExceptionHandler:协程异常处理,结合supervisorScopeSupervisorJob的使用。

这一章节中,我们将主要讲解kotlin协程在Android中的基础使用。我们先引入相关扩展库组件库:

    implementation "androidx.activity:activity-ktx:1.2.2"
    implementation "androidx.fragment:fragment-ktx:1.3.3"

Android使用kotlin协程

我们在之前的章节中使用协程的方式都是通过runBlocking或者使用GlobalScopelaunchasync方式启动,当然也可以通过创建一个新的CoroutineScope,然后通过launch或者async方式启动一个新的协程。我们在讲解协程异常处理的篇章中就提到,通过SupervisorJobCoroutineExceptionHandler实现了一个和supervisorScope相同的作用域。

private fun testException(){
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Log.d("exceptionHandler", "${coroutineContext[CoroutineName].toString()} 处理异常 :$throwable")
    }
    val supervisorScope = CoroutineScope(SupervisorJob() + exceptionHandler)
    with(supervisorScope) {
        launch{
        }
        //省略...
    }
}

在第一节中我们提到runBlocking它会将常规的阻塞代码连接到一起,主要用于main函数和测试中。而GlobalScope又是一个全局顶级协程,我们在之前的案例中基本都使用这种方式。但是这个协程是在整个应用程序生命周期内运行的,如果我们用GlobalScope启动协程,我们启动一个将会变得极其繁琐,而且需要对于各种引用的处理以及管控异常取消操作。

我们可以先忽略CoroutineExceptionHandler协程异常处理。因为不管是任何方式启动协程,如果不在程上下文中添加CoroutineExceptionHandler,当产生未捕获的异常时都会导致应用崩溃。

那么下面代码会出现什么问题?

private fun start() {
    GlobalScope.launch{
        launch {
            //网络请求1...
            throw  NullPointerException("空指针")
        }
        val result = withContext(Dispatchers.IO) {
            //网络请求2...
            requestData()
            "请求结果"
        }
         btn.text = result
        launch {
            //网络请求3...
        }
    }
}
  • 因为我们的GlobalScope默认使用的是Dispatchers.Default,这会导致我们在非主线程上刷新UI。

  • 子协程产生异常会产生相互干扰。子协程异常取消会导致父协程取消,同时其他子协程也将会被取消。

  • 如果我们这个时候activity或者framgent退出,因为协程是在GlobalScope中运行,所以即使activity或者framgent退出,这个协程还是在运行,这个时候会产生各种泄露问题。同时此协程当执行到刷新操作时,因为我们的界面已经销毁,这个时候执行UI刷新将会产生崩溃。

如果我们要解决上面的问题。我们得这么做:

var job:Job? = null
private fun start() {
    job = GlobalScope.launch(Dispatchers.Main + SupervisorJob()) {
        launch {
            throw  NullPointerException("空指针")
        }
        val result = withContext(Dispatchers.IO) {
            //网络请求...
            "请求结果"
        }
        launch {
            //网络请求3...
        }
        btn.text = result
    }
}

override fun onDestroy() {
    super.onDestroy()
    job?.cancel()
}

我们先需要通过launch启动时加入Dispatchers.Main来保证我们是在主线程刷新UI,同时还需要再GlobalScope.launch的协程上下文中加入SupervisorJob来避免子协程的异常取消会导致整个协程树被终结。 最后我们还得把每次通过GlobalScope启动的Job保存下来,在activity或者framgent退出时调用job.cancel取消整个协程树。这么来一遍感觉还行,但是我们不是写一次啊,每次写的时候会不会感觉超麻烦,甚至怀疑人生。

所以官方在kotlin协程中提供了一个默认在主线程运行的协程:MainScope,我们可以通过它来启动协。

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

我们可以看到MainScope的创建默认就使用了SupervisorJobDispatchers.Main。说明我们可以通过MainScope来处理UI组件刷新。同时由于MainScope采用的是SupervisorJob,所以我们各个子协程中的异常导致的取消操作并不会导致MainScope的取消。这就很好的简化了我们通过GlobalScope去启动一个协程的过程。

private val mainScope = MainScope()
private fun start() {
    mainScope.launch {
        launch {
            throw  NullPointerException("空指针")
        }
        val result = withContext(Dispatchers.IO) {
            //网络请求...
            "请求结果"
        }
        launch {
            //网络请求3...
        }
        btn.text = result
    }
} 
override fun onDestroy() {
    super.onDestroy()
    mainScope.cancel()
}

通过使用MainScope我们是不是省略了很多操作。同时我们也不需要保存每一个通过MainScope启动的Job了,直接在最后销毁的时候调用mainScope.cancel()就能取消所有通过mainScope启动的协程。

这里多提一点:可能这里有的人会想,我使用GlobalScope也不保存启动的Job,直接GlobalScope.cancel不行吗?如果是这样的话,那么恭喜你喜提超级崩溃BUG一个。这里就不扩展了。可以自己动手去试试,毕竟实践出真理。

那可能还有人想,我连创建MainScope都懒得写,而且脑子经常不好使,容易忘记调用mainScope进行cancel操作怎么办。

image.png

官方早就为我们这些懒人想好了解决方案,这个时候我们只需要再集成一个ktx运行库就可以了。

在Activity与Framgent中使用协程

    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"

这个时候我们就可以在activity或者framgent直接使用lifecycleScope进行启动协程。我们看来看看activity中的lifecycleScope实现

public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

我们可以到lifecycleScope它是通过lifecycle得到一个coroutineScope,是一个LifecycleCoroutineScope对象。

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
            }
        }
    }

我们可以看到lifecycleScope采用的和MainScope一样的创建CoroutineScope,同时它又通过结合lifecycle来实现当lifecycle状态处于DESTROYED状态的时候自动关闭所有的协程。

public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
    internal abstract val lifecycle: Lifecycle
    public fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenCreated(block)
    }
    public fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenStarted(block)
    }
    public fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenResumed(block)
    }
}

internal class LifecycleCoroutineScopeImpl(
    override val lifecycle: Lifecycle,
    override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
    init {
        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()
        }
    }
}

同时我们也可以通过launchWhenCreatedlaunchWhenStartedlaunchWhenResumed来启动协程,等到lifecycle处于对应状态时自动触发此处创建的协程。

比如我们可以这么操作:

class MainTestActivity : AppCompatActivity() {
    init {
        lifecycleScope.launchWhenResumed {
            Log.d("init", "在类初始化位置启动协程")
        }
    }

  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  }
}
D/onResume: onResume
D/init: 在类初始化位置启动协程

按照我们正常情况加载顺序,是不是应该init先执行输出?然而在实际情况中它是在等待Activity进入onResume状态以后才执行接着看launchWhenResumed中调用的whenResumed实现。

public suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T {
    return whenStateAtLeast(Lifecycle.State.RESUMED, block)
}

public suspend fun <T> Lifecycle.whenStateAtLeast(
    minState: Lifecycle.State,
    block: suspend CoroutineScope.() -> T
): 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()
    }
}

@MainThread
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) {
            handleDestroy(parentJob)
        } else if (source.lifecycle.currentState < minState) {
            dispatchQueue.pause()
        } else {
            dispatchQueue.resume()
        }
    }

    init {
        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
            handleDestroy(parentJob)
        } else {
            lifecycle.addObserver(observer)
        }
    }

    private inline fun handleDestroy(parentJob: Job) {
        parentJob.cancel()
        finish()
    }

    @MainThread
    fun finish() {
        lifecycle.removeObserver(observer)
        dispatchQueue.finish()
    }
}

我们可以看到,实际上是调用了whenStateAtLeast,同时使用了withContext进行了一个同步操作。然后在LifecycleController中通过添加LifecycleObserver来监听状态,通过lifecycle当前状态来对比我们设定的触发状态,最终决定是否恢复执行。

现在我们对于Activity中的lifecycleScope的创建以及销毁流程有了一个大概的了解。同理Fragment中的lifecycleScope实现原理也是和Activity是一样的,这里我们就不再重复讲解。我们做个简单的实验:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        lifecycleScope.launch {
            delay(2000)
            Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show()
        }
    }
}

这个时候是不是比之前的使用方式简单多了,我们既不用关心创建过程,也不用关心销毁的过程。

这个时候我们就需要提到CoroutineExceptionHandler协程异常处理。通过之前的章节我们知道,启动一个协程以后,如果未在协程上下文中添加CoroutineExceptionHandler情况下,一旦产生了未捕获的异常,那么我们的程序将会崩溃退出。

class MainActivity : AppCompatActivity() {
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Log.d("exceptionHandler", "${coroutineContext[CoroutineName]} $throwable")
    }
    fun load() {
        lifecycleScope.launch(exceptionHandler) {
           //省略...
        }
         lifecycleScope.launch(exceptionHandler) {
           //省略...
        }
         lifecycleScope.launch(exceptionHandler) {
           //省略...
        }
    }
}

当出现这种情况的时候,像笔者这种有严重偷懒情结的人就开始抓狂了。为什么要写这么多遍 lifecycleScope.launch,同时每一次启动都要手动添加CoroutineExceptionHandler。难道就不能再简便一点吗?

image.png

当然可以,首先我们自定义一个异常处理,,我们在实现上只做一个简单的异常日志输出:

/**
 * @param errCode 错误码
 * @param errMsg 简要错误信息
 * @param report 是否需要上报
 */
class GlobalCoroutineExceptionHandler(private val errCode: Int, private val errMsg: String = "", private val report: Boolean = false) : CoroutineExceptionHandler {
    override val key: CoroutineContext.Key<*>
        get() = CoroutineExceptionHandler

    override fun handleException(context: CoroutineContext, exception: Throwable) {
     val msg =  exception.stackTraceToString()
        Log.e("$errCode","GlobalCoroutineExceptionHandler:${msg}")
    }
}

然后我们在通过kotlin的扩展函数来简化我们的使用,去掉重复写lifecycleScope.launchexceptionHandler的过程,我们就定义三个常用方法。

inline fun AppCompatActivity.requestMain(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        crossinline block: suspend CoroutineScope.() -> Unit) {
    lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
        block.invoke(this)
    }
}

inline fun AppCompatActivity.requestIO(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        crossinline block: suspend CoroutineScope.() -> Unit): Job {
    return lifecycleScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
       block.invoke(this)
    }
}

inline fun AppCompatActivity.delayMain(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        delayTime: Long, crossinline block: suspend CoroutineScope.() -> Unit) {
    lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
        withContext(Dispatchers.IO) {
            delay(delayTime)
        }
         block.invoke(this)
    }
}

这个时候我们就可以愉快的在Activity中使用了

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        requestMain {
            delay(2000)
            Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show()
        }
        requestIO {
            loadNetData()
        }
        delayMain(100){
            Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show()
        }
    }

    private suspend fun loadNetData(){
        //网络加载
    }
}

同样的我们再扩展一套基于Fragment的方法

inline fun Fragment.requestMain(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        crossinline block: suspend CoroutineScope.() -> Unit) {
    lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
        block.invoke(this)
    }
}

inline fun Fragment.requestIO(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        crossinline block: suspend CoroutineScope.() -> Unit) {
    lifecycleScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
        block.invoke(this)
    }
}

inline fun Fragment.delayMain(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false, delayTime: Long,
        crossinline block: suspend CoroutineScope.() -> Unit) {
    lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
        withContext(Dispatchers.IO) {
            delay(delayTime)
        }
        block.invoke(this)
    }
}

然后也可以愉快的在Fragment中使用了

class HomeFragment:Fragment() {

    init {
        lifecycleScope.launchWhenCreated {
            Toast.makeText(context,"Fragment创建了", Toast.LENGTH_SHORT).show()
        }
    }
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_main,container,false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        requestMain {
            //...
        }
        requestIO {
            //...
        }
        delayMain(100){
            //...
        }
    }
}

这里需要提一下,可能有的人不太明白,为什么要把ActivityFragment都分开写,他们都是使用的lifecycleScope,我们直接通过lifecycleScope扩展就不可以了吗。假如我们这么扩展:

inline fun LifecycleCoroutineScope.requestMain(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        crossinline block: suspend CoroutineScope.() -> Unit) {
        launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
        block.invoke(this)
    }
}
复制代码

我们以Dailog为例,来启动一个协程:

val dialog = Dialog(this)
dialog.show()
(dialog.context as LifecycleOwner).lifecycleScope.requestMain { 
    withContext(Dispatchers.IO){
        //网络加载
    }
    // 刷新UI
}
dialog.cancel()

那么可能会出现一个什么问题?是的,内存泄露的问题以及错误的引用问题。虽然我的dialog被销毁了,但是我们lifecycleScope并不处于DESTROYED状态,所以我们的协程依然会执行,这个时候我们就会出现内存泄露和崩溃问题。

通过上面的学习,我们已经基本掌握了协程在ActivityFragment中的使用方式。接下来我们讲解在Viewmodel中使用协程。

ViewModel中使用协程

如果我们想和在ActivityFragment中一样的简便、快速的在ViewModel使用协程。那么我们就需要集成下面这个官方的ViewModel扩展库。

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"

ActivityFragment不同的是,在ViewModel我们使用的不是lifecycleScope,而是使用viewModelScope,使用viewModelScope,使用viewModelScope。重要的事情说三遍。

这里一定要注意噢,之前就有好几个人问我为什么在viewmodel里面用不了协程,我开始纳闷半天咋就用不了呢。最后一问结果是ViewModel使用lifecycleScope这样做是不对滴

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)
        )
    }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        coroutineContext.cancel()
    }
}

viewModelScope相比较lifecycleScope实现会稍微简单一点。都是使用的SupervisorJob() + Dispatchers.Main上下文,同时最终的取消操作也类似lifecycleScope,只不过viewModelScope取消是在ViewModel的销毁的时候取消。

final void clear() {
    mCleared = true;
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                closeWithRuntimeException(value);
            }
        }
    }
    onCleared();
}

<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) {
        closeWithRuntimeException(result);
    }
    return result;
}

private static void closeWithRuntimeException(Object obj) {
    if (obj instanceof Closeable) {
        try {
            ((Closeable) obj).close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

同样的通过上面的总结,我们也为ViewModel扩展一套常用的方法

inline fun ViewModel.requestMain(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        crossinline block: suspend CoroutineScope.() -> Unit) {
    viewModelScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
        block.invoke(this)
    }
}

inline fun ViewModel.requestIO(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        crossinline block: suspend CoroutineScope.() -> Unit) {
    viewModelScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
        block.invoke(this)
    }
}

inline fun ViewModel.delayMain(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false, delayTime: Long,
        crossinline block: suspend CoroutineScope.() -> Unit) {
    viewModelScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
        withContext(Dispatchers.IO) {
            delay(delayTime)
        }
        block.invoke(this)
    }
}

然后我们就可以愉快的在ViewModel进行使用协程了。

class MainViewModel:ViewModel() {

    init {
        requestMain {
            Log.d("MainViewModel", "主线程中启动协程")
        }
        requestIO {
            Log.d("MainViewModel", "IO线程中启动协程进行网络加载")
        }
        delayMain(100){
            Log.d("MainViewModel", "主线程中启动协程并延时一定时间")
        }
    }
}

好了,常规使用协程的方式我都已经学会。但是我们在一些环境下如法使用使用lifecycleScopeviewModelScope的时候我们又该怎么办。比如:在ServiceDialogPopWindow以及一些其他的环境中又该如何使用。

其他环境下使用协程

在这些环境中我们可以采用通用的方式进行处理,其实还是根据协程作用域的差异分为两类:

  • 协同作用域:这一类我们就模仿MainScope自定义一个CoroutineScope
  • 主从(监督)作用域:这一类我们直接使用MainScope,然后在此基础上做一些扩展即可。

如果对这两个概念还不理解的,麻烦移步到第二章节里面仔细阅读一遍,这里就不再解释。

我们接下来模仿MainScope创建一个CoroutineScope,它是在主线程下执行,并且它的Job不是SupervisorJob

@Suppress("FunctionName")
public fun NormalScope(): CoroutineScope = CoroutineScope(Dispatchers.Main)

然后我再基于NormalScopeMainScope进行使用。我们就以Service为例来实现。

abstract class BaseService :Service(){
    private val normalScope = NormalScope()

    override fun onDestroy() {
        normalScope.cancel()
        super.onDestroy()
    }

    protected fun requestMain(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        block: suspend CoroutineScope.() -> Unit) {
        normalScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
            block.invoke(this)
        }
    }

    protected fun requestIO(
        errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        block: suspend CoroutineScope.() -> Unit): Job {
        return normalScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
            block.invoke(this)
        }
    }

    protected fun delayMain(
        delayTime: Long,errCode: Int = -1, errMsg: String = "", report: Boolean = false,
        block: suspend CoroutineScope.() -> Unit) {
        normalScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
            withContext(Dispatchers.IO) {
                delay(delayTime)
            }
            block.invoke(this)
        }
    }
}

我们创建一个抽象类BaseService类,然后再定义一些基础使用方法后,我就可以快速的使用了

class MainService : BaseService() {

    override fun onBind(intent: Intent): IBinder?  = null

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        requestIO {
            //网络加载
        }
        return super.onStartCommand(intent, flags, startId)
    }
}

同理在DialogPopWindow以及一些其他的环境中可以依照此方法,定义符合我们自己需求的CoroutineScope一定要记得不要跨域使用,以及及时的关闭协程。

又到了文章末尾,在此章节中我们已经了解了协程结合ActivityFragmentLifecycleViewmodel的基础使用,以及如何简单的自定义一个协程,如果还有不清楚的地方,可在下方留言。


如果你想学习kotlin又缺少学习资料,我正好薅到这本谷歌内部大佬根据实战编写的Kotlin宝典,从入门到精通,教程通俗易懂,实例丰富,既有基础知识,也有进阶技能,能够帮助读者快速入门,是你学习Kotlin的葵花宝典,快收藏起来!!!

今天分享的一共分为两部分:【字节跳动厂内部超高质量Kotlin笔记】、【谷歌大佬编写高级Kotlin强化实战(附Demo)】。

一、字节跳动厂内部超高质量Kotlin笔记

首先目录乘上:

1.准备开始

主要内容:基本语法、习惯用语、编码风格

2.基础

主要内容:基本类型、包、控制流、返回与跳转

3.类和对象

主要内容:类和继承、属性和字段、接口、可见性修饰词、扩展、数据对象、泛型、嵌套类、枚举类、对象表达式和声明、代理模式、代理属性

4.函数和lambda表达式

主要内容:函数、高阶函数与 lambda 表达式

5.其它

主要内容:多重申明,Ranges,类型检查和自动转换,This表达式,等式,运算符重载,空安全,异常,注解,反射,动态类型

6.互用性

主要内容:动态类型

7.工具

主要内容:使用Maven、使用 Ant、使用 Gradle、使用Griffon

8.FAQ

主要内容:与java对比、与Scala对比

基础知识掌握之后就是靠实战提升了!

二、谷歌大佬编写高级Kotlin强化实战(附Demo)

照样目录乘上:

第一章 Kotlin入门教程

  • Kotlin 概述
  • Kotlin 与 Java 比较
  • 巧用 Android Studio
  • 认识 Kotlin 基本类型
  • 走进 Kotlin 的数组
  • 走进 Kotlin 的集合
  • 集合问题
  • 完整代码
  • 基础语法

第二章 Kotlin 实战避坑指南

  • 方法入参是常量,不可修改
  • 不要 Companion 、INSTANCE ?
  • Java 重载,在 Kotlin 中怎么巧妙过渡一下?
  • Kotlin 中的判空姿势
  • Kotlin 复写 Java 父类中的方法
  • Kotlin “狠”起来,连TODO 都不放过!
  • is、as` 中的坑
  • Kotlin 中的 Property 的理解
  • also 关键字
  • takeIf 关键字
  • takeIf 关键字
  • 单例模式的写法

第三章 项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始
  • Kotlin 写 Gradle 脚本是一种什么体验?
  • Kotlin 编程的三重境界
  • Kotlin 高阶函数
  • Kotlin 泛型
  • Kotlin 扩展
  • Kotlin 委托
  • 协程“不为人知”的调试技巧
  • 图解协程:suspend

有需要的朋友直接点击此处免费获取完整文档。

おすすめ

転載: blog.csdn.net/m0_57081622/article/details/116504013