Android L Phone进程初始化_02

本文代码目录以高通平台Android 5.0为基础,可能与AOSP有不同之处。
* 如转帖请注明它来自这里 http://blog.csdn.net/shrimpcolo?viewmode=contents*

概述

上一篇文章主要是说明了2个问题:Phone进程初始化
1) Phone进程在创建过程中的入口以及各个关系类的用途,没有深入展开
2) RIL的实例化过程
那么本篇是在上文的基础上,针对各个关系类展开描述。目的还是只有一个,剥洋葱!!

目标

  1. 针对上文中的主要关系类,展开学习。主要是学习,在Phone进程中有什么作用,扮演什么角色。
  2. 梳理清楚几个关键的消息注册,包括MT(来电)消息注册,状态改变消息注册。这是基础,懂得了这些,会有助于分析定位问题。
    —————————————–感觉又是一个坑啊———————————————

再探PhoneGlobal

    public void onCreate() {
        if (VDBG) Log.v(LOG_TAG, "onCreate()...");

        ContentResolver resolver = getContentResolver();

        // Cache the "voice capable" flag.
        // This flag currently comes from a resource (which is
        // overrideable on a per-product basis):
        sVoiceCapable =
                getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
        // ...but this might eventually become a PackageManager "system
        // feature" instead, in which case we'd do something like:
        // sVoiceCapable =
        //   getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_VOICE_CALLS);

        if (phone == null) {
            // Initialize the telephony framework
            //这就是上篇的主要内容,初始化Phone数组,RIL数组,PhoneProxy... ...
            PhoneFactory.makeDefaultPhones(this);
            //忽略此处,(其实很重要,是跟双卡侦测有关系的)
            int numPhones = TelephonyManager.getDefault().getPhoneCount();
            if(numPhones > 1) PrimarySubSelectionController.init(this);

            // Get the default phone
            //得到默认的Phone接口,其实就是在PhoneFactory中的sProxyPhones[0]
            /* //使用默认的phoneproxy和RIL接口
              //sProxyPhone = sProxyPhones[0];
            */
            phone = PhoneFactory.getDefaultPhone();

            // Start TelephonyDebugService After the default phone is created.
            Intent intent = new Intent(this, TelephonyDebugService.class);
            startService(intent);

            //这个是高通自有的,phone 和 mPhones的关系是, phone = mPhones[0]
            mPhones = new PhoneProxy[numPhones];
            mPhones = PhoneFactory.getPhones();

             *1) CallManager是phone进程的一部分,负责消息注册 处理,上报,承上启下的作用.*
            mCM = CallManager.getInstance();
            for (Phone ph : mPhones) {
                mCM.registerPhone(ph);//注册的部分,细节需要说明
            }

            // Create the NotificationMgr singleton, which is used to display
            // status bar icons and control other status bar behavior.
             *2) 这里会稍微说明下,以后会写一篇关于通话过程中的notification显示的文章*
            notificationMgr = NotificationMgr.init(this);

            mHandler.sendEmptyMessage(EVENT_START_SIP_SERVICE);
            //获得phone type类型,这个是在new phone的时候就知道了的
            int phoneType = phone.getPhoneType();

            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
                // Create an instance of CdmaPhoneCallState and initialize it to IDLE
                cdmaPhoneCallState = new CdmaPhoneCallState();
                cdmaPhoneCallState.CdmaPhoneCallStateInit();
            }
            //以下是些其他重要的东西,电话会跟电源相关,会有自己的wakelock, 当然也跟Keygurad有联系
            // before registering for phone state changes
            mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
            mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, LOG_TAG);
            // lock used to keep the processor awake, when we don't care for the display.
            mPartialWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
                    | PowerManager.ON_AFTER_RELEASE, LOG_TAG);

            mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);

            // get a handle to the service so that we can use it later when we
            // want to set the poke lock.
            mPowerManagerService = IPowerManager.Stub.asInterface(
                    ServiceManager.getService("power"));

            // Get UpdateLock to suppress system-update related events (e.g. dialog show-up)
            // during phone calls.
            mUpdateLock = new UpdateLock("phone");

            if (DBG) Log.d(LOG_TAG, "onCreate: mUpdateLock: " + mUpdateLock);

            CallLogger callLogger = new CallLogger(this, new CallLogAsync());

            callGatewayManager = CallGatewayManager.getInstance();

            // Create the CallController singleton, which is the interface
            // to the telephony layer for user-initiated telephony functionality
            // (like making outgoing calls.)
             *3) 从上面的描述说,这个会跟outgoing calls相关,但是从现在我看到的MO流程是不一样的,并没有走callcontroller中的placecall,或许我忽略了什么??*
            callController = CallController.init(this, callLogger, callGatewayManager);

            // Create the CallerInfoCache singleton, which remembers custom ring tone and
            // send-to-voicemail settings.
            //
            // The asynchronous caching will start just after this call.
            callerInfoCache = CallerInfoCache.init(this);

            // Monitors call activity from the telephony layer
             *4) 看名字就知道了使用了监听者模式,这个是一个非常重要的设计模式,在Phone模块使用频繁,需要讲解下*
            callStateMonitor = new CallStateMonitor(mCM);

            // Bluetooth manager
            bluetoothManager = new BluetoothManager();
             *5) 是phone服务的实现类,在内部可以直接使用,第三方apps不能直接获取,需要使用TelephonyManager来间接使用该服务。*
            phoneMgr = PhoneInterfaceManager.init(this, phone);

            // Create the CallNotifer singleton, which handles
            // asynchronous events from the telephony layer (like
            // launching the incoming-call UI when an incoming call comes
            // in.)
             *6) 这个是通知的主类,所谓“通知”是指 当有phone状态变化或是有来自telephony layer层的事件的时候,会触发该类,然后通过此类通知UI层*
            notifier = CallNotifier.init(this, phone, callLogger, callStateMonitor,
                    bluetoothManager);

            // register for ICC status
            IccCard sim = phone.getIccCard();
            if (sim != null) {
                if (VDBG) Log.v(LOG_TAG, "register for ICC status");
                sim.registerForPersoLocked(mHandler, EVENT_PERSO_LOCKED, null);
            }

            // register for MMI/USSD
            mCM.registerForMmiComplete(mHandler, MMI_COMPLETE, null);

            // register connection tracking to PhoneUtils
            PhoneUtils.initializeConnectionHandler(mCM);

            // Register for misc other intent broadcasts.
            IntentFilter intentFilter =
                    new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
            intentFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
            intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
            intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
            intentFilter.addAction(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
            intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
            intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
            registerReceiver(mReceiver, intentFilter);

            //set the default values for the preferences in the phone.
            PreferenceManager.setDefaultValues(this, R.xml.network_setting, false);

            PreferenceManager.setDefaultValues(this, R.xml.call_feature_setting, false);

            // Make sure the audio mode (along with some
            // audio-mode-related state of our own) is initialized
            // correctly, given the current state of the phone.
            PhoneUtils.setAudioMode(mCM);
        }

        cdmaOtaProvisionData = new OtaUtils.CdmaOtaProvisionData();
        cdmaOtaConfigData = new OtaUtils.CdmaOtaConfigData();
        cdmaOtaScreenState = new OtaUtils.CdmaOtaScreenState();
        cdmaOtaInCallScreenUiState = new OtaUtils.CdmaOtaInCallScreenUiState();

        // XXX pre-load the SimProvider so that it's ready
        resolver.getType(Uri.parse("content://icc/adn"));

        // start with the default value to set the mute state.
        mShouldRestoreMuteOnInCallResume = false;

        // TODO: Register for Cdma Information Records
        // phone.registerCdmaInformationRecord(mHandler, EVENT_UNSOL_CDMA_INFO_RECORD, null);

        // Read HAC settings and configure audio hardware
        *声音设置,这里不关注*
        if (getResources().getBoolean(R.bool.hac_enabled)) {
            int hac = android.provider.Settings.System.getInt(phone.getContext().getContentResolver(),
                                                              android.provider.Settings.System.HEARING_AID,
                                                              0);
            AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
            audioManager.setParameter(CallFeaturesSetting.HAC_KEY, hac != 0 ?
                                      CallFeaturesSetting.HAC_VAL_ON :
                                      CallFeaturesSetting.HAC_VAL_OFF);
        }
    }

1. CallManager

 mCM = CallManager.getInstance();
            for (Phone ph : mPhones) {
                mCM.registerPhone(ph);//注册的部分,细节需要说明
            }

mCM = CallManager.getInstance();

跟踪代码发现如下内容:

    /**
     * get singleton instance of CallManager
     * @return CallManager
     */
    public static CallManager getInstance() {
        return INSTANCE;
    }

–>

private static final CallManager INSTANCE = new CallManager(); 

–>

    private CallManager() {
        mPhones = new ArrayList<Phone>();
        mRingingCalls = new ArrayList<Call>();
        mBackgroundCalls = new ArrayList<Call>();
        mForegroundCalls = new ArrayList<Call>();
        mDefaultPhone = null;
    }

说明
初始化CallManager,仅仅是初始化了 3个Calls List和1个Phone List。这里有个问题,Ringing Calls, Background Calls, Foreground Calls是什么?或者说是按照什么标准分类的?
要说明这个问题,也顺带的把后续要说明的Call的状态分类。统一在这里说明下。(注意,不要忘记上面我们的问题)

Phone: 理解为我家的座机。
座机不用的时候是空闲状态 IDLE(空闲);
来电话了,此时座机是响铃状态 RINGING(响铃);
通话过程中,此时座机状态是通话状态 OFFHOOK(摘机:通话中或是拨号中)

Call: 理解为使用我家的座机来打电话。
ACTIVE: 电话处于活动状态。
HOLDING: 电话处于保持状态,类似暂停一样,不过通话时间还是要算的哦。
DIALING: 电话处于拨号状态,使用座机拨了号码。
ALERTING: 电话处于振铃状态,这是针对拨号者而言的,对方此时的状态是INCOMING或是WAITING。
INCOMING:电话处于来电状态,手机当前处于IDLE状态时的来电是incoming
WAITING: 电话处于等待状态,当你电话处于通话过程中,再来电的时的情况。
IDEL: 电话处于空闲状态
DISCONNECTING: 电话正在挂断状态,这个是一瞬间的状态。
DISCONNECTED: 电话已经挂断,之后会转到IDLE。

Connection:理解为一个通话连接
一通电话至少有一个连接,但是一通电话可以有多个连接,电话会议就是典型例子。
电话会议允许有5个连接存在,这个是GSM协议规定的。也就是说,使用智能手机,开通多方通话功能后,手机上最多有7个连接。包括 一个电话会议(5 个Connections),一通电话(1个Connection),一通来电(Connection)。

从上面的例子就可以引申出上面的问题,7个连接。当电话会议处于活动状态的,那么电话会议属于Foreground Call, 另外的一通电话是Background Call, 来电的就是Ringing Call。

至于这三个是如何划分的,也是按照CallState的状态划分。
CallManger中的CallState分类

mCM.registerPhone(ph);

    /**
     * Register phone to CallManager
     * @param phone to be registered
     * @return true if register successfully
     */
    public boolean registerPhone(Phone phone) {
        Phone basePhone = getPhoneBase(phone);

        if (basePhone != null && !mPhones.contains(basePhone)) {

            if (DBG) {
                Rlog.d(LOG_TAG, "registerPhone(" +
                        phone.getPhoneName() + " " + phone + ")");
            }

            if (mPhones.isEmpty()) {
                mDefaultPhone = basePhone;
            }
            //mPhones, mRingingCalls, mBackgroundCalls, mForegroundCalls都是list 
            mPhones.add(basePhone);
            mRingingCalls.add(basePhone.getRingingCall());
            mBackgroundCalls.add(basePhone.getBackgroundCall());
            mForegroundCalls.add(basePhone.getForegroundCall());
            //注册重要的Phone状态
            registerForPhoneStates(basePhone);
            return true;
        }
        return false;
    }

–>

    private void registerForPhoneStates(Phone phone) {
       ... ...
        CallManagerHandler handler = mHandlerMap.get(phone);
        if (handler != null) {
            Rlog.d(LOG_TAG, "This phone has already been registered.");
            return;
        }

        // New registration, create a new handler instance and register the phone.
        handler = new CallManagerHandler();
        mHandlerMap.put(phone, handler);

        // for common events supported by all phones
        phone.registerForPreciseCallStateChanged(handler, EVENT_PRECISE_CALL_STATE_CHANGED, null);
        ... ...
        phone.registerForNewRingingConnection(handler, EVENT_NEW_RINGING_CONNECTION, null);
        phone.registerForUnknownConnection(handler, EVENT_UNKNOWN_CONNECTION, null);
        phone.registerForIncomingRing(handler, EVENT_INCOMING_RING, null);
        ... ...
        phone.registerForSuppServiceFailed(handler, EVENT_SUPP_SERVICE_FAILED, null);
        phone.registerForServiceStateChanged(handler, EVENT_SERVICE_STATE_CHANGED, null);

        // for events supported only by GSM, CDMA and IMS phone
        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
                phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA ||
                phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
            phone.setOnPostDialCharacter(handler, EVENT_POST_DIAL_CHARACTER, null);
        }

        // for events supported only by CDMA phone
        ... ...

        // for events supported only by IMS phone
        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
            phone.registerForOnHoldTone(handler, EVENT_ONHOLD_TONE, null);
        }
    }

2. Notification Manager

            // Create the NotificationMgr singleton, which is used to display
            // status bar icons and control other status bar behavior.
             2) 这里会稍微说明下,只会初始化一次,以后会直接使用
            notificationMgr = NotificationMgr.init(this);
    /**
     * Initialize the singleton NotificationMgr instance.
     *
     * This is only done once, at startup, from PhoneApp.onCreate().
     * From then on, the NotificationMgr instance is available via the
     * PhoneApp's public "notificationMgr" field, which is why there's no
     * getInstance() method here.
     */
    /* package */ static NotificationMgr init(PhoneGlobals app) {
        synchronized (NotificationMgr.class) {
            if (sInstance == null) {
                sInstance = new NotificationMgr(app);
            } else {
                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
            }
            return sInstance;
        }
    }

–>

    /**
     * Private constructor (this is a singleton).
     * @see #init(PhoneGlobals)
     */
    private NotificationMgr(PhoneGlobals app) {
        mApp = app;
        mContext = app;
        //需要使用系统的NotificationManager服务
        mNotificationManager =
                (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
        //获得StatusBarManager服务
        mStatusBarManager =
                (StatusBarManager) app.getSystemService(Context.STATUS_BAR_SERVICE);
        mUserManager = (UserManager) app.getSystemService(Context.USER_SERVICE);
        statusBarHelper = new StatusBarHelper();
    }

3. CallController

            // Create the CallController singleton, which is the interface
            // to the telephony layer for user-initiated telephony functionality
            // (like making outgoing calls.)
            // 3) 从上面的描述说,这个会跟outgoing calls相关,但是5.0的MO流程跟4.4是不一样的,并没有走callcontroller中的placecall,或许我忽略了什么??
            callController = CallController.init(this, callLogger, callGatewayManager);
    /**
     * Initialize the singleton CallController instance.
     *
     * This is only done once, at startup, from PhoneApp.onCreate().
     * From then on, the CallController instance is available via the
     * PhoneApp's public "callController" field, which is why there's no
     * getInstance() method here.
     */
    /* package */ static CallController init(PhoneGlobals app, CallLogger callLogger,
            CallGatewayManager callGatewayManager) {
        synchronized (CallController.class) {
            if (sInstance == null) {
                sInstance = new CallController(app, callLogger, callGatewayManager);
            } else {
                Log.wtf(TAG, "init() called multiple times!  sInstance = " + sInstance);
            }
            return sInstance;
        }
    }

–>

    /**
     * Private constructor (this is a singleton).
     * @see init()
     */
    private CallController(PhoneGlobals app, CallLogger callLogger,
            CallGatewayManager callGatewayManager) {
        if (DBG) log("CallController constructor: app = " + app);
        mApp = app;
        mCM = app.mCM;
        mCallLogger = callLogger;
        mCallGatewayManager = callGatewayManager;
    }

4. CallStateMonitor

            // Monitors call activity from the telephony layer
            // 4) 看名字就知道了使用了监听者模式,这个是一个非常重要的设计模式,在Phone模块使用频繁,需要讲解下
            callStateMonitor = new CallStateMonitor(mCM);

讲解这个之前,需要简单复习下观察者模式是什么样子的,强烈建议参考 程杰老师的《大话设计模式》中的 观察者模式这一章节你,值得拥有!!!

观察者模式结构图

    /**
     * Register for call state notifications with the CallManager.
     */
    private void registerForNotifications() {
        ... ...
        //callManager.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);
        //callManager.registerForPreciseCallStateChanged(this, PHONE_STATE_CHANGED, null);
        //callManager.registerForDisconnect(this, PHONE_DISCONNECT, null);
        //callManager.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);
        callManager.registerForCdmaOtaStatusChange(this, EVENT_OTA_PROVISION_CHANGE, null);
        //callManager.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null);
        callManager.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null);
        callManager.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null);
        callManager.registerForInCallVoicePrivacyOn(this, PHONE_ENHANCED_VP_ON, null);
        callManager.registerForInCallVoicePrivacyOff(this, PHONE_ENHANCED_VP_OFF, null);
        //callManager.registerForRingbackTone(this, PHONE_RINGBACK_TONE, null);
        //callManager.registerForResendIncallMute(this, PHONE_RESEND_MUTE, null);
        //callManager.registerForPostDialCharacter(this, PHONE_ON_DIAL_CHARS, null);
    }

注意到,这里注释了好多跟来电相关的注册,仅仅保留了几个其他的消息,说明来电通知这块不会使用CallStateMonitor来承担了,由其他的人士去完成了。

6. CallNotifer

注意 这里的编号是6, 是按照PhoneGlobals.onCreate()中的顺序而来, 4,6一起是为了说明观察者模式

            // Create the CallNotifer singleton, which handles
            // asynchronous events from the telephony layer (like
            // launching the incoming-call UI when an incoming call comes
            // in.)
            // 6) 这个是通知的主类,所谓“通知”是指 当有phone状态变化或是有来自telephony layer层的事件的时候,会触发该类,然后通过此类通知UI层
            notifier = CallNotifier.init(this, phone, callLogger, callStateMonitor,
                    bluetoothManager);

结合4 ,6 来具体说明下观察者模式的在CallStateMonitor 和 CallNotifer中的使用。
图形:Observer4CallStateMonitor.jpg
观察者模式在CallStateMonitor中的使用

说明:

  • 不需要关注CallManager类的内容。CallStateMonitor 和 CallNotifer都是继承自Handler,都自己重写了Handler中的handleMessage方法。

  • 这里subject 和 Observe都是一个类 Handler,CallStateMonitor当成subjectState, CallNotifer当成concreteObserver。handleMessage就是其中的Notify方法。

  • CallStateMonitor中有addListener(Handler handler),用于将外界对CallStateMonitor感兴趣的Handler (如CallNotifer), 放入自己的Handler list中。当然外界的CallNotifer也需要有CallStateMonitor这个subjectState的实例(参数变量或是成员变量),然后才能使用他的addListener方法。

  • 一旦CallStateMonitor有处理到HandleMessage到,就会以如下的方式调用:

    @Override
    public void handleMessage(Message msg) {
        if (DBG) {
            Log.d(LOG_TAG, "handleMessage(" + msg.what + ")");
        }

        for (Handler handler : registeredHandlers) {
            handler.handleMessage(msg);
        }
    }

5. PhoneInterfaceManger

            // 5) 是phone服务的实现类,在内部可以直接使用,第三方apps不能直接获取,需要使用TelephonyManager来间接使用该服务。
            phoneMgr = PhoneInterfaceManager.init(this, phone);
   /**
     * Initialize the singleton PhoneInterfaceManager instance.
     * This is only done once, at startup, from PhoneApp.onCreate().
     */
    /* package */ static PhoneInterfaceManager init(PhoneGlobals app, Phone phone) {
        synchronized (PhoneInterfaceManager.class) {
            if (sInstance == null) {
            //new PhoneInterfaceManager 实例
                sInstance = new PhoneInterfaceManager(app, phone);
            } else {
                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
            }
            return sInstance;
        }
    }

–>

//都是为了“phone”服务的运行做准备
    /** Private constructor; @see init() */
    private PhoneInterfaceManager(PhoneGlobals app, Phone phone) {
        mApp = app;
        mPhone = phone;
        mCM = PhoneGlobals.getInstance().mCM;
        mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
        //这是一个Handle,处理phone主线中的消息,主要是供系统内部的其他apps或是纯粹的第三方app来使用。
        mMainThreadHandler = new MainThreadHandler();
        carrierPrivilegeConfigs =
                PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
//这一步就是把phone服务加入ServiceManager中,是的外界可以使用【phone】服务
        publish();
    }

–>

    private void publish() {
        if (DBG) log("publish: " + this);
        ServiceManager.addService("phone", this);
    }

小结

这篇本想回答两个问题:

  1. 针对上文中的主要关系类,展开学习。主要是学习,在Phone进程中有什么作用,扮演什么角色。
  2. 梳理清楚几个关键的消息注册,包括MT(来电)消息注册,状态改变消息注册。这是基础,懂得了这些,会有助于分析定位问题。

但是分析代码,发现问题 1. 算是将清楚了,但是问题 2. 却没有说明白。而是偏向了Phone 状态, Call 状态的分类, 这个很重要
建议去查看 @seven同学写的Phone浅析,非常的详细,也非常的用心。
Android 4.4 Kitkat Phone工作流程浅析(八)__Phone状态分析

关于MT来电的消息注册在CallManager中提及了,因为有了消息的注册:

        phone.registerForNewRingingConnection(handler, EVENT_NEW_RINGING_CONNECTION, null);
        phone.registerForIncomingRing(handler, EVENT_INCOMING_RING, null); 

只要沿着 phone.registerForNewRingingConnection
->
phoneBase.registerForNewRingingConnection
保存到特定列表中。然后反向分析是谁调用了
mNewRingingConnectionRegistrants.notifyRegistrants(ar)
也就梳理清楚了消息注册流程。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值