Android 协程使用到原理详解

Fragment中使用viewLifecycleOwner/getActivity/this 观察liveData使用生命周期 如图:当liveData想在fragment里观察的时候,可以使用getActivity/this/getViewLifecycleOwner。 getActivity不必说,就是获取父容器activity的生命周期。 如果传递的LifecycleOwner参数为this,即Fragment时,会收到Android Studio的提醒,要求使用viewLifecycleOwner,或者直接标红,显示错误。如下图所示: 为什么呢? 通过在官网寻找and... 阅读详情

协程是什么

协程是我们在 Android上进行异步编程的推荐解决方案之一,通过挂起和恢复让状态机状态流转实现把层层嵌套的回调代码变成像同步代码那样直观、简洁,协程的出现很好的避免了回调地狱的出现。

所谓挂起,是指挂起协程,而非挂起线程,并且这个操作对线程是非阻塞式的。当线程执行到协程的 suspend 函数的时候,对于线程而言,线程会被回收或者再利用执行其他工作,就像主线程其实是会继续 UI 刷新工作。而对于协程本身,会根据 withContext 传入的 Dispatchers 所指定的线程去执行任务。

关于恢复,当挂起函数执行完毕后,会自动根据 CoroutineContext 切回原来的线程往下执行。

协程怎样集成

dependencies {
    // -----1----
    // Kotlin
    implementation "org.jetbrains.kotlin:kotlin-stdlib:1.5.30"

    // -----2----
    // 协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
    // 协程 Android 支持库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"
  
    // -----3----
    // lifecycle 对于协程的扩展封装
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
}

其中 part 3 主要是对写 view 层的一些库,lifecycle 对于协程的扩展封装在业务开发上非常重要。

下面,介绍一些使用上的一些基本概念

CoroutineScope

CoroutineScope 是指协程作用域,它其实是一个接口,作用是使得协程运行在其范围内

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

执行协程代码块的还有 runBlocking,其只有当内部相同作用域的所有协程都运行结束后,声明在 runBlocking 之后的代码才能执行,即 runBlocking 会阻塞其所在线程,但其内部运行的协程又是非阻塞的,由于对线程有阻塞行为,日常开发中一般不会用到,多用于做单元测试,在此不展开说了。

下面看看官方自带的几种 CoroutineScope

1. GlobalScope

public object GlobalScope : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

public object EmptyCoroutineContext : CoroutineContext, Serializable {
    ...
}

从源码可以看出,GlobalScope 是一个单例,该实例所用的 CoroutineContext 是一个 EmptyCoroutineContext 实例,且 EmptyCoroutineContext 也是一个单例,GlobalScope 对象没有和 view 的生命周期组件相关联,是全局协程作用域,需要自己管理 GlobalScope 所创建的 Coroutine,所以一般而言我们不直接使用 GlobalScope 来创建 Coroutine。

2. Fragment/Activity 的 lifecycleScope

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

// Lifecycle.kt
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            ...
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (...) {
                newScope.register()
                return newScope
            }
        }
    }

// Lifecycle.kt
internal class LifecycleCoroutineScopeImpl(
    override val lifecycle: Lifecycle,
    override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
    ...

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

从上面的 androidx.lifecycle.LifecycleCoroutineScopeImpl#register 和 androidx.lifecycle.LifecycleCoroutineScopeImpl#onStateChanged 我们可以看出 lifecycleScope 使用的生命周期如下

// 开始
override fun onCreate(…)

// 结束
override fun onDestroy()

3. Fragment 的 viewLifecycleScope

Fragment 其实并没有 viewLifecycleScope 的拓展属性,这里的 viewLifecycleScope 是指在 Fragment 对 View 的 LifecycleScope,因为 Fragment 可以没有 View
我们可以给 Fragment 写一个拓展属性

val Fragment.viewLifecycleScope get() = viewLifecycleOwner.lifecycleScope

这里我们可以看看 viewLifecycleOwner 是什么

// Fragment.java

    void performCreateView(...) {
        mViewLifecycleOwner = new FragmentViewLifecycleOwner(this, getViewModelStore());
        mView = onCreateView(inflater, container, savedInstanceState);
        if (mView != null) {
            // Initialize the view lifecycle
            mViewLifecycleOwner.initialize();
        } else {
            if (mViewLifecycleOwner.isInitialized()) {
                throw new IllegalStateException("Called getViewLifecycleOwner() but "
                            + "onCreateView() returned null");
                }
                mViewLifecycleOwner = null;
            }
    }

    public LifecycleOwner getViewLifecycleOwner() {
        if (mViewLifecycleOwner == null) {
            throw new IllegalStateException("Can't access the Fragment View's LifecycleOwner when "
                    + "getView() is null i.e., before onCreateView() or after onDestroyView()");
        }
        return mViewLifecycleOwner;
    }

而 performCreateView 的调用是在创建 View 的时候,可以看出,如果我们没有复写 onCreateView,那么 mView 就会为 null,从而导致 mViewLifecycleOwner 为 null 而复写了就会,所以我们不应该在没有 View 的 Fragment 中使用 viewLifecycleScope,否则在 getViewLifecycleOwner 的时候就会抛异常。所以可以看看在复写 View 时候 viewLifecycleScope 使用的生命周期为

// 开始
override fun onCreateView(…): View?

// 结束
override fun onDestroyView()

4. ViewModel 的 viewModelScope

// ViewModel.kt
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()
    }
}

//----------------------------------------------------

// ViewModel.java

    <T> T setTagIfAbsent(String key, T newValue) {
        ...
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        ...
        return result;
    }
    
    final void clear() {
        ...
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }
    
    
    private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

从上面源码可以看出,viewModelScope 是 lazy 的,调用的时候进行初始化,而 ViewModel#clear 方法是在 ViewModel 销毁的时候调用的,从而最终走到 CloseableCoroutineScope#close,使得协程被 cancel,所以可以得出,viewModelScope 的使用周期在 ViewModel 的生命周期内

Coroutine Builders

Coroutine Builders 是指 kotlinx.coroutines.Builders.kt,其内部有 CoroutineScope 的一些拓展方法等,下面介绍一下 Builders 类中两个重要的拓展方法的作用

1. launch

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
}

context:协程的上下文
start:协程的启动方式,默认值为 CoroutineStart.DEFAULT,即协程会在声明的同时就立即进入等待调度的状态,即可以立即执行的状态,CoroutineStart.LAZY 能实现延迟启动
block:协程的执行体
返回值为 Job,指当前协程任务的句柄

我们在 view 层进行执行协程时候,一般会这样用

viewLifecycleScope.launchWhenStarted {
    ...
}

这其实就是个 launch,我们看看源码

/// Lifecycle.kt
    public fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenStarted(block)
    }
    

2. async

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

async 返回值 Deferred 继承于 Job 接口,其主要是在 Job 的基础上扩展了 await 方法,是返回协程的执行结果,而 launch 返回的 Job 是不携带结果的

public interface Deferred<out T> : Job {
    public suspend fun await(): T  
    public val onAwait: SelectClause1<T>
    public fun getCompleted(): T
    public fun getCompletionExceptionOrNull(): Throwable?
}

CoroutineContext

协程的上下文,使用以下元素集定义协程的行为

  • Job:控制协程的生命周期
  • CoroutineDispatcher:将任务分发给适当的线程
  • CoroutineName:协程的名称,可用于辅助
  • CoroutineExceptionHandler:处理未捕获的异常

1. Job

在源码注释中,Job 有这样的描述
描述 1

State[isActive][isCompleted][isCancelled]
New (optional initial state)falsefalsefalse
Active (default initial state)truefalsefalse
Completing (transient state)truefalsefalse
Cancelling (transient state)falsefalsetrue
Cancelled (final state)falsetruetrue
Completed (final state)falsetruefalse

描述的是一个任务的状态:新创建 (New)、活跃 (Active)、完成中 (Completing)、已完成 (Completed)、取消中 (Cancelling) 和已取消 (Cancelled)。但我们无法直接方位这些状态,可以通过方位 Job 的几个属性:isActive、isCancelled 和 isCompleted

描述 2

                                      wait children
+-----+ start  +--------+ complete   +-------------+  finish  +-----------+
| New | -----> | Active | ---------> | Completing  | -------> | Completed |
+-----+        +--------+            +-------------+          +-----------+
                 |  cancel / fail       |
                 |     +----------------+
                 |     |
                 V     V
             +------------+                           finish  +-----------+
             | Cancelling | --------------------------------> | Cancelled |
             +------------+                                   +-----------+

描述的是状态的流转,举个状态流转例子:当任务创建(New)后,协程处于活跃状态(Active),协程运行出错或者调用 job.cancel()(cancel / fail)都会将当前任务置为取消中 (Cancelling) 状态 (isActive = false, isCancelled = true),当所有的子协程都完成后,协程会进入已取消 (Cancelled) 状态,此时 isCompleted = true。

我们再来认识一下 Job 的几个常用的方法

/// Job.kt
    /**
     * 启动 Coroutine, 当前 Coroutine 还没有执行调用该函数返回 true
     * 如果当前 Coroutine 已经执行或者已经执行完毕,则调用该函数返回 false
     */
    public fun start(): Boolean
    
    /**
     * 取消当前任务,可以指定原因异常信息
     */
    public fun cancel(cause: CancellationException? = null)

    /**
     * 这个 suspend 函数会暂停当前所处的 Coroutine 直到该 Coroutine 执行完成。
     * 所以 join 函数一般用来在另外一个 Coroutine 中等待 job 执行完成后继续执行。
     * 当 Job 执行完成后,job.join 函数恢复,这个时候 job 这个任务已经处于完成状态
     * 调用 job.join 的 Coroutine 还继续处于 activie 状态
     */
    public suspend fun join()

    /**
     * 通过这个函数可以给 Job 设置一个完成通知
     */
    public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
1.1 Deferred

Deferred 继承自 Job,是我们使用 async 创建协程的返回值,我们看看 Deferred 基于 Job 拓展的几个方法

public interface Deferred<out T> : Job {

    /**
     * 用来等待这个 Coroutine 执行完毕并返回结果
     */
    public suspend fun await(): T

    /**
     * 用来获取Coroutine执行的结果
     * 如果Coroutine还没有执行完成则会抛出 IllegalStateException
     * 如果任务被取消了也会抛出对应的异常
     * 所以在执行这个函数前可以通过 isCompleted 来判断一下当前任务是否执行完毕了
     */
    @ExperimentalCoroutinesApi
    public fun getCompleted(): T

    /**
     * 获取已完成状态的 Coroutine 异常信息
     * 如果任务正常执行完成了,则不存在异常信息,返回 null
     */
    @ExperimentalCoroutinesApi
    public fun getCompletionExceptionOrNull(): Throwable?
}

1.2 SupervisorJob
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)

SupervisorJob 是一个顶层函数,里面的子 Job 不相互影响,一个子 Job 失败了,不影响其他子 Job,可以看到有个 parent 入参,如果指定了这个参数,则所返回的 Job 就是参数 parent 的子 Job。

2. CoroutineDispatcher

定义任务的线程

  • Dispatchers.Default
    默认的调度器,适合处理后台计算,是一个CPU密集型任务调度器,使用一个共享的后台线程池来运行里面的任务,任务执行在子线程
  • Dispatchers.IO
    和 Default 共用一个共享的线程池来执行里面的任务,区别在最大并发数不同,用途在阻塞 IO 操作
  • Dispatchers.Unconfined
    未定义线程池,所以执行的时候默认在启动线程,也就是在哪个线程启动就在哪个线程执行
  • Dispatchers.Main
    主线程

协程项目使用场景

1. 回调变协程

以执行多个动画为例,场景是点击某个按钮要切换到其他图标。
首先将 suspendCancellableCoroutine 封装一下,这个方法的作用是将回调变协程,但是我们需要控制其释放

class ContinuationHolder<T>(continuation: CancellableContinuation<T>) {
    var continuation: CancellableContinuation<T>?
        private set

    init {
        this.continuation = continuation
        continuation.invokeOnCancellation {
            this.continuation = null
        }
    }
}

/**
 * 避免continuation泄漏
 */
suspend inline fun <T> suspendCancellableCoroutineRefSafe(
    crossinline block: (ContinuationHolder<T>) -> Unit
): T = suspendCancellableCoroutine {
    val continuationHolder = ContinuationHolder(it)
    block(continuationHolder)
}

接下来就可以使用 suspendCancellableCoroutineRefSafe,看看怎样来将一个回调处理改装成协程

private suspend fun viewScaleAnimator(view: View, duration: Long, vararg values: Float): Boolean {
        return suspendCancellableCoroutineRefSafe { holder ->
            val animatorSet = AnimatorSet()
            animatorSet.play(ObjectAnimator.ofFloat(view, "scaleX", *values))
                .with(ObjectAnimator.ofFloat(view, "scaleY", *values))
            animatorSet.duration = duration
            animatorSet.addListener(object : Animator.AnimatorListener {
                override fun onAnimationStart(animation: Animator?) {
                }

                override fun onAnimationEnd(animation: Animator?) {
                    holder.continuation?.resume(true)
                }

                override fun onAnimationCancel(animation: Animator?) {
                    holder.continuation?.resume(false)
                }

                override fun onAnimationRepeat(animation: Animator?) {
                }
            })
            animatorSet.start()
        }
    }

viewScaleAnimator 方法是将一个缩放动画变成协程的处理,返回动画执行的结果
这样,我们就可以顺序的执行多个动画了

val animator1End = viewScaleAnimator(imageView, 100, 1f, 0.75f)
if (animator1End) {
    imageView.setImageResource(nextImage)
    val animator2End = viewScaleAnimator(imageView, 100, 0.75f, 1f)
    if (animator2End) {
        onAllAnimationEnd.invoke()
    }
}

2. IO 异步处理

以下载了文件后需要解压为例

/**
 * 异步解压文件
 */
suspend fun unZipFolderAsync(zipFileString: String, outPathString: String) = withContext(Dispatchers.IO) {
    unZipFolder(zipFileString, outPathString)
}
internal fun unZipFolder(zipFileString: String, outPathString: String) {
    // FileInputStream、ZipInputStream 等的一些操作
    ...
}

5. 自定义 CoroutineScope

官方的 CoroutineScope 并不能满足所有场景,所以这时候我们可以自定义 CoroutineScope。

class MyRepository {

    private var mScope: CoroutineScope? = null
    
    /**
     * 打开的时候调用
     */
    fun initScope() {
        mScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
    }

    /**
     * 操作
     */
    private fun handle() {
          mScope?.launch {
              ...
          }
    }
    
    /**
     * 退出时候调用
     */
    fun exit() {
        mScope?.cancel()
        mScope = null
        ...
    }
}

协程项目踩坑案例

1. 在 Fragment 中,lifecycleScope 和 viewLifecycleScope 分不清用哪个

viewLifecycleScope 强调的是 View 生命周期内的协程执行范围

  • 在无 UI 的逻辑 fragment 中使用 viewLifecycleScope 会抛异常
  • 在不考虑 View 回收,如横竖屏切换,需要 keep 住一些状态可以使用 lifecycleScope
  • 需要跟 Fragment 生命周期的用 lifecycleScope
  • 跟 View 创建回收时机有关系的用 viewLifecycleScope
  • 大多数情况下使用 viewLifecycleScope

2. CoroutineScope 和 Job 的 cancel 问题

CoroutineScope cancel 了 Job 会跟着 cancel,Job cancel 了 CoroutineScope 未必需要 cancel,CoroutineScope cancel 后 Job 就不活跃了。
Job 的 cancel 场景其中要注意的有:例如我们 collect 一个返回值为 StateFlow 的方法,其实该方法在执行了 trymit 处理完状态后,该协程并未执行完毕,而是始终在等待中,所以我们可以在 collect 内部检测到任务执行完了,就主动将当前 Job cancel 掉,可以避免浪费内存开销。结合上面提到的回调变协程,例子如下

    private fun ...(...) {
        ...
        mAnimatorJob = mAnimatorScope?.launch {
                val animator1End = viewScaleAnimator(imageView, 100, 1f, 0.75f)
                if (animator1End) {
                    imageView.setImageResource(nextImage)
                    val animator2End = viewScaleAnimator(imageView, 100, 0.75f, 1f)
                    if (animator2End) {
                        onAllAnimationEnd.invoke()
                    }
                    // 注意!这里做了 Job 的 cancel
                    mAnimatorJob?.cancel()
                    mAnimatorJob = null
                }
            }
    }

    override fun onDestroy() {
        ...
        // fragment 销毁,未处理完任务也应该销毁
        mAnimatorJob?.cancel()
        mAnimatorJob = null
        mAnimatorScope?.cancel()
        mAnimatorScope = null
    }

挂起和切线程的原理

挂起原理

前面介绍挂起的时候提到挂起操作是非阻塞式的,那么我们来看看协程是怎样做到的。
我们先看看一个小例子

class TestClass {
    suspend fun test1() {
        test2()
    }

    suspend fun test2() {
    }
}

我们看看这个类的字节码

public final class TestClass {
   @Nullable
   public final Object test1(@NotNull Continuation $completion) {
      Object var10000 = this.test2($completion);
      return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;
   }

   @Nullable
   public final Object test2(@NotNull Continuation $completion) {
      return Unit.INSTANCE;
   }
}

可以看到,挂起函数主要用到了 Continuation

public interface Continuation<in T> {
    public val context: CoroutineContext
    public fun resumeWith(result: Result<T>)
}

这么看,实际挂起函数用到了类似于 callback 的逻辑了,resumeWith 相当于 callback 中一个回调函数,其作用是执行接下来要执行的代码,可以理解成在 resumeWith 回调里面继续执行下一步。
而我们在协程外是无法调用的,这里可以看出因为需要传递一个 NotNull 的 Continuation。

切线程原理

接下来讲下切线程,在项目开发中,遇到切线程的比较多的做法 withContext,下面讲述其中原理

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T {  
    return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
        // 创建新的context
        val oldContext = uCont.context
        val newContext = oldContext + context
        ....
        // 使用新的Dispatcher,覆盖外层
        val coroutine = DispatchedCoroutine(newContext, uCont)
        coroutine.initParentJob()
        //DispatchedCoroutine作为了complete传入
        block.startCoroutineCancellable(coroutine, coroutine)
        coroutine.getResult()
    }
}

private class DispatchedCoroutine<in T>(
    context: CoroutineContext,
    uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
	// 在complete时会会回调
    override fun afterCompletion(state: Any?) {
        afterResume(state)
    }

    override fun afterResume(state: Any?) {
        // uCont就是父协程,context 仍是老版 context, 因此可以切换回原来的线程上
        uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
    }
}

传入的新的 CoroutineContext 会覆盖原来所在的 CoroutineContextDispatchedCoroutine 作为 complete: Continuation 传入协程体的创建函数中,因此协程体执行完成后会回调到 afterCompletion 中,DispatchedCoroutine 中传入的 uCont 是父协程,它的拦截器仍是外层的拦截器,因此会切换回原来的线程中

后话

思考:协程设计思想

  • 我认为,协程可以使得一个复杂的操作变得可追踪结果,如果这个复杂操作既涉及到异步操作场景,更为显著,将一个完整的操作变得可追踪,业务逻辑上很清晰。

参考:

移动开发最新【Android】Coroutine协程原理详解_android 协程 jvm,android常见面试题及答案 函数跟匿名函数看起来没啥区别,但是反编译为java后还是能看出点差异如果只是用普通的函数,那么他跟普通java 函数没啥区别。比如fun a()就是对应java方法但是如果通过函数引用(:: a)来用这个函数,那么他并不是直接调用fun a()而是重新生成一个Function0。 阅读详情

相关推荐

使用 Ktor 构建现代 Android 应用的后端服务

在移动互联网时代,Android 应用对后端服务的实时性与性能要求越来越高,而传统的后端框架在一些场景中存在复杂度高、扩展性不足等问题。Ktor 作为 JetBrains 推出的异步 Web 框架,充分利用 Kotlin 协程的优势,能够高效构建轻量、易扩展、易维护的服务端应用。本文将通过丰富的实践案例、代码示例和最佳实践,帮助你深入了解 Ktor 的核心特性与应用场景,并指导你如何将它与现代 Android 开发完美结合。

jiet_h的博客 2731

Android协程

在Looper的死循环中,它会不断地从消息队列中取出消息,并将消息分发给对应的Handler处理。当消息队列为空时,Looper会一直循环等待新的消息到达。在等待消息期间,Looper的死循环会阻塞主线程,因为它会一直占用主线程的执行时间片。这意味着主线程无法继续执行其他任务或响应用户的输入事件或系统事件。只有当主线程长时间占用了 CPU 或其他系统资源,并且长时间无法响应用户输入事件或完成关键操作时,才会触发 ANR 错误。耗时操作本身并不会导致主线程卡死,导致主线程卡死的真正原因是。

m0_61130325的博客 3914

告别XRecyclerView!用Paging3 + Kotlin协程Flow重构你的Android分页列表(附完整Demo)

本文详细介绍了如何从传统的XRecyclerView迁移到现代化的Paging3架构,利用Kotlin协程Flow实现高效的Android分页列表。通过对比分析Paging3的核心优势,如生命周期感知、错误重试机制和协程原生支持,帮助开发者提升开发效率和代码质量。文章还提供了完整的Demo和实战指南,助力开发者快速掌握Jetpack Paging3的应用。

weixin_30919571的博客 426

Android协程简介 -- 基础概念

之前通过一个登录请求,简单了解Android协程的实现方式。相信很多人和我刚开始的时候一样,看的云里雾里的不清楚。这是因为我们对协程缺乏基础的了解。这篇文章,我们就详细介绍一下协程相关的基础知识。协程的概念协程优点协程相关的类、函数结构化并发如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

嘴巴吃糖了 1437

Android中的Coroutine协程原理详解

协程是一个并发方案。也是一种思想。 传统意义上的协程是单线程的,面对io密集型任务他的内存消耗更少,进而效率高。但是面对计算密集型的任务不如多线程并行运算效率高。 不同的语言对于协程都有不同的实现,甚至同一种语言对于不同平台的操作系统都有对应的实现。 我们kotlin语言的协程是 coroutines for jvm的实现方式。底层原理也是利用java 线程。

B1ueSocks的博客 829

kotlin协程-Android实战

都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到Android架构师/技术专家?我总结了这 5大块;我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 PDF(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。Java语言与原理;大厂,小厂。Android面试先看你熟不熟悉Java语言高级UI与自定义view。

2401_84123171的博客 1106

Kotlin 协程实现原理

前言 在文章正式上干货之前,先说一点背景吧;我是 Kotlin 协程官方文档的译者,大家在 Kotlin 中文官网上看到的绝大多数协程的中文官方文档都是我翻译的。 官方文档可以说是比较全面的介绍了协程的使用,但是就我的感觉来说,这些文档分布的比较散乱,甚至还有三篇分布在协程的官方 Github 的 project 中,很多协程的初学者对这些文档的阅读顺序也尝尝感到摸不到头脑。这里我将一共 15...

android开发笔记 1万+

Android 协程深度解析:原理、实践与性能优化

摘要:Kotlin协程作为Android异步编程的核心技术,通过轻量级线程和顺序式编码简化了并发处理。文章系统介绍了协程的基础概念、核心组件、工作原理及在Android中的调度机制,重点分析了结构化并发、异常处理、性能优化等最佳实践,并探讨了协程与Flow、Jetpack组件的整合应用。同时提供了常见面试题解析和进阶话题,帮助开发者全面掌握协程技术,提升异步编程的安全性和效率。

郑伟强dev的专栏 199

Android 协程的基本使用和最简要原理概述

本文介绍了协程的核心概念与使用。协程是轻量级线程管理框架,具有挂起恢复(suspend)、非阻塞并发和结构化并发等特性。文章详细说明了协程的创建方式(launch/async)、线程调度(Dispatchers)、生命周期管理以及状态机实现原理。此外还提供了协程在Android开发中的扩展应用Flow相关文章链接。内容涵盖协程的基础使用到底层机制,适合开发者系统学习协程技术。

赵星海 1312

GD32F103实战:用ADC+DMA实现高精度多通道电压采集与OLED实时显示(附完整工程)

本文详细介绍了如何利用GD32F103的ADC+DMA技术实现高精度多通道电压采集,并通过OLED实时显示数据。文章包含硬件架构设计、ADC配置技巧、DMA传输优化及显示系统实现方案,提供完整工程代码和调试指南,适用于工业传感器和环境监测设备开发。

weixin_42567497的博客 399

Metal ECO实战:如何在不修改底层掩模的情况下修复芯片时序问题

本文深入探讨了Metal ECO技术在芯片设计中的应用,这是一种在不修改底层晶体管掩模的前提下,仅通过调整金属连线和替换预置填充单元来修复时序问题的“微创手术”。文章详细解析了其原理、适用场景、关键操作流程与命令,并分享了实战中的高级技巧与常见陷阱,为面临流片前时序违例的工程师提供了宝贵的修复指南。

wdx0123456的博客 430

Android开发中协程工作原理解析

启动 (launchasync在某个(如) 内,使用调度器 (如) 启动协程。新协程继承作用域的上下文 (Job, 调度器等)。协程代码在Dispatcher指定的线程上开始执行。遇到suspend函数(如网络请求)时:协程状态被保存到其对象。返回,协程挂起,释放当前线程。底层异步库(如 Retrofit Callback)持有这个。被释放的线程可以执行其他任务(其他协程或非协程代码)。网络请求完成。恢复 (resume异步库(在回调线程)调用或。协程库检查协程原本的调度器 (

JMY_2016的博客 1472

Win11显卡驱动安装失败的全面解决方案

Windows 11显卡驱动安装失败是一个比较常见的问题,但通过上述方法,我们可以有效地解决这一问题。方法一通过调整组策略编辑器的驱动程序安装策略,可能解决一些因签名问题导致的安装失败;而方法二则通过重装Windows 11系统,为系统提供一个全新的、干净的环境,从而可能解决更复杂的安装问题。在实际操作中,用户可以根据具体情况选择合适的方法来解决显卡驱动安装失败的问题。

nntxthml的博客 2662

Android学习总结之kotlin协程面试篇

kotlin协程的面试题讲解

2301_80329517的博客 3977

Android车载多屏开发实战:从零搭建同显/异显Demo(附源码)

本文详细介绍了Android车载多屏开发的实战经验,从零搭建同显/异显Demo,涵盖多屏模拟、核心API调用、同显/异显架构实现及性能优化等关键环节。通过C++构建完整开发链路,帮助开发者快速掌握多屏同显和多屏异显技术,提升智能座舱系统的交互体验。附源码供参考。

weixin_29233857的博客 403

芒果YOLO11改进22:检测头Head篇:即插即用|《新颖高效AsDDet检测头》独一无二的结构改进,公开数据集mAP高效涨点,即插即用|检测头新颖改进,性能高效涨点,全网首发独一份

检测头Head篇:即插即用|《新颖高效AsDDet检测头》独一无二的结构改进,公开数据集mAP高效涨点,即插即用|检测头新颖改进,性能高效涨点,全网首发独一份

包括YOLOv5、YOLOv7、YOLOv8等模型改进 746

Android Kotlin 协程详解

什么是协程?要理解协程,就要将它和线程联系起来理解。线程是什么?我想大家都清楚,而协程,它比线程更加轻量级,一个线程上面可以有多个协程。如果我们应用开启一万个线程,可能就崩溃了,但是如果我们开启10万个协程,对应用的性能也不会有太大的影响。协程是可挂起计算的实例,它需要一个代码块运行,并具有类似的生命周期(可以被创建、启动和取消),它不绑定到任何特定的线程,可以在一个线程中挂起其执行,并在另一个线程中恢复,它在完结时可能伴随着某种结果(值或异常)

袁震的博客 2204

【人工智能】提示词工程:让大模型精准响应的核心技术,提示词的基础框架 以及 常见提示词框架及场景

编写提示词的基础原则包括:简单、明确、详细,提示词拥有零样本提示、少样本提示、分配角色、提示写作风格、限定输出格式等技巧。

奶爸的编程之路,也就一周冷个三天 1138

【Android】Coroutine协程原理详解

协程是一个并发方案。也是一种思想。传统意义上的协程是单线程的,面对io密集型任务他的内存消耗更少,进而效率高。但是面对计算密集型的任务不如多线程并行运算效率高。不同的语言对于协程都有不同的实现,甚至同一种语言对于不同平台的操作系统都有对应的实现。我们kotlin语言的协程是 coroutines for jvm的实现方式。底层原理也是利用java 线程。 为什么一些人总觉得协程晦涩难懂? 1.网络上没有详细的关于协程的概念定义,每种语言、每个系统对其实现都不一样。可谓是众说纷纭,什么内核态用户态巴拉巴拉,很

m0_56144365的博客 599

AXI2APB_bridge 设计

此处研究AHB Slave到 APB master的桥接器,实现接口转化,例如下图之后是参数描述

Starry__的博客 4035

ZYNQ工业级串口方案:AXI UART 16550扩展RS-485实战(自动方向控制+Linux驱动)

可以轻松扩展出多路工业级串口。但常规方案需要手动控制收发方向(DE/RE引脚),存在实时性差、CPU占用高等痛点。本文从硬件逻辑设计到Linux驱动开发,完整实现了ZYNQ平台的RS-485工业通信方案。,解决了传统方案实时性差的痛点。所有代码均经过实际项目验证,可直接用于生产环境。:尝试将此设计扩展为多协议兼容接口(支持RS-422/RS-232切换)!等特性,依然是现场总线通信的首选。,通过硬件逻辑+Linux驱动协同设计,实现零延迟切换。在工业自动化、能源电力等场景中,RS-485凭借。

QQ_778132974的博客 830

协程模式在Android中的应用及工作原理

在Android开发中,很多开发者通过代码模式学习协程,通常这已经足够应付了。但这种学习方式忽略了协程背后的精髓,事实上,它们的原理非常简单。那么,是什么使得这些模式起作用呢?拿起你的工具,我们来揭开一些常见的协程模式,这些模式你可能已经见过很多次,并惊叹于它们背后的奥妙。当然,如果你对协程还不太熟悉,那么欢迎!以下是一些对Android开发者来说非常值得学习的模式。

虎哥LoveDroid 1613

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Android ---【Kotlin入门】为什么要用协程?Kotlin 协程原理 + 实战深度解析

协程是基于线程,它是轻量级的线程java用的是线程,kotlin用的是协程,协程是个并发管理工具,定位跟线程其实是一样的。其实就是把java的线程包了一层。之前我们做网络请求的时候,想把网络请求的结果响应在主线程中,应该怎么写?一般情况下我们会在主线程去开启一个子线程发起网络请求,然后将请求的结果在主线程显示比如Retrofit,会帮我们实现自动在主线程去发起网络请求,不用我们自己new Thread,然后。

梁同学与Android 2534

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Android 协程使用

协程是一种轻量级的线程,它们可以被挂起和恢复,使得我们可以用同步的代码风格来编写异步代码。与传统的线程不同,协程不依赖于底层的操作系统线程,因此它们更加高效且性能更好。协程通过简化异步代码的编写,提高了代码的可读性和维护性。其轻量级和高效的特点使得它在Android开发中具有显著优势。理解和掌握协程的原理和应用,可以帮助开发者编写更高效和可维护的Android应用。

阿饱同学 887

Android kotlin实战之协程suspend详解与使用

Kotlin 是一门仅在标准库中提供最基本底层 API 以便各种其他库能够利用协程的语言。与许多其他具有类似功能的语言不同,async与await在 Kotlin 中并不是关键字,甚至都不是标准库的一部分。此外,Kotlin 的挂起函数概念为异步操作提供了比 future 与 promise 更安全、更不易出错的抽象。

4944
上一篇: 探索 Jetpack Compose
胡小明同学
博客等级 码龄10年 35粉丝 52原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值