ActivityManagerService(AMS)--学习笔记

: mBatteryStatsService.getActiveStatistics().getIsOnBattery();

mBatteryStatsService.getActiveStatistics().setCallback(this);

//创建进程统计服务,信息保存在目录/data/system/procstats

mProcessStats = new ProcessStatsService(this, new File(systemDir, “procstats”));

mAppOpsService = mInjector.getAppOpsService(new File(systemDir, “appops.xml”), mHandler);

mGrantFile = new AtomicFile(new File(systemDir, “urigrants.xml”), “uri-grants”);

//创建多用户、VR controller

mUserController = new UserController(this);

mVrController = new VrController(this);

… …

// 创建 Intent 防火墙

mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);

mTaskChangeNotificationController =

new TaskChangeNotificationController(this, mStackSupervisor, mHandler);

// 创建控制 Activity 启动代理对象

mActivityStartController = new ActivityStartController(this);

// 创建最近任务列表,并保存在最近任务列表栈中

mRecentTasks = createRecentTasks();

mStackSupervisor.setRecentTasks(mRecentTasks);

// 创建 任务栈锁,比如在Screen pinning Mode 下

mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);

mLifecycleManager = new ClientLifecycleManager();

//创建名为"CpuTracker"的线程, 主要用于 收集 ANRS、电池状态信息等

mProcessCpuThread = new Thread(“CpuTracker”) {

@Override

public void run() {

synchronized (mProcessCpuTracker) {

mProcessCpuInitLatch.countDown();

mProcessCpuTracker.init();

}

… …

// 更新CPU 状态

updateCpuStatsNow();

… …

}

};

mHiddenApiBlacklist = new HiddenApiSettings(mHandler, mContext);

// 获取 Watchdog看门狗实例 ,并添加到Monitor监控 以及mHandler 添加到Thread中

Watchdog.getInstance().addMonitor(this);

Watchdog.getInstance().addThread(mHandler);

// 更新 oom_adj

updateOomAdjLocked();

… …

}

3.后台Service 最大限制数设置


class ActivityManagerDebugConfig {

… …

static final boolean TAG_WITH_CLASS_NAME = false;

// Default log tag for the activity manager package.

static final String TAG_AM = “ActivityManager”;

… …

}

四、启动AMS.Start() 实现

==============================================================================

1.AMS.Start() 主要功能


  1. 移除所有的进程组

  2. 启动CpuTracker线程

  3. 启动 app 操作服务AppOpsService

  4. 将ActivityManagerInternal 添加到本地Service中

2.AMS.Start() 功能 实现


AMS.Start() 功能实现如下:

private void start() {

//移除所有的进程组

removeAllProcessGroups();

//启动CpuTracker线程

mProcessCpuThread.start();

//启动电池统计服务

mBatteryStatsService.publish();

// 启动 app 操作Service

mAppOpsService.publish(mContext);

Slog.d(“AppOps”, “AppOpsService published”);

// 将ActivityManagerInternal 添加到本地Service中

LocalServices.addService(ActivityManagerInternal.class, new LocalService());

}

五、AMS设置系统进程实现

=========================================================================

setSystemProcess 主要作用是

添加 各种服务 包括meminfo、gfxinfo、dbinfo、cpuinfo以及permissionprocessinfo系统Service。同时, 更新进程相关的 Lru 算法 ,以及oom_adj 值。

实现代码如下:

public void setSystemProcess() {

try {

//注册添加各种Service,可以使用adb shell dumpsys

ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,

DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);

ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);

//adb shell dumpsys meminfo 注册内存信息Service

ServiceManager.addService(“meminfo”, new MemBinder(this), /* allowIsolated= */ false,

DUMP_FLAG_PRIORITY_HIGH);

//adb shell dumpsys gfxinfo 注册GraphicsBinder

ServiceManager.addService(“gfxinfo”, new GraphicsBinder(this));

//adb shell dumpsys dbinfo 注册 DbBinder

ServiceManager.addService(“dbinfo”, new DbBinder(this));

if (MONITOR_CPU_USAGE) {

//adb shell dumpsys cpuinfo 注册CPU 信息Service

ServiceManager.addService(“cpuinfo”, new CpuBinder(this),

/* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);

}

//adb shell dumpsys packages permissions 注册 系统权限 信息Service

ServiceManager.addService(“permission”, new PermissionController(this));

ServiceManager.addService(“processinfo”, new ProcessInfoService(this));

// 获取包名为 android 的应用信息,framework-res.apk

ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(

“android”, STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY);

mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader());

synchronized (this) {

ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0);

app.persistent = true;

app.pid = MY_PID;

app.maxAdj = ProcessList.SYSTEM_ADJ;

app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);

synchronized (mPidsSelfLocked) {

mPidsSelfLocked.put(app.pid, app);

}

//更新 进程 Lru 算法 ,以及oom_adj 值

updateLruProcessLocked(app, false, null);

updateOomAdjLocked();

}

}

// 当 packager manager 启动并运行时开始监听 app 操作

mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,

new IAppOpsCallback.Stub() {

@Override public void opChanged(int op, int uid, String packageName) {

if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {

if (mAppOpsService.checkOperation(op, uid, packageName)

!= AppOpsManager.MODE_ALLOWED) {

runInBackgroundDisabled(uid);

}

}

}

});

}

六、安装系统 Provider

===========================================================================

通过 SystemServer.java 类中的startOtherServices()方法mActivityManagerService.installSystemProviders();调用 AMS中的installSystemProviders方法。

下面我们看看installSystemProviders 方法的主要功能

public final void installSystemProviders() {

List providers;

synchronized (this) {

ProcessRecord app = mProcessNames.get(“system”, SYSTEM_UID);

providers = generateApplicationProvidersLocked(app);

if (providers != null) {

for (int i=providers.size()-1; i>=0; i–) {

ProviderInfo pi = (ProviderInfo)providers.get(i);

// 1&1=1 1&0=0 0&0=0

if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {

Slog.w(TAG, "Not installing system proc provider " + pi.name

  • “: not system .apk”);

//移除非系统 app

providers.remove(i);

}

}

}

}

… …

mConstants.start(mContext.getContentResolver());

// 创建 CoreSettingsObserver ,监控核心设置的变化

mCoreSettingsObserver = new CoreSettingsObserver(this);

// 创建 FontScaleSettingObserver,监控字体的变化

mFontScaleSettingObserver = new FontScaleSettingObserver();

// 创建 DevelopmentSettingsObserver 监控开发者选项

mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();

GlobalSettingsToPropertiesMapper.start(mContext.getContentResolver());

//对外公布 settings provider

RescueParty.onSettingsProviderPublished(mContext);

//mUsageStatsService.monitorPackages();

}

七、AMS.systemReady准备完成

=================================================================================

SystemServer 中的 AMS.systemReady 主要完成以下功能

  1. 确保系统Service已经准备完成。

  2. ActivityManager 引导启动完成。

  3. 开始监听NativeCrash。

  4. WebView 准备完毕,方便三方apk 调用。

  5. 启动车载相关的服务。

  6. 启动SystemUI。

  7. 确保 MakeNetworkManagementService 准备完成。

  8. 启动 Watchdog 看门狗程序。

  9. 等待所有的app数据预加载,然后,可以启动三方app。

  10. Location、telephony、输入法、Media、MMS、Daemon等相关的Service已经运行并准备好

SystemServer中具体实现代码情况下文:

public final class SystemServer {

… …

/**

  • The main entry point from zygote.

*/

public static void main(String[] args) {

new SystemServer().run();

}

public SystemServer() {

… …

}

private void run() {

… …

//启动系统服务

startOtherServices();

… …

}

private void startOtherServices() {

… …

// 系统准备完毕,可以让第三代码调用

mActivityManagerService.systemReady(() -> {

Slog.i(TAG, “Making services ready”);

traceBeginAndSlog(“StartActivityManagerReadyPhase”);

// ActivityManager 准备完毕

mSystemServiceManager.startBootPhase(

SystemService.PHASE_ACTIVITY_MANAGER_READY);

traceEnd();

traceBeginAndSlog(“StartObservingNativeCrashes”);

try {

// 开始监听 Native Crash

mActivityManagerService.startObservingNativeCrashes();

} catch (Throwable e) {

reportWtf(“observing native crashes”, e);

}

traceEnd();

//WebView 准备好,方便三方apk 调用

final String WEBVIEW_PREPARATION = “WebViewFactoryPreparation”;

Future<?> webviewPrep = null;

if (!mOnlyCore && mWebViewUpdateService != null) {

webviewPrep = SystemServerInitThreadPool.get().submit(() -> {

Slog.i(TAG, WEBVIEW_PREPARATION);

TimingsTraceLog traceLog = new TimingsTraceLog(

SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);

traceLog.traceBegin(WEBVIEW_PREPARATION);

ConcurrentUtils.waitForFutureNoInterrupt(mZygotePreload, “Zygote preload”);

mZygotePreload = null;

mWebViewUpdateService.prepareWebViewInSystemServer();

traceLog.traceEnd();

}, WEBVIEW_PREPARATION);

}

// 启动车载相关的服务

if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {

traceBeginAndSlog(“StartCarServiceHelperService”);

mSystemServiceManager.startService(CAR_SERVICE_HELPER_SERVICE_CLASS);

traceEnd();

}

// 启动SystemUI

traceBeginAndSlog(“StartSystemUI”);

try {

startSystemUi(context, windowManagerF);

} catch (Throwable e) {

reportWtf(“starting System UI”, e);

}

traceEnd();

// MakeNetworkManagementService 准备完成

traceBeginAndSlog(“MakeNetworkManagementServiceReady”);

try {

if (networkManagementF != null) networkManagementF.systemReady();

} catch (Throwable e) {

reportWtf(“making Network Managment Service ready”, e);

}

CountDownLatch networkPolicyInitReadySignal = null;

if (networkPolicyF != null) {

networkPolicyInitReadySignal = networkPolicyF

.networkScoreAndNetworkManagementServiceReady();

}

traceEnd();

traceBeginAndSlog(“MakeIpSecServiceReady”);

try {

if (ipSecServiceF != null) ipSecServiceF.systemReady();

} catch (Throwable e) {

reportWtf(“making IpSec Service ready”, e);

}

traceEnd();

traceBeginAndSlog(“MakeNetworkStatsServiceReady”);

try {

if (networkStatsF != null) networkStatsF.systemReady();

} catch (Throwable e) {

reportWtf(“making Network Stats Service ready”, e);

}

traceEnd();

traceBeginAndSlog(“MakeConnectivityServiceReady”);

try {

if (connectivityF != null) connectivityF.systemReady();

} catch (Throwable e) {

reportWtf(“making Connectivity Service ready”, e);

}

traceEnd();

traceBeginAndSlog(“MakeNetworkPolicyServiceReady”);

try {

if (networkPolicyF != null) {

networkPolicyF.systemReady(networkPolicyInitReadySignal);

}

} catch (Throwable e) {

reportWtf(“making Network Policy Service ready”, e);

}

traceEnd();

// 启动 Watchdog 看门狗程序

traceBeginAndSlog(“StartWatchdog”);

Watchdog.getInstance().start();

traceEnd();

//等待所有的app数据预加载

mPackageManagerService.waitForAppDataPrepared();

// It is now okay to let the various system services start their

// third party code…

traceBeginAndSlog(“PhaseThirdPartyAppsCanStart”);

// confirm webview completion before starting 3rd party

if (webviewPrep != null) {

ConcurrentUtils.waitForFutureNoInterrupt(webviewPrep, WEBVIEW_PREPARATION);

}

// 三方 app 已准备好,可以启动

mSystemServiceManager.startBootPhase(

SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);

traceEnd();

traceBeginAndSlog(“MakeLocationServiceReady”);

try {

// 定位服务已经运行,并准备好

if (locationF != null) locationF.systemRunning();

} catch (Throwable e) {

reportWtf(“Notifying Location Service running”, e);

}

traceEnd();

traceBeginAndSlog(“MakeCountryDetectionServiceReady”);

try {

if (countryDetectorF != null) countryDetectorF.systemRunning();

} catch (Throwable e) {

reportWtf(“Notifying CountryDetectorService running”, e);

}

traceEnd();

traceBeginAndSlog(“MakeNetworkTimeUpdateReady”);

try {

if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();

} catch (Throwable e) {

reportWtf(“Notifying NetworkTimeService running”, e);

}

traceEnd();

traceBeginAndSlog(“MakeCommonTimeManagementServiceReady”);

try {

if (commonTimeMgmtServiceF != null) {

commonTimeMgmtServiceF.systemRunning();

}

} catch (Throwable e) {

reportWtf(“Notifying CommonTimeManagementService running”, e);

}

traceEnd();

traceBeginAndSlog(“MakeInputManagerServiceReady”);

try {

// 输入法相关的Service已经运行并准备好

// TODO(BT) Pass parameter to input manager

if (inputManagerF != null) inputManagerF.systemRunning();

} catch (Throwable e) {

reportWtf(“Notifying InputManagerService running”, e);

}

traceEnd();

traceBeginAndSlog(“MakeTelephonyRegistryReady”);

try {

// telephony相关的Service已经运行并准备好

if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();

} catch (Throwable e) {

reportWtf(“Notifying TelephonyRegistry running”, e);

}

traceEnd();

traceBeginAndSlog(“MakeMediaRouterServiceReady”);

try {

// media 相关的Service已经运行并准备好

if (mediaRouterF != null) mediaRouterF.systemRunning();

} catch (Throwable e) {

reportWtf(“Notifying MediaRouterService running”, e);

}

traceEnd();

traceBeginAndSlog(“MakeMmsServiceReady”);

try {

// 短信 相关的Service已经运行并准备好

if (mmsServiceF != null) mmsServiceF.systemRunning();

} catch (Throwable e) {

reportWtf(“Notifying MmsService running”, e);

}

traceEnd();

traceBeginAndSlog(“IncidentDaemonReady”);

try {

// TODO: Switch from checkService to getService once it’s always

// in the build and should reliably be there.

final IIncidentManager incident = IIncidentManager.Stub.asInterface(

ServiceManager.getService(Context.INCIDENT_SERVICE));

if (incident != null) incident.systemRunning();

} catch (Throwable e) {

reportWtf(“Notifying incident daemon running”, e);

}

traceEnd();

}, BOOT_TIMINGS_TRACE_LOG);

}

… …

}

AMS 中 SystemReady 主要作用有以下功能

  1. kill 掉非persistent app进程。

  2. 检测 Setting中的一些配置索引。

  3. 注册低电量模式的关闭。

  4. 启动 persistent app。

  5. 启动Home Activity 比如Launcher

  6. 恢复显示TopActivity

具体实现代码如下:

public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {

traceLog.traceBegin(“PhaseActivityManagerReady”);

synchronized(this) {

if (mSystemReady) {

// 如果我们完成所有的receiver 调用,然后需要调用 SystemServer 的boot phase 方法

if (goingCallback != null) {

goingCallback.run();

}

return;

}

… …

// 确保 VR 、多用户控制、最近任务列表、最近app操作已完成

mVrController.onSystemReady();

mUserController.onSystemReady();

mRecentTasks.onSystemReadyLocked();

mAppOpsService.systemReady();

mSystemReady = true;

}

… …

// 非 persistent 应用都会添加到kill list中

ArrayList procsToKill = null;

synchronized(mPidsSelfLocked) {

for (int i=mPidsSelfLocked.size()-1; i>=0; i–) {

ProcessRecord proc = mPidsSelfLocked.valueAt(i);

// 判断是否是persistent 应用

if (!isAllowedWhileBooting(proc.info)){

if (procsToKill == null) {

procsToKill = new ArrayList();

}

procsToKill.add(proc);

}

}

}

synchronized(this) {

if (procsToKill != null) {

for (int i=procsToKill.size()-1; i>=0; i–) {

ProcessRecord proc = procsToKill.get(i);

//杀掉非persistent 应用

removeProcessLocked(proc, true, false, “system update done”);

}

}

// Now that we have cleaned up any update processes, we

// are ready to start launching real processes and know that

// we won’t trample on them any more.

mProcessesReady = true;

}

Slog.i(TAG, “System now ready”);

EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_AMS_READY,

SystemClock.uptimeMillis());

synchronized(this) {

… …

// 检索Settings的一些信息

// 比如多用户、画中画、分屏、fullScreen等

retrieveSettings();

final int currentUserId = mUserController.getCurrentUserId();

synchronized (this) {

// 读取授权的URI 权限文件

readGrantedUriPermissionsLocked();

}

final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);

if (pmi != null) {

// 注册并更新低电量模式广播

pmi.registerLowPowerModeObserver(ServiceType.FORCE_BACKGROUND_CHECK,

state -> updateForceBackgroundCheck(state.batterySaverEnabled));

updateForceBackgroundCheck(

pmi.getLowPowerState(ServiceType.FORCE_BACKGROUND_CHECK).batterySaverEnabled);

} else {

Slog.wtf(TAG, “PowerManagerInternal not found.”);

}

… …

synchronized (this) {

// 启动 persistent app

startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_AWARE);

// Start up initial activity.

mBooting = true;

// Enable home activity for system user, so that the system can always boot. We don’t

// do this when the system user is not setup since the setup wizard should be the one

// to handle home activity in this case.

if (UserManager.isSplitSystemUser() &&

Settings.Secure.getInt(mContext.getContentResolver(),

Settings.Secure.USER_SETUP_COMPLETE, 0) != 0) {

ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);

try {

AppGlobals.getPackageManager().setComponentEnabledSetting(cName,

PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0,

UserHandle.USER_SYSTEM);

} catch (RemoteException e) {

throw e.rethrowAsRuntimeException();

}

}

// 启动 Home Activity ,比如 通过 StartHomeActivity(inten,ainfo,myReason) 启动Launcher

// 启动 Launcher方法请见 步骤八

startHomeActivityLocked(currentUserId, “systemReady”);

long ident = Binder.clearCallingIdentity();

try {

// 发送 ACTION_USER_STARTED 的广播

Intent intent = new Intent(Intent.ACTION_USER_STARTED);

intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY

| Intent.FLAG_RECEIVER_FOREGROUND);

写在最后

对程序员来说,很多技术的学习都是“防御性”的。也就是说,我们是在为未来学习。我们学习新技术的目的,或是为了在新项目中应用,或仅仅是为了将来的面试。但不管怎样,一定不能“止步不前”,不能荒废掉。

![
[]


文章以下内容会给出阿里与美团的面试题(答案+解析)、面试题库、Java核心知识点梳理等

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

}

}

// 启动 Home Activity ,比如 通过 StartHomeActivity(inten,ainfo,myReason) 启动Launcher

// 启动 Launcher方法请见 步骤八

startHomeActivityLocked(currentUserId, “systemReady”);

long ident = Binder.clearCallingIdentity();

try {

// 发送 ACTION_USER_STARTED 的广播

Intent intent = new Intent(Intent.ACTION_USER_STARTED);

intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY

| Intent.FLAG_RECEIVER_FOREGROUND);

写在最后

对程序员来说,很多技术的学习都是“防御性”的。也就是说,我们是在为未来学习。我们学习新技术的目的,或是为了在新项目中应用,或仅仅是为了将来的面试。但不管怎样,一定不能“止步不前”,不能荒废掉。

[外链图片转存中…(img-hm7NZ2Ly-1714258160135)]

[外链图片转存中…(img-YgRaDTF4-1714258160135)]
[]

[外链图片转存中…(img-sF91LKiz-1714258160136)]
[外链图片转存中…(img-RkRixFRf-1714258160136)]

文章以下内容会给出阿里与美团的面试题(答案+解析)、面试题库、Java核心知识点梳理等

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
android电子签名,屏幕上手写签名 搜集很多资料,项目能够完美运行,拿来即可使用,整理备用 应用场景: 就是在屏幕是用手写字,然后保存成图片,简称就是电子签名,可以用在手机上签合同,等技术。 使用技术: 使用了接口回调,绘制完成之后给用户去操作 自定义Dialog,在dialog上画图,给dialog设置主题,dialog的宽高设置为手机屏幕的宽高充满全屏 注意在计算高度的时候记得减去通知栏的高度 注意把画布的背景设置为白色,不然点击缩略图查看的时候是全黑色 参考如下资料: http://hbxflihua.iteye.com/blog/1512765 http://www.jianshu.com/p/c4f017603413 https://github.com/gcacace/android-signaturepad http://download.csdn.net/download/mmlinux/7687091 1,android 如何让自定义dialog的宽度跟屏幕的宽度一样? 在你dialog.show();后面加上 WindowManager windowManager = getWindowManager(); Display display = windowManager.getDefaultDisplay(); WindowManager.LayoutParams lp = dialog.getWindow().getAttributes(); lp.width = (int)(display.getWidth()); //设置宽度 dialog.getWindow().setAttributes(lp); 2,如何获取通知栏的高度? public int getStatusBarHeight() { int result = 0; int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = getResources().getDimensionPixelSize(resourceId); } return result; } 3,如何对图片进行压缩? http://blog.sina.com.cn/s/blog_497f718e0100sl13.html http://www.cnblogs.com/Soprano/articles/2577152.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值