关于Handler同步屏障你可能不知道的问题

Android Handler同步屏障:深入解析 Android Handler同步屏障 阅读详情

关于Handler同步屏障你可能不知道的问题

前言

很高兴遇见你 ~

这篇文章主要讲Handler中的同步屏障问题,这也是面试的热门问题。很多读者觉得这一块的知识很偏,实战中并没有什么用处,仅仅用来面试,其实同步屏障对于我们的日常使用的话其实是没有多大用处。因为设置同步屏障和创建异步Handler的方法都是标志为hide,说明谷歌不想要我们去使用他。

笔者在前段时间面试时被问到这个问题,之后重新思考了这个问题,发现了一些不一样的地方。结合了一些大佬的观点,发现同步屏障这个机制,并不如我们所想完全没用,而还是有他的长处。这篇文章则表达一下我对同步屏障机制的思考,希望对你有帮助。

文章主要内容是:先介绍什么同步屏障,再分析如何使用以及正确地使用。

那么,我们开始吧。

什么是同步屏障机制

同步屏障机制是一套为了让某些特殊的消息得以更快被执行的机制。

注意这里我在同步屏障之后加上了机制二字,原因是单纯的同步屏障并不起作用,他需要和其他的Handler组件配合才能发挥作用。

这里我们假设一个场景:我们向主线程发送了一个UI绘制操作Message,而此时消息队列中的消息非常多,那么这个Message的处理可能会得到延迟,绘制不及时造成界面卡顿。同步屏障机制的作用,是让这个绘制消息得以越过其他的消息,优先被执行。

MessageQueue中的Message,有一个变量isAsynchronous,他标志了这个Message是否是异步消息;标记为true称为异步消息,标记为false称为同步消息。同时还有另一个变量target,标志了这个Message最终由哪个Handler处理。

我们知道每一个Message在被插入到MessageQueue中的时候,会强制其target属性不能为null,如下代码:

 

MessageQueue.class

boolean enqueueMessage(Message msg, long when) {
  // Hanlder不允许为空
  if (msg.target == null) {
      throw new IllegalArgumentException("Message must have a target.");
  }
  ...
}

而android提供了另外一个方法来插入一个特殊的消息,强行让target==null:

 

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        // 把当前需要执行的Message全部执行
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        // 插入同步屏障
        if (prev != null) { 
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

代码有点长,重点在于:没有给Message赋值target属性,且插入到Message队列头部。当然源码中还涉及到延迟消息,我们暂时不关心。这个target==null的特殊Message就是同步屏障

MessageQueue在获取下一个Message的时候,如果碰到了同步屏障,那么不会取出这个同步屏障,而是会遍历后续的Message,找到第一个异步消息取出并返回。这里跳过了所有的同步消息,直接执行异步消息。为什么叫同步屏障?因为它可以屏蔽掉同步消息,优先执行异步消息。

我们来看看源码是怎么实现的:

 

Message next() {
    ···
    if (msg != null && msg.target == null) {
        // 同步屏障,找到下一个异步消息
        do {
            prevMsg = msg;
            msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());
    }
    ···
}

如果遇到同步屏障,那么会循环遍历整个链表找到标记为异步消息的Message,即isAsynchronous返回true,其他的消息会直接忽视,那么这样异步消息,就会提前被执行了。

注意,同步屏障不会自动移除,使用完成之后需要手动进行移除,不然会造成同步消息无法被处理。我们可以看一下源码:

 

Message next() {
    ...
    // 阻塞时间
    int nextPollTimeoutMillis = 0;
    for (;;) {
        // 阻塞对应时间 
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // 同步屏障,找到下一个异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            // 如果上面有同步屏障,但却没找到异步消息,
            // 那么msg会循环到链表尾,也就是msg==null
            if (msg != null) {
                ···
            } else {
                // 没有消息,进入阻塞状态
                nextPollTimeoutMillis = -1;
            }
            ···
        }
    }
}

可以看到如果没有即时移除同步屏障,他会一直存在且不会执行同步消息。因此使用完成之后必须即时移除。但我们无需操心这个,后面就知道了。

如何发送异步消息

上面我们了解到了同步屏障的作用,但是会发现postSyncBarrier方法被标记为@hide,也就是我们无法调用这个方法。那,讲了这么多有什么用?

咳咳~不要慌,但我们可以发异步消息啊。在系统添加同步屏障的时候,不就可以趁机上车了,是吧。

添加异步消息有两种办法:

  • 使用异步类型的Handler发送的全部Message都是异步的
  • 给Message标志异步

给Message标记异步是比较简单的,通过setAsynchronous方法即可。

Handler有一系列带Boolean类型的参数的构造器,这个参数就是决定是否是异步Handler:

 

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    // 这里赋值
    mAsynchronous = async;
}

在发送消息的时候就会给Message赋值:

 

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    // 赋值
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

但是异步类型的Handler构造器是标记为hide,我们无法使用,但在api28之后添加了两个重要的方法:

 

public static Handler createAsync(@NonNull Looper looper) {
    if (looper == null) throw new NullPointerException("looper must not be null");
    return new Handler(looper, null, true);
}

public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
    if (looper == null) throw new NullPointerException("looper must not be null");
    if (callback == null) throw new NullPointerException("callback must not be null");
    return new Handler(looper, callback, true);
}

通过这两个api就可以创建异步Handler了,而异步Handler发出来的消息则全是异步的。

 

public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}

如何正确使用

上面我们似乎漏了一个问题:系统什么时候添加同步屏障?。

异步消息需要同步屏障的辅助,但同步屏障我们无法手动添加,因此了解系统何时添加和删除同步屏障是非常必要的。只有这样,才能更好地运用异步消息这个功能,知道为什么要用和如何用。

了解同步屏障需要简单了解一点屏幕刷新机制的内容。放心,只需要了解一丢丢就可以了。

我们的手机屏幕刷新频率有不同的类型,60Hz、120Hz等。60Hz表示屏幕在一秒内刷新60次,也就是每隔16.6ms刷新一次。屏幕会在每次刷新的时候发出一个 VSYNC 信号,通知CPU进行绘制计算。具体到我们的代码中,可以认为就是执行onMesure()、onLayout()、onDraw()这些方法。好了,大概了解这么多就可以了。

了解过 view 绘制原理的读者应该知道,view绘制的起点是在 viewRootImpl.requestLayout() 方法开始,这个方法会去执行上面的三大绘制任务,就是测量布局绘制。但是,重点来了:

调用requestLayout()方法之后,并不会马上开始进行绘制任务,而是会给主线程设置一个同步屏障,并设置 ASYNC 信号监听。 当 ASYNC 信号的到来,会发送一个异步消息到主线程Handler,执行我们上一步设置的绘制监听任务,并移除同步屏障

这里我们只需要明确一个情况:调用requestLayout()方法之后会设置一个同步屏障,知道ASYNC信号到来才会执行绘制任务并移除同步屏障。(这里涉及到Android屏幕刷新以及绘制原理更多的内容,本文不详细展开,感兴趣的读者可以点击文末的连接阅读。)

那,这样在等待ASYNC信号的时候主线程什么事都没干?是的。这样的好处是:保证在ASYNC信号到来之时,绘制任务可以被及时执行,不会造成界面卡顿。但这样也带来了相对应的代价:

  • 我们的同步消息最多可能被延迟一帧的时间,也就是16ms,才会被执行
  • 主线程Looper造成过大的压力,在VSYNC信号到来之时,才集中处理所有消息

改善这个问题办法就是:使用异步消息。当我们发送异步消息到MessageQueue中时,在等待VSYNC期间也可以执行我们的任务,让我们设置的任务可以更快得被执行且减少主线程Looper的压力。

可能有读者会觉得,异步消息机制本身就是为了避免界面卡顿,那我们直接使用异步消息,会不会有隐患?这里我们需要思考一下,什么情况的异步消息会造成界面卡顿:异步消息任务执行过长、异步消息海量。

如果异步消息执行时间太长,那即时是同步任务,也会造成界面卡顿,这点应该都很好理解。其次,若异步消息海量到达影响界面绘制,那么即使是同步任务,也是会导致界面卡顿的;原因是MessageQueue是一个链表结构,海量的消息会导致遍历速度下降,也会影响异步消息的执行效率。所以我们应该注意的一点是:

不可在主线程执行重量级任务,无论异步还是同步。

那,我们以后岂不是可以直接使用异步Handler来取代同步Handler了?是,也不是。

同步Handler有一个特点是会遵循与绘制任务的顺序,设置同步屏障之后,会等待绘制任务完成,才会执行同步任务;而异步任务与绘制任务的先后顺序无法保证,在等待VSYNC的期间可能被执行,也有可能在绘制完成之后执行。因此,我的建议是:如果需要保证与绘制任务的顺序,使用同步Handler;其他,使用异步Handler。

总结

技术深挖,总是能学到一些更加不一样的知识。当知识的广度越来越广,知识之间的联系会迸发出不一样的火花。

第一次学习Handler,仅仅知道可以发送消息并执行;第二次学习Handler,知道了其在Android消息机制重要地位;第三次学习Handler,知道了原来Handler和屏幕刷新机制还有这么一个联系。

温故而知新,古人诚不欺我。

最后

为了让大家更好的去学习和提升自己,我和几位大佬一起收录整理的 Flutter进阶资料以及Android学习PDF+架构视频+面试文档+源码笔记 ,并且还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料……

这些都是我闲暇时还会反复翻阅的精品资料。可以有效的帮助大家掌握知识、理解原理。当然你也可以拿去查漏补缺,提升自身的竞争力。
如果你有需要的话,可以前往 GitHub 自行查阅。

Handler之消息屏障你应该知道的 前言 由于Handler消息机制的同步屏障问题使用的场景不多,即使不了解可能也不会影响正常开发,因此往往容易被忽视,笔者也是自认比较熟悉Handler的消息机制,在之前同步屏障也算盲区,要想更全面的掌握Handler消息机制,同步屏障就不应该缺席。本文将从同步屏障的插入、删除、使用场景等角度分析同步屏障的工作原理 消息的分类 Message分为3中:普通消息(同步消息)、屏障消息(同步屏障)和异步消息。我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着, 阅读详情

相关推荐

Handler之同步屏障机制(sync barrier)

0 引言 Handler中的Message可以分为两类:同步消息、异步消息。消息类型可以通过以下函数得知 //Message.java public boolean isAsynchronous() { return (flags & FLAG_ASYNCHRONOUS) != 0; } 一般情况下这两种消息的处理方式没什么区别,只有在设置了同步屏障时才会出现差异。 1 什

asdgbc的专栏 1万+

Handler.createAsyn作用

结论 从API28开始,Handler类增加了静态方法createAsyn。主要作用是使所有通过这个Handler发送的Message,都会被设置为FLAG_ASYNCHRONOUS异步消息(默认是同步消息),在搭配消息屏障使用的情况下,会被优先调用。 原理 #Handler.java @NonNull public static Handler createAsync(@NonNull...

DemoKiller的博客 1881

Handler机制中同步屏障原理及结合实际问题分析

本文介绍了Handler的同步屏障机制及结合工作中遇到的一个App有触摸事件但没有界面更新问题分析

weixin_43952603的博客 1318

Android handler用法及分析

handler用法分析

Qq2179344的博客 1926

什么是Handler的同步屏障机制?

前言 对于Handler机制,想必大家都已经非常熟悉了吧,从迈进Android开发这扇大门的时候,就不停的研究和使用它,同样的这也是Android系统架构的精髓之一。然而在我们使用的时候,往往会忽略掉一些不常见却又很重要的内容,今天就来讲一讲经常被忽略的同步屏障以及异步消息。 制流程中窥视handler同步屏障 为了引出今天的主题,我们先来看看ui的渲染流程吧。在Android的绘制流程中,ViewRootImpl这个类发挥了非常重要的作用,首先我们看一下这个类中比较重要的一个方法requestLayou.

鸿蒙开发知识记录 2411

Handler异步消息与同步屏障(SyncBarrier)

前言 Android的消息机制之前有一篇文章有写,里面具体讲到了Handler怎么发送和处理消息的整个过程。感兴趣的同学可以先跳转过去看看 从Handler.post(Runnable r)再一次梳理Android的消息机制(以及handler的内存泄露) 在看消息机制的时候,不管是把消息加入队列,还是取出队列,Message有个isAsynchronous()方法一直没关注,今天来看看这个方法到底是做什么的。 Handler同步屏障(SyncBarrier) 要理解这个方法的含义,我们要先了解一下Hand

梦里梦到醒不来的梦 3807

Android之Handler源码分析(第六篇:其他特性)

上面两个,API >= 28时可使用,Android9.0增加的方法,也同样可以创建Handler

王员外的博客 417

handler解析(3)-同步消息、异步消息、同步屏障

Message分为3种:普通消息(同步消息)、屏障消息(同步屏障)和异步消息。我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。也就是我一般使用的Message,再通过Handler进行到消息队列,前提是构造Handler时候传的构造参数async为false。

z936689039的博客 2664

关于Handler同步屏障你可能不知道的问题,字节Android高级岗

原因是MessageQueue是一个链表结构,海量的消息会导致遍历速度下降,也会影响异步消息的执行效率。同步Handler有一个特点是会遵循与绘制任务的顺序,设置同步屏障之后,会等待绘制任务完成,才会执行同步任务;当我们发送异步消息到MessageQueue中时,在等待VSYNC期间也可以执行我们的任务,让我们设置的任务可以更快得被执行且减少主线程Looper的压力。异步消息需要同步屏障的辅助,但同步屏障我们无法手动添加,因此了解系统何时添加和删除同步屏障是非常必要的。但我们无需操心这个,后面就知道了。

m0_60634964的博客 1225

2023电视专用!安卓电子相册APP避坑指南(附开源代码)

本文针对2023年智能电视用户,提供安卓电子相册APP的避坑指南与实战教程。文章详细解析了电视端相册应用的常见广告与兼容性陷阱,并指导用户如何安全获取、部署纯净的APK应用,通过核心设置实现无广告的个性化图片轮播体验,最终打造专属的家庭回忆墙。

weixin_29012677的博客 498

华为云Stack扩容实战:从CMDB配置到计算节点增减容,一份避坑指南

本文详细解析华为云Stack扩容实战,从CMDB配置到计算节点增减容的全流程操作指南。通过典型场景案例和避坑建议,帮助企业高效管理计算资源扩容与减容,确保业务连续性。特别针对华为云Stack的弹性扩容能力提供专业指导,涵盖存储、网络及自动化工具链的配合使用。

weixin_30591551的博客 479

Handler机制——同步屏障

一、消息种类 关于Handler机制的基本原理不了解可以看这里: Handler机制源码解析。 Message分为3中:普通消息、屏障消息和异步消息。我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息...

start_mao的博客 1万+

HFSS学习记录——0. HFSS软件安装(ANSYS Electromagnetics Suite 2023 R1)

HFSS 2023 R1安装步骤

plmxu34的博客 15万+

Android Handler消息机制完全解析-同步屏障(三)

想搞明白这个问题,我们需要对Android的UI刷新有点了解,以电影为例,动画至少要达到24FPS,才能保证动画的流畅性,低于这个值肉眼会感觉到卡顿,在手机上这个值被调整到60FPS,60FPS意思就是每秒播放60帧画面,那么每帧画面的时间间隔为1000 /60=16ms,这也是为什么有个16ms的指标,也就是说一般android中UI刷新的间隔是16ms。UI的刷新也是通过Handler发送消息进行的,并且UI刷新的消息是优先级最高的,否则当消息比较多时手机上的画面就会卡顿,这一点大家应该很容易想明白。

乐于分享,乐于讨论,欢迎大家积极讨论 2220

四类九种移位寄存器总结(循环(左、右、双向)移位寄存器、逻辑和算术移位寄存器、串并转换移位寄存器、线性反馈移位寄存器LFSR|verilog代码|Testbench|仿真结果)

移位寄存器有哪些分类呢? 按移位方向分类:①单向移位寄存器(包括左移、右移)②双向移位寄存器 按循环方式分类:①循环移位寄存器②非循环移位寄存器 按部位的不同分类:①逻辑移位寄存器②算术移位寄存器 按输入输出方式分类:①串入串出②串入并出③并入串出④并入并出

Loudrs的博客 1万+

GD32F303驱动WS2812B灯带:用TIMER+PWM+DMA解放CPU,实现流畅灯效(附完整代码)

本文详细介绍了基于GD32F303的TIMER+PWM+DMA全自动控制方案,用于高效驱动WS2812B灯带。通过硬件外设协同工作,该方案不仅解放了CPU资源,还能实现复杂的灯光效果,如渐变、流水等。文章提供了完整的代码实现和性能优化技巧,适合嵌入式开发者参考。

weixin_42561143的博客 381

Handler 同步屏障&异步消息

简单来说,同步屏障就是一套为了让特殊消息更快执行的一个机制。这里我们假设一个场景:我们向主线程发送了一个UI绘制操作Message,而此时消息队列中的消息非常多,那么这个Message的处理可能会得到延迟,绘制不及时造成界面卡顿。同步屏障机制的作用,是让这个绘制消息得以越过其他的消息,优先被执行。MessageQueue中的Message,有一个变量,他标志了这个Message是否是异步消息;标记为true称为异步消息,标记为false称为同步消息。同时还有另一个变量target。

Pumpkin's Blog 1147

Python 爬虫进阶技巧:新手必学的反爬突破与效率优化

本文系统总结了爬虫进阶的核心技巧,针对新手常见的反爬拦截、效率低下、动态页面解析等痛点问题,提出了可落地的解决方案。主要内容包括:通过随机UA池、代理IP池和Cookie池突破常见反爬机制;利用多线程/异步请求提升爬取效率;使用Selenium解析动态渲染页面;以及数据清洗方法提升数据质量。文中提供了完整的代码示例和实战对比数据,并强调爬虫开发应遵守合规性要求。这些技巧能帮助开发者快速提升爬虫实战能力,应对80%以上的常见爬取需求。

2503_91057718的博客 1436

Handler 同步屏障机制

本文摘要: Handler消息机制中的同步屏障机制详解。文章介绍了三种消息类型(普通消息、异步消息、屏障消息),重点分析了同步屏障的工作原理。通过postSyncBarrier()方法插入屏障消息(target为null的特殊消息),屏障会阻挡后续普通消息,仅允许异步消息通过。在next()方法中,遇到屏障时会跳过同步消息直到找到异步消息。最后可通过removeSyncBarrier()移除屏障。该机制确保了异步消息的优先处理,是Android UI渲染等场景的重要基础。

似霰的博客 981

GMSL转eDP芯片MAX9686X(ADI)

MAX96860/MAX96862/MAX96864解串器可以把单路或双路GMSL转换成eDP V1.4a,支持820MHzPCLK的无压缩的24bpp(每个像素的位数)视频,或660MHz的30bpp视频。芯片的GMSL支持NRZ编码的6Gbps/187Mbps的GMSL2,PAM4编码的12Gbps/187Mbps的GMSL3。同时支持单线(COAX模式)双向全双工传输控制数据和video数据。支持DSC压缩的芯片,最大支持像素时钟1080MHz@24bpp或者864MHz@30bpp,传输方式包括同轴

qflook的博客 2851

《我的Classic Platform AUTOSAR之路》 BSWM 模式管理(二)ESH

只有 PostRun 和 wakeup 才能切换到 RUN,其他状态需要先切换到 wakeup 模式。进入 RUN 模式需要打开通信 allow,退出 Run 需要 disable 通信 allow。Prepareshut 与 dem_shutdown 绑定,如果没有 dem 配置,不会生成。除了 Init 模式下,其他模式下都要检查 ECU 的唤醒源和通信 pending 需求。与 ECUM 相关,整个 ECU 状态管理的状态机制。postrun 可以不配置,需要根据自己的需求使能。

libuping2023的博客 965

Android Handler之同步屏障机制

1.Message消息分类 Handler的Message分为3种:普通消息、屏障消息、异步消息。 其中普通消息又称为同步消息,屏障消息又称为同步屏障。 通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息。因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。 2.发送异步消息 通常使用Handler发消息时都是同步消息

zenmela2011的专栏 1935

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

关于Android Handler同步屏障那些事

关于Android Handler同步屏障那些事   前一段时间,肥柴突然听到一句:Handler同步屏障,一脸懵逼,啥玩意?   于是就去了解并总结了一下Handler的异步消息和同步屏障的相关知识,只能说,肥柴确实学到了。 一、Handler Message种类   首先我们来了解一下Handler的Message种类:   1、普通消息   2、异步消息   3、屏障消息   其中普通消息又称为同步消息,肥柴平时发送的消息基本都是同步消息,而我们又基本很少有发送异步甚至屏障消息的需求,所以就没怎么了

weixin_38196407的博客 1648

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

上一篇: 今年Android面试有新要求,资深面试官口述了这几个点
下一篇: OfferKiller | Https 为什么是安全的?(上)
懂你的大海
博客等级 码龄6年 9049粉丝 1381原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值