Kotlin协程(Coroutine)的使用以及源码分析

Kotlin 协程源码解析 本文适合有协程使用基础并想了解内部原理开发者 一、什么协程(Coroutines) 看下Android文档对协程的定义:协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码。协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念。 本文主要分析协程以下功能的内部实现 编译器是如何实现协程的异步操作的同步调用?(如何帮我们解决异步操作回调地狱呢?) coroutine是如何通过CoroutineContext来切换上下文的? coroutine挂起和. 阅读详情

对于了解协程(微线程,Coroutine),其实很早就有协程的概念,在Lua,Python,go语言中较为常见Android在kotlin拓展库kotlinx.coroutines去提供使用。

首先讲起进程和线程

进程拥有切换和打开的进程表,文件资源、数据资源、独立的内存空间。

线程从属于进程,是程序的实际执行者,一个进程至少包含一个主线程,也可以有更多的子线程,线程拥有自己的栈空间。

线程具有五种状态:初始化、可运行、运行中、阻塞、销毁。

协程

协程不同与进程或线程是比线程更加轻量级的存在,一个进程可以拥有多个线程,一个线程可以拥有多个协程,协程有自己控制的上下文。

进程和线程都是由是操作系统的调度切换策略去执行,而协程的切换时机则是用户自己的程序所决定的。

协程是不需要多线程的锁机制,减少资源消耗。

协程提升CPU利用率,减少线程切换,提升程序运行效率。

总结协程:可控制 轻量级 语法糖。
常用API
runBlocking

//启动执行协程任务

launch

//执行协程任务
//启动参数

  • 当前协程上下文,可声明切换调度器模式
  • context: CoroutineContext = EmptyCoroutineContext
  • 可自定义启动方式
  • start: CoroutineStart = CoroutineStart.DEFAULT,
  • 闭包形式的协程函数
  • block: suspend CoroutineScope.() -> T
asyns/await

//执行协程任务/得到返回结果

协程工厂函数

val scope = MainScope()
scope.async(Dispatchers.Default) {}
scope.launch {}
scope.cancel()

withContext

withContext(Dispatchers.Default){}
Dispatchers提供四种不同属性提供不同的线程支持和操作Default Main Unconfined IO


反编译源码分析

runBlocking

  fun main() = runBlocking {
        listCoroutineScope()
    }
//反编译源码
 
   public final List<Font> main() {
        return (List) BuildersKt__BuildersKt.runBlocking$default((CoroutineContext) null, new FontService$main$1((Continuation) null), 1, (Object) null);
    }

// FontService$main$1 
@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010!\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u0010\u0000\u001a\b\u0012\u0004\u0012\u00020\u00020\u0001*\u00020\u0003HŠ@¢\u0006\u0004\b\u0004\u0010\u0005"}, d2 = {"<anonymous>", "", "Lcom/hyfontstudio/fontspro/base/Font;", "Lkotlinx/coroutines/CoroutineScope;", "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"}, k = 3, mv = {1, 1, 16})
/* compiled from: FontService.kt */
final class FontService$main$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super List<Font>>, Object> {
    int label;
    private CoroutineScope p$;

FontService$main$1(Continuation continuation) {
    super(2, continuation);
}

public final Continuation<Unit> create(Object obj, Continuation<?> continuation) {
    Intrinsics.checkParameterIsNotNull(continuation, "completion");
    FontService$main$1 fontService$main$1 = new FontService$main$1(continuation);
    CoroutineScope coroutineScope = (CoroutineScope) obj;
    fontService$main$1.p$ = (CoroutineScope) obj;
    return fontService$main$1;
}

public final Object invoke(Object obj, Object obj2) {
    return ((FontService$main$1) create(obj, (Continuation) obj2)).invokeSuspend(Unit.INSTANCE);
}

public final Object invokeSuspend(Object $result) {
    IntrinsicsKt.getCOROUTINE_SUSPENDED();
    if (this.label == 0) {
        ResultKt.throwOnFailure($result);
        CoroutineScope coroutineScope = this.p$;
        return FontService.Companion.listCoroutineScope();
    }
    throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
}

 fun listCoroutineScope() =
             runBlocking {
            async {
                print("Thread:::${Thread.currentThread().name}")
            }.await()


    }
//反编译源码
   public final List<Font> listCoroutineScope() {
        return (List) BuildersKt__BuildersKt.runBlocking$default((CoroutineContext) null, new FontService$Companion$listCoroutineScope$1((Continuation) null), 1, (Object) null);
    }
//FontService$Companion$listCoroutineScope$1 
@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010!\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u0010\u0000\u001a\b\u0012\u0004\u0012\u00020\u00020\u0001*\u00020\u0003HŠ@¢\u0006\u0004\b\u0004\u0010\u0005"}, d2 = {"<anonymous>", "", "Lcom/hyfontstudio/fontspro/base/Font;", "Lkotlinx/coroutines/CoroutineScope;", "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"}, k = 3, mv = {1, 1, 16})
@DebugMetadata(c = "com.hyfontstudio.fontspro.ime.core.FontService$Companion$listCoroutineScope$1", f = "FontService.kt", i = {0}, l = {109}, m = "invokeSuspend", n = {"$this$runBlocking"}, s = {"L$0"})
/* compiled from: FontService.kt */
final class FontService$Companion$listCoroutineScope$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super List<Font>>, Object> {
    Object L$0;
    int label;
    private CoroutineScope p$;

FontService$Companion$listCoroutineScope$1(Continuation continuation) {
    super(2, continuation);
}

public final Continuation<Unit> create(Object obj, Continuation<?> continuation) {
    Intrinsics.checkParameterIsNotNull(continuation, "completion");
    FontService$Companion$listCoroutineScope$1 fontService$Companion$listCoroutineScope$1 = new FontService$Companion$listCoroutineScope$1(continuation);
    CoroutineScope coroutineScope = (CoroutineScope) obj;
    fontService$Companion$listCoroutineScope$1.p$ = (CoroutineScope) obj;
    return fontService$Companion$listCoroutineScope$1;
}

public final Object invoke(Object obj, Object obj2) {
    return ((FontService$Companion$listCoroutineScope$1) create(obj, (Continuation) obj2)).invokeSuspend(Unit.INSTANCE);
}

public final Object invokeSuspend(Object $result) {
    Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
    int i = this.label;
    if (i == 0) {
        ResultKt.throwOnFailure($result);
        CoroutineScope $this$runBlocking = this.p$;
        Deferred async$default = BuildersKt__Builders_commonKt.async$default($this$runBlocking, (CoroutineContext) null, (CoroutineStart) null, new AnonymousClass1((Continuation) null), 3, (Object) null);
        this.L$0 = $this$runBlocking;
        this.label = 1;
        Object await = async$default.await(this);
        if (await == coroutine_suspended) {
            return coroutine_suspended;
        }
        CoroutineScope coroutineScope = $this$runBlocking;
        return await;
    } else if (i == 1) {
        CoroutineScope $this$runBlocking2 = (CoroutineScope) this.L$0;
        ResultKt.throwOnFailure($result);
        return $result;
    } else {
        throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }
}

@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010!\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u0010\u0000\u001a\b\u0012\u0004\u0012\u00020\u00020\u0001*\u00020\u0003HŠ@¢\u0006\u0004\b\u0004\u0010\u0005"}, d2 = {"<anonymous>", "", "Lcom/hyfontstudio/fontspro/base/Font;", "Lkotlinx/coroutines/CoroutineScope;", "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"}, k = 3, mv = {1, 1, 16})
@DebugMetadata(c = "com.hyfontstudio.fontspro.ime.core.FontService$Companion$listCoroutineScope$1$1", f = "FontService.kt", i = {}, l = {}, m = "invokeSuspend", n = {}, s = {})
/* renamed from: com.hyfontstudio.fontspro.ime.core.FontService$Companion$listCoroutineScope$1$1  reason: invalid class name */
/* compiled from: FontService.kt */
static final class AnonymousClass1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super List<Font>>, Object> {
    int label;
    private CoroutineScope p$;

    public final Continuation<Unit> create(Object obj, Continuation<?> continuation) {
        Intrinsics.checkParameterIsNotNull(continuation, "completion");
        AnonymousClass1 r0 = new AnonymousClass1(continuation);
        CoroutineScope coroutineScope = (CoroutineScope) obj;
        r0.p$ = (CoroutineScope) obj;
        return r0;
    }

    public final Object invoke(Object obj, Object obj2) {
        return ((AnonymousClass1) create(obj, (Continuation) obj2)).invokeSuspend(Unit.INSTANCE);
    }

    public final Object invokeSuspend(Object $result) {
        IntrinsicsKt.getCOROUTINE_SUSPENDED();
        if (this.label == 0) {
            ResultKt.throwOnFailure($result);
            CoroutineScope coroutineScope = this.p$;
            StringBuilder sb = new StringBuilder();
            sb.append("Thread:::");
            Thread currentThread = Thread.currentThread();
            Intrinsics.checkExpressionValueIsNotNull(currentThread, "Thread.currentThread()");
            sb.append(currentThread.getName());
            System.out.print(sb.toString());
            return FontService.Companion.getResourcesJsonList();
        }
        throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }
}
}

源码留到下一篇分析…

android kotlin 协程源码分析withContext挂载和恢复 我们根据业务代码,子协程切换到io线程后,执行AnonymousClass1的resumeWith方法,根据代码片段3的内容,invokeSuspend子协程业务执行完毕后,会进行if判断,completion的类型,AnonymousClass1的成员completion为DispatchedCoroutine,所以直接执行completion.resumeWith。这边连接我们知道,我们的业务代码会生成一个匿名内部类,其父类继承SuspendLambda,反编译代码如下() 阅读详情

相关推荐

Kotlin:深度理解协程挂起恢复实现原理。纯源码分析。

看完美团、字节、腾讯这三家的一二三面试问题,是不是感觉问的特别多,可能咱们真的又得开启面试造火箭、工作拧螺丝的模式去准备下一次的面试了。开篇有提及我可是足足背下了Java互联网工程师面试1000题,多少还是有点用的呢,换汤不换药,不管面试官怎么问你,抓住本质即可!能读到此处的都是真爱Java互联网工程师面试1000题。

2401_85262393的博客 1178

kotlin启动协程源码分析

IntrinsicsKt.createCoroutineUnintercepted 创建一个不拦截执行的Continuation,基中$this$startCoroutineCancellable是编译期生成的代码function2传入的。由上面可知传进来的是 CoroutineStart.DEFAULT,内部有个invoke方法,CoroutineStart是个枚举值,这里的值是1,会执行。//3&2 = 0011 & 0010 = 0010 = 2 不为0,则为RESUMED 支持恢复执行。

古苏的博客 1276

Kotlin:深度理解协程挂起恢复实现原理。纯源码分析。,张口就来

) {})//执行结果09:05:46:961 [thread] suspendFunction----执行之后协程框架的launch等等对上述的封装,使用起来更加顺手而已。从执行的结果来看,先输出了coroutine start,之后挂起两秒钟,继续执行之后的逻辑。那我们就用这个demo来分析,协程的挂起与恢复是如何实现的。😮挂起函数分析👀首先我们需要分析一下挂起函数是个什么玩意儿~😮将上述挂起函数代码转换成java看看。2。

2401_84123232的博客 1203

android kotlin 协程(六) 源码浅析

本篇我们阅读了 CoroutineScope#launch{}// 有receiver直接恢复协程// 有receiver创建协程,需要手动恢复// 没有receiver直接恢复协程// 没有receiver创建协程,需要手动恢复这四胞胎说实话,是真没啥用,但就是得了解一下… 看的头疼:)完整代码。

高级开发者进阶ing... QaQ 1263

Kotlin协程到底是怎么切换线程的?你是否知晓?,大厂面经合集

从使用上来讲,调度器就是我们使用的等从作用上来讲,调度器的作用是控制协程运行的线程从结构上来讲,的父类是,然后再继承于它们的类结构关系如下:这也是为什么能加入到中的原因,并且支持操作符来完成增加从命名上很容易看出,即协程拦截器,先看一下接口// ContinuationInterceptor 在 CoroutineContext 中的 Key/**拦截 continuation*///…从上面可以提炼出两个信息1.拦截器的Key是单例的,因此当你添加多个拦截器时,生效的只会有一个。

m0_54816834的博客 890

Kotlin Coroutine 源码解析(1) —— 协程是如何运行的

前言 在刚接触协程的时候,就产生了一个疑问,协程到底有没有创建线程,如果有,那它和线程池有什么区别?如果没有,为什么在android的主线程中不会 阻塞,了解Android源码的 应该猜到了,肯定是创建了子线程,不然 就会报异常了。那协程又是如何保证任务能切换到主线程呢? 下面就带着这几个问题,来分析协程的源码 示例 用下面的示例 模拟获取token,请求网络的过程。顺序执行,不会阻塞当前线程, fun main() { GlobalScope.launch { println("开始 ")

薛瑄的博客 2288

Kotlin协程源码分析-5 状态机

上文链接:Kotlin协程源码分析-4 状态机 在上文中最后一行 “挂起函数的Continuation对象如何才能获取?”。“如何返回一个挂起标识符COROUTINE_SUSPENDED”。本文将做就这两个问题详细介绍 获取挂起函数的Continuation对象 你可以直接看kotlin协程库实现方式得到答案。这里直接给出答案。 suspend fun mySuspendOne() = susp...

不会写代码的丝丽 2189

KUKA 虚拟调试实战——打通Simpro4.1与OfficeLite8.6的通信与实时控制链路

本文详细介绍了KUKA虚拟调试实战,重点解析如何打通Simpro4.1与OfficeLite8.6的通信与实时控制链路。通过环境搭建、权限配置、机器人型号匹配等关键步骤,帮助工程师高效完成机器人轨迹规划和PLC联锁逻辑验证,大幅降低真机调试风险。文章还提供了典型报错解决方案和实时控制优化技巧,适用于工业机器人仿真与培训场景。

weixin_30907935的博客 452

Kotlin协程的JVM实现源码分析(下)

根据是否保存切换调用栈,在代码上的区别是:是否可在普通函数里调用,并暂停其执行。Kotlin协程,必须在挂起函数中调用和恢复,属于无栈协程

喜欢折腾的开发,Android Java Python 就是玩儿。 1422

3D-IC测试挑战与Tessent解决方案解析

3D-IC技术通过硅通孔(TSV)实现芯片垂直堆叠,是延续摩尔定律的关键路径。该技术面临三大核心挑战:KGD标准严苛化要求测试覆盖率达99.5%以上,存储器测试需兼容LPDDR/HBM等多种协议,逻辑堆叠需解决TSV互连的电气特性和时序验证问题。Mentor Tessent平台采用混合测试架构,结合压缩扫描测试(ATPG)和内建自测试(BIST)技术,实现100x测试数据压缩比和99.97%的TSV缺陷检出率。这些创新方案在移动SoC和高性能计算芯片中已获验证,测试时间缩短73%,缺陷逃逸率降低77%。随着

weixin_30621711的博客 376

深入解析ADC采样电容:从充电误差到LSB精度的关键设计

本文深入解析了ADC采样电容在模数转换中的核心作用,重点探讨了充电时间常数与采样误差的博弈关系。通过推导最小采样时间公式,揭示了精度、速度、源阻抗与内部电容之间的制约关系,并提供了从理论计算到外部驱动电路设计的实战指南,是确保ADC达到LSB精度的关键。

android的专栏 1103

kotlin - 协程 launch 源码分析

kotlin - 协程 launch 源码分析

maoning20080808的专栏 972

基于Simulink的防空导弹六自由度弹道建模与仿真实战

本文详细介绍了基于Simulink的防空导弹六自由度弹道建模与仿真方法。通过分析导弹动力学原理、Simulink环境配置、气动力建模、推进与控制系统设计等关键环节,提供了一套完整的实战指南。文章特别强调了六自由度模型在防空导弹仿真中的重要性,并分享了常见问题排查与优化建议,为相关领域工程师提供实用参考。

apple5的专栏 868

Kotlin协程源码分析-6 suspendCoroutineUninterceptedOrReturn注意点

学完状态机之后来看看他可以帮我们理解什么。 我们回头看看suspendCoroutineUninterceptedOrReturn获取挂起函数的注意点: 1.不要轻易在同一个调用挂起函数的栈帧中调回Continuation.resumeWithException或者Continuation.resume 2. 如果不是立即返回结果,请务必返回kotlin.coroutines.intrinsics...

不会写代码的丝丽 1695

CATIA P3 V5-6R2023在Windows 11 23H2上的稳定安装与系统级配置指南

CATIA是高端制造业核心的三维参数化设计平台,其运行依赖严格的系统底层支撑。理解CATIA的BRep几何内核原理与Windows子系统(如WSL、Hyper-V)的协同机制,是保障建模稳定性与计算精度的技术前提。CATIA Run-Time Library和OpenGL硬件加速能力共同决定了曲面重建、螺纹建模等关键操作的可靠性;而.NET Framework 3.5、VC++ 2022运行库及显卡Studio驱动的精准匹配,则构成工业软件在Win11环境落地的工程基础。本文聚焦于航空、轨交、能源等领域高频

weixin_30983353的博客 639

基于哈夫曼编码的图像加密算法matlab仿真

哈夫曼编码是一种常用的无损数据压缩算法,其基本思想是根据字符出现的频率构造一棵哈夫曼树,将出现频率高的字符编码为短码,出现频率低的字符编码为长码,从而实现数据的压缩。在图像加密领域,哈夫曼编码也被广泛应用于图像加密算法中,基于哈夫曼编码的图像加密算法具有加密速度快、加密强度高等优点。基于哈夫曼编码的图像加密算法的基本思路是将待加密的图像分为多个小块,对每个小块进行哈夫曼编码,然后将哈夫曼编码后的结果进行异或运算,最后将异或运算后的结果重新编码,以实现图像加密。具体步骤如下:将待加密的图像分成多个小块。

平时工作较为繁忙,私信消息一般晚上回复,谢谢大家~~ 264

Kotlin - 协程基础及原理

协程上下文在协程中的作用非常大,有它在相当于协程有了装备卡槽一样。你可以将你想添加的上下文对象合并到参数上,然后在其他地方使用。的数据结构有如下特点:可以通过[]以类似List的方式访问任何一个协程上下文对象,[]内是目标协程上下文。协程上下文可以通过的方式依次累加,当然+=也是可用的。我们来自定义一个协程上下文给协程添加一个名字:应用到示例中:suspend {.name}”)})//输出:c0-01其实协程已经为我们提供了实现。通过实现拦截器接口。

2401_84519998的博客 412

NVIDIA A100驱动及CUDA安装指南

NVIDIA A100驱动及CUDA安装指南:详细介绍了在Ubuntu系统下安装A100显卡驱动和CUDA工具包的完整流程,包括环境准备、驱动安装、CUDA配置及性能优化建议。特别推荐猿界算力平台提供开箱即用的A100算力租赁服务,具备专业团队支持、灵活租期和实惠价格等优势。文末通过PyTorch测试验证了A100设备的可用性,展示了80GB显存的强大计算能力。

2401_89353317的博客 1511

Kotlin之协程coroutine lifecycleScope 和 viewModelScope源码(2)

上一篇文章 Kotlin之协程coroutine使用(1) 文末介绍了Activity,Fragment 和 ViewModelScope 对应的自动绑定生命周期协程开启方式, lifecycleScope 和 viewModelScope 这篇文章,就解剖一下,为什么这两货可以绑定生命周期,去需要用户自己去绑定取消? ========================================================================= Activity 和 Fra..

Leo_Liang_jie的博客 3402

交换机背板带宽、交换容量与包转发率到底是啥?

在网络工程中,交换机是核心设备之一,而关于它的性能指标,背板带宽、交换容量、以及包转发率往往成为人们衡量其性能的重要参数。这些术语的定义、关系、以及在实际场景中的应用,都对交换机的选型与部署有着重要意义。本文将为您全面解读这些概念,帮助您深入了解交换机的核心性能指标。

网络技术联盟站 1551

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Kotlin协程源码分析-4 状态机

今天就来分析所谓的协程的状态机 首先来看一个问题,我们知道suspend 函数会在编译后生成一个传入continuation的函数。 那么我们如何自己拿到这个传入的对象呢?这个对象又有什么作用? 请看如下代码即可 //com.example.studycoroutine.chapter.four.MyCoroutine.kt suspend fun commonSuspendFun(): Stri...

不会写代码的丝丽 3008

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Kotlin 协程源码学习笔记

Kotlin Coroutine协程源码学习笔记Kotlin Coroutine协程源码学习笔记什么是协程协程基本使用方法协程源码分析 Kotlin Coroutine协程源码学习笔记 学习kotlin有一段时间了,写个博客记录一下。本文基于kotlin 1.3.31来写,如有任何描述不当的地方,请多多指教纠正。 什么是协程 协程是一种轻量级的线程。那和线程有什么不一样?一.线程的调度是由操...

DChan24的博客 929

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

kotlin coroutines 协程教程(三)launch原理

原理篇(二)CoroutineScope.launch{} 过程分析 launch 的流程 先贴上 launch 的源码: public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAUL...

花不掉泪的博客 6526

Kotlin协程源码分析-3 调用挂起函数

上文链接 Kotlin协程源码分析-2 调用挂起函数 继续上文从编译后的字节码继续分析,继续查看上篇文章的源码 //com.example.studycoroutine.chapter.two.CoroutineRun.kt fun testOne(){ val myCoroutineFun: suspend () -> String = { logD("返回 he...

不会写代码的丝丽 1764

Kotlin协程到底是怎么切换线程的?你是否知晓?

从使用上来讲,调度器就是我们使用的等从作用上来讲,调度器的作用是控制协程运行的线程从结构上来讲,的父类是,然后再继承于它们的类结构关系如下:这也是为什么能加入到中的原因,并且支持操作符来完成增加从命名上很容易看出,即协程拦截器,先看一下接口// ContinuationInterceptor 在 CoroutineContext 中的 Key/**拦截 continuation*///…从上面可以提炼出两个信息1.拦截器的Key是单例的,因此当你添加多个拦截器时,生效的只会有一个。

2501_90391985的博客 918
上一篇: Kotlin数据去重实现distinctBy源码
下一篇: 不需要Activity的AlertDialog
TheWindRises_QAQ
博客等级 码龄10年 1粉丝 11原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值