Android 9.0 网络评分之---NetworkFactory

一、网络评分框架图
在这里插入图片描述

二、NetworkFactory 与ConnectivityService连接过程

拿以太网为例,以太网的网口检测类EthernetTracker 会在自己的构造函数中将以太网类型的networkfactory 注册到ConnectivityService中。

EthernetTracker(Context context, Handler handler) {
    mHandler = handler;

    // The services we use.
    IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
    mNMService = INetworkManagementService.Stub.asInterface(b);

    // Interface match regex.
    mIfaceMatch = context.getResources().getString(
            com.android.internal.R.string.config_ethernet_iface_regex);

    // Read default Ethernet interface configuration from resources
    final String[] interfaceConfigs = context.getResources().getStringArray(
            com.android.internal.R.array.config_ethernet_interfaces);
    for (String strConfig : interfaceConfigs) {
        parseEthernetConfig(strConfig);
    }

    mConfigStore = new EthernetConfigStore();

    NetworkCapabilities nc = createNetworkCapabilities(true /* clear default capabilities */);
    mFactory = new EthernetNetworkFactory(handler, context, nc);
    mFactory.register();
}

NetworkFactory 中会的register 方法如下:
public void register() {
if (DBG) log(“Registering NetworkFactory”);
if (mMessenger == null) {
mMessenger = new Messenger(this);
ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG);
}
}

ConnectivityService 中的registerNetworkFactory 方法。会为当前NetworkFactory创建一个NetworkFactoryInfo ,并通过mHandler 发送EVENT_REGISTER_NETWORK_FACTORY

从这里我们看到,在ConnectivityService中,利用当前的NetworkFactory创建NetworkFactoryInfo对象,该对象保存了当前网络连接的name、Messenger对象,并且为其创建了AsyncChannel。 接下来继续看注册过程:
@Override
public void registerNetworkFactory(Messenger messenger, String name) {
enforceConnectivityInternalPermission();
NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel());
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
}

NetworkFactoryInfo是ConnectivityService 的一个内部类,会保存当前的NetworkFactory 的messenger和asyncChannel 对象,这里name 就是EthernetNetworkFactory 里面定义的类型NETWORK_TYPE = “Ethernet”
private static class NetworkFactoryInfo {
public final String name;
public final Messenger messenger;
public final AsyncChannel asyncChannel;

    public NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel) {
        this.name = name;
        this.messenger = messenger;
        this.asyncChannel = asyncChannel;
    }
}

mHandler 是ConnectivityService 的一个内部类 InternalHandler
private class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case EVENT_REGISTER_NETWORK_FACTORY: {
                handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj);
                break;
            }

到这里我们发现,ConnectivityService将当前的NetworkFactoryInfo保存到mNetworkFactoryInfos中,然后向当前的NetworkFactory发起AsyncChannel单向通道申请(AsyncChannel相关介绍见这里)。
申请成功时,将会向当前的mTrackerHandler发送CMD_CHANNEL_HALF_CONNECTED消息:
原文链接:https://blog.csdn.net/u010961631/article/details/48971431
这里面是通过AsyncChannel 让ConnectivityService 的mTrackerHandler 和NetworkFactory连接通信。
三、ConnectivityService 通过AsyncChannel与NetworkFactory 连接的详细过程
AsyncChannel 的客户端(ConnectivityService)发起connect请求,和NetworkFactory连接。所以AsyncChannel 就架起了客户端ConnectivityService 的mTrackerHandler与服务端NetworkFactory之间的桥梁。连接成功后,ConnectivityService会收到AsyncChannel.CMD_CHANNEL_HALF_CONNECTED 的message。当客户端接收到AsyncChannel.CMD_CHANNEL_HALF_CONNECTED消息后,说明当前的单项通道建立成功。此时的客户端可以通过sAsyncChannel的sendMessage()方法向服务端发送消息了。
原文链接:https://blog.csdn.net/u010961631/article/details/48179305
AsyncChannel 的例子:https://blog.csdn.net/u010961631/article/details/48179305

作为客户端,如果想要创建与服务端之间的AsyncChannel,需要做以下几个准备:
1、获取服务端的Messenger对象,该对象其实就是利用服务端的Handler构建的Messenger;
2、创建客户端自己的Handler对象;
3、创建AsyncChannel对象;
4、通过AsyncChannel对象,连接当前的Handler和服务端的Messenger,从而申请连接。

3.1 首先ConnectvityService 发起connect 连接 NetworkFactory
private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
if (DBG) log("Got NetworkFactory Messenger for " + nfi.name);
mNetworkFactoryInfos.put(nfi.messenger, nfi);
nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
}

3.2 连接成功后客户端ConnectivityService会收到服务端NetworkFactory发来的AsyncChannel.CMD_CHANNEL_HALF_CONNECTED

AsyncChannel的connected 方法实际上连接的是传入的客户端handler 和服务端messager。
代码路径:frameworks/base/core/java/com/android/internal/util/AsyncChannel.java

3.3 connect 方法如下,这里会执行connected()replyHalfConnected(STATUS_SUCCESSFUL)
public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
if (DBG) log(“connect srcHandler to the dstMessenger E”);

    // We are connected
    connected(srcContext, srcHandler, dstMessenger);

    // Tell source we are half connected
    replyHalfConnected(STATUS_SUCCESSFUL);

    if (DBG) log("connect srcHandler to the dstMessenger X");
}
    public void connected(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
    if (DBG) log("connected srcHandler to the dstMessenger  E");

    // Initialize source fields
    mSrcContext = srcContext;
    mSrcHandler = srcHandler;
    mSrcMessenger = new Messenger(mSrcHandler);

    // Initialize destination fields
    mDstMessenger = dstMessenger;
    if (DBG) log("connected srcHandler to the dstMessenger X");
}

3.4 AsyncChannnel 的 replyHalfConnected 主要回复CMD_CHANNEL_HALF_CONNECTED MSG和添加msg.arg1为STATUS_SUCCESSFUL的参数
private void replyHalfConnected(int status) {
Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_HALF_CONNECTED);
msg.arg1 = status;
msg.obj = this;
msg.replyTo = mDstMessenger;
if (!linkToDeathMonitor()) {
// Override status to indicate failure
msg.arg1 = STATUS_BINDING_UNSUCCESSFUL;
}

    mSrcHandler.sendMessage(msg);
}

3.5 单向通道建立,ConnectivityService 收到AsyncChannel.STATUS_SUCCESSFUL 后发送网络请求android.net.NetworkFactory.CMD_REQUEST_NETWORK
private void handleAsyncChannelHalfConnect(Message msg) {
AsyncChannel ac = (AsyncChannel) msg.obj;
if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
if (VDBG) log(“NetworkFactory connected”);
// A network factory has connected. Send it all current NetworkRequests.
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
if (nri.request.isListen()) continue;
NetworkAgentInfo nai = getNetworkForRequest(nri.request.requestId);
ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
(nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
}
} else {
loge(“Error connecting NetworkFactory”);
mNetworkFactoryInfos.remove(msg.obj);
}
} else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
if (VDBG) log(“NetworkAgent connected”);
// A network agent has requested a connection. Establish the connection.
mNetworkAgentInfos.get(msg.replyTo).asyncChannel.
sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
} else {
loge(“Error connecting NetworkAgent”);
NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo);
if (nai != null) {
final boolean wasDefault = isDefaultNetwork(nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.remove(nai.network.netId);
mNetIdInUse.delete(nai.network.netId);
}
// Just in case.
mLegacyTypeTracker.remove(nai, wasDefault);
}
}
}
}

3.5 NetworkFactory 收到CMD_REQUEST_NETWORK请求。
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case CMD_REQUEST_NETWORK: {
handleAddRequest((NetworkRequest)msg.obj, msg.arg1);
break;
}

3.6 handleAddRequest创建一个NetworkRequestInfo 对象将请求的ID保存起来并保存分数。
@VisibleForTesting
protected void handleAddRequest(NetworkRequest request, int score) {
NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
if (n == null) {
if (DBG) log(“got request " + request + " with score " + score);
n = new NetworkRequestInfo(request, score);
mNetworkRequests.put(n.request.requestId, n);
} else {
if (VDBG) log(“new score " + score + " for exisiting request " + request);
n.score = score;
}
if (VDBG) log(” my score=” + mScore + “, my filter=” + mCapabilityFilter);

    evalRequest(n);
}

3.7 然后评价该网络的分数是否可以用于上网
private void evalRequest(NetworkRequestInfo n) {
if (VDBG) log(“evalRequest”);
// we always NEED each network for any network request
//if (n.requested == false && n.score < mScore &&
//n.request.networkCapabilities.satisfiedByNetworkCapabilities(
//mCapabilityFilter) && acceptRequest(n.request, n.score)) {
if (VDBG) log(" needNetworkFor");
needNetworkFor(n.request, n.score);
n.requested = true;
//} else if (n.requested == true &&
//(n.score > mScore || n.request.networkCapabilities.satisfiedByNetworkCapabilities(
//mCapabilityFilter) == false || acceptRequest(n.request, n.score) == false)) {
//if (VDBG) log(" releaseNetworkFor");
//releaseNetworkFor(n.request);
//n.requested = false;
//} else {
//if (VDBG) log(" done");
//}
}

该逻辑就是整个网络评价系统最关键的地方,如果NetworkRequestInfo没有被requested过,并且其分值(n.score)小于当前NetworkFactory自己的分值(mScore),那么就说明,当前NetworkFactory所处的网络优先级高于其他网络的优先级,就会触发当前NetworkFactory所在网络的needNetworkFor()流程,也就是连接建立流程,并将标记NetworkRequestInfo.requested=true。
当NetworkRequestInfo被requested过(也就是当前网络被needNetworkFor过),此时如果再次收到请求,并且携带的新score大于当前NetworkFactory所处网络的mScore,那么就说明当前NetworkFactory所在网络优先级已经不是最高,需要将其releaseNetworkFor掉,并标记NetworkRequestInfo.requested=false。
对于初始化流程来说,由于NetworkRequestInfo是刚才在handleAddRequest新创建的,所以其requested状态必然为false,而且我们前面提到,ConnectivityService发送CMD_REQUEST_NETWORK时携带的分值参数为0,并且对于数据网络来说,其mScore=50,因此此时的判定状态将会是:n.requested=false AND n.score < mScore。
也就是说,对于数据网络环境初始化过程来说,将会满足第一个if判断,进入needNetworkFor流程,也就是触发数据网络的建立。
至此,NetworkFactory注册流程结束。

四、ConnectivityService 的mTrackerHandler
mTrackerHandler是ConnectivityService定义的一个内部类Handler。里面主要处理maybeHandleAsyncChannelMessage(NetworkFactory), NetworkMonitor, NetworkAgentInfo和NetworkAgent 的消息。

    @Override
    public void handleMessage(Message msg) {
        if (!maybeHandleAsyncChannelMessage(msg) &&
                !maybeHandleNetworkMonitorMessage(msg) &&
                !maybeHandleNetworkAgentInfoMessage(msg)) {
            maybeHandleNetworkAgentMessage(msg);
        }
    }


// must be stateless - things change under us.
private class NetworkStateTrackerHandler extends Handler {
    public NetworkStateTrackerHandler(Looper looper) {
        super(looper);
    }

    private boolean maybeHandleAsyncChannelMessage(Message msg) {
        switch (msg.what) {
            default:
                return false;
            case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
                handleAsyncChannelHalfConnect(msg);
                break;
            }
            case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
                NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
                if (nai != null) nai.asyncChannel.disconnect();
                break;
            }
            case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
                handleAsyncChannelDisconnected(msg);
                break;
            }
        }
        return true;
    }

    private void maybeHandleNetworkAgentMessage(Message msg) {
        NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
        if (nai == null) {
            if (VDBG) {
                log(String.format("%s from unknown NetworkAgent", eventName(msg.what)));
            }
            return;
        }

        switch (msg.what) {
            case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
                final NetworkCapabilities networkCapabilities = (NetworkCapabilities) msg.obj;
                if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) ||
                        networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) ||
                        networkCapabilities.hasCapability(NET_CAPABILITY_FOREGROUND)) {
                    Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
                }
                updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
                break;
            }
            case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: {
                handleUpdateLinkProperties(nai, (LinkProperties) msg.obj);
                break;
            }
            case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {
                NetworkInfo info = (NetworkInfo) msg.obj;
                updateNetworkInfo(nai, info);
                break;
            }
            case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: {
                Integer score = (Integer) msg.obj;
                if (score != null) updateNetworkScore(nai, score.intValue());
                break;
            }
            case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: {
                if (nai.everConnected && !nai.networkMisc.explicitlySelected) {
                    loge("ERROR: already-connected network explicitly selected.");
                }
                nai.networkMisc.explicitlySelected = true;
                nai.networkMisc.acceptUnvalidated = (boolean) msg.obj;
                break;
            }
            case NetworkAgent.EVENT_PACKET_KEEPALIVE: {
                mKeepaliveTracker.handleEventPacketKeepalive(nai, msg);
                break;
            }
        }
    }

    private boolean maybeHandleNetworkMonitorMessage(Message msg) {
        switch (msg.what) {
            default:
                return false;
            case NetworkMonitor.EVENT_NETWORK_TESTED: {
                final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
                if (nai == null) break;

                final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
                final boolean wasValidated = nai.lastValidated;
                final boolean wasDefault = isDefaultNetwork(nai);

                final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";

                if (DBG) {
                    final String logMsg = !TextUtils.isEmpty(redirectUrl)
                             ? " with redirect to " + redirectUrl
                             : "";
                    log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg);
                }
                if (valid != nai.lastValidated) {
                    if (wasDefault) {
                        metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
                                SystemClock.elapsedRealtime(), valid);
                    }
                    final int oldScore = nai.getCurrentScore();
                    nai.lastValidated = valid;
                    nai.everValidated |= valid;
                    updateCapabilities(oldScore, nai, nai.networkCapabilities);
                    // If score has changed, rebroadcast to NetworkFactories. b/17726566
                    if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                    if (valid) handleFreshlyValidatedNetwork(nai);
                }
                updateInetCondition(nai);
                // Let the NetworkAgent know the state of its network
                Bundle redirectUrlBundle = new Bundle();
                redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
                nai.asyncChannel.sendMessage(
                        NetworkAgent.CMD_REPORT_NETWORK_STATUS,
                        (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
                        0, redirectUrlBundle);
                if (wasValidated && !nai.lastValidated) {
					nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, null, null);
                    handleNetworkUnvalidated(nai);
                } else if(!wasValidated && nai.lastValidated) {
					nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
					handleNetworkValidated(nai);
                } else if(!wasValidated && !nai.lastValidated) {
                    if(!nai.everFirstUnvalidated) {
                        log(nai.name() + " ever FRIST Unvalidated and notify SUSPENDED.");
                        nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, null, null);
                        handleNetworkUnvalidated(nai);
                        nai.everFirstUnvalidated = true;
                    }
                }
                break;
            }
            case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
                final int netId = msg.arg2;
                final boolean visible = toBool(msg.arg1);
                final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
                // If captive portal status has changed, update capabilities or disconnect.
                if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
                    final int oldScore = nai.getCurrentScore();
                    nai.lastCaptivePortalDetected = visible;
                    nai.everCaptivePortalDetected |= visible;
                    if (nai.lastCaptivePortalDetected &&
                        Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
                        if (DBG) log("Avoiding captive portal network: " + nai.name());
                        nai.asyncChannel.sendMessage(
                                NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
                        teardownUnneededNetwork(nai);
                        break;
                    }
                    updateCapabilities(oldScore, nai, nai.networkCapabilities);
                }
                if (!visible) {
                    mNotifier.clearNotification(netId);
                } else {
                    if (nai == null) {
                        loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
                        break;
                    }
                    if (!nai.networkMisc.provisioningNotificationDisabled) {
                        mNotifier.showNotification(netId, NotificationType.SIGN_IN, nai, null,
                                (PendingIntent) msg.obj, nai.networkMisc.explicitlySelected);
                    }
                }
                break;
            }
            case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
                final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
                if (nai == null) break;

                updatePrivateDns(nai, (PrivateDnsConfig) msg.obj);
                break;
            }
        }
        return true;
    }

    private int getCaptivePortalMode() {
        return Settings.Global.getInt(mContext.getContentResolver(),
                Settings.Global.CAPTIVE_PORTAL_MODE,
                Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
    }

    private boolean maybeHandleNetworkAgentInfoMessage(Message msg) {
        switch (msg.what) {
            default:
                return false;
            case NetworkAgentInfo.EVENT_NETWORK_LINGER_COMPLETE: {
                NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj;
                if (nai != null && isLiveNetworkAgent(nai, msg.what)) {
                    handleLingerComplete(nai);
                }
                break;
            }
        }
        return true;
    }

    @Override
    public void handleMessage(Message msg) {
        if (!maybeHandleAsyncChannelMessage(msg) &&
                !maybeHandleNetworkMonitorMessage(msg) &&
                !maybeHandleNetworkAgentInfoMessage(msg)) {
            maybeHandleNetworkAgentMessage(msg);
        }
    }
}

博客链接:https://blog.csdn.net/u010961631/article/details/48971431

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
很抱歉,我无法提供关于"df -h"命令在Android 9.0上的输出。因为该命令用于查看文件系统的磁盘使用情况,而作为一个AI助手,我无法执行命令并提供实时的输出结果。但是,我可以告诉你一些关于Android 9.0的信息。 在Android 9.0中,进程管理方面相比Android 5.0有一些改变。Android 9.0引入了一种名为"Project Treble"的新架构,这使得系统和厂商定制的代码更加分离,使得系统更新更加容易和快速。这意味着在Android 9.0中,一些系统分区的内容可能被移动到了更加独立的分区,例如vendor.img分区,这是供厂商使用的一个分区,用于存储厂商定制的代码和驱动程序。 此外,Android 9.0还引入了更多的分区概念,例如动态分区(Dynamic Partitions)。动态分区允许系统根据需要动态调整分区大小,以便在不重新分区的情况下更新系统。这种分区方式可以提高系统更新的效率和速度[3]。 总之,Android 9.0在进程管理方面进行了一些改进,并引入了新的分区概念,以提高系统的灵活性和更新效率。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Android 9.0 & 5.0 进程对比](https://blog.csdn.net/weixin_37927853/article/details/108947979)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [android 9.0 中关于OTA 升级的注意事项](https://blog.csdn.net/wed110/article/details/85691521)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mrsongs的心情杂货铺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值