Android 车载应用开发指南 - CarService 详解

一 概述

CarService 是车载 Android 操作系统 Android Automotive OS(下文简介 AAOS)的核心服务之一,所有车载相关的应用都需要通过 CarService 来查询、控制整车的状态,不仅仅是车辆控制,实际上 CarService 几乎就是整个车载 Framework 最核心的组件。


二 CarService 架构

2.1 简介

AAOS 并没有大刀阔斧的改变 Android 原有的整体架构,几乎所有的核心服务 (AMS、WMS、PMS) 与手机并无区别,采用的是同一套源代码,所以我们可以将 AAOS 理解为 Android OS + Automotive Services + Automotive APPs

传统的手机系统加上相关的汽车服务,构成了现在的 AAOS,而其中 CarService 就是提供汽车相关功能的最主要的模块。

AAOS 定义了标准的硬件抽象层 HAL(Hardware Abstraction Layer) 来规范各个子系统与 Framework 的调用接口,并且通过 CarService 以及相关的 Car API 对上层应用提供标准编程接口。

ac4b24d8ff406d19087f2762d5dc7f2e.jpeg

车载 HAL 与 AAOS 架构:

  • Car App: 包括 OEM 和第三方开发的 App

  • Car API: 内有包含 CarSensorManager 在内的 API。位于 /packages/services/Car/car-lib

  • CarService: 系统中与车相关的服务。位于 /packages/services/Car

  • Vehicle HAL: 汽车的硬件抽象层描述。位于 /hardware/interfaces/automotive/vehicle/2.0/default/impl/vhal_v2_0

2.2 CarService 组成

CarService 源码位置:/packages/services/Car

其目录结构如下所示:

.
├── Android.mk
 ├── apicheck.mk
 ├── apicheck_msg_current.txt
 ├── apicheck_msg_last.txt
 ├── car-cluster-logging-renderer    //LoggingClusterRenderingService 继承 InstrumentClusterRenderingService
 ├── car-default-input-service   //按键消息处理
 ├── car-lib         //提供给汽车 App 特有的接口,许多定制的模块都在这里实现,包括 Sensor,HVAC,Cabin,ActiveParkingAssiance,Diagnostic,Vendor 等
 ├── car-maps-placeholder    //地图软件相关
 ├── car_product         //系统编译相关
 ├── car-support-lib     //android.support.car
 ├── car-systemtest-lib  //系统测试相关
 ├── car-usb-handler     //开机自启,用于管理车机 USB
 ├── CleanSpec.mk
 ├── evs  
 ├── obd2-lib
 ├── PREUPLOAD.cfg
 ├── procfs-inspector
 ├── service    //com.android.car 是一个后台运行的组件,可以长时间运行并且不需要和用户去交互的,这里即使应用被销毁,它也可以正常工作
 ├── tests
 ├── tools   //是一系列的工具,要提到的是里面的 emulator,测试需要用到的。python 写的,通过 adb 可以连接 vehicleHal 的工具,用于模拟测试
 ├── TrustAgent
 └── vehicle-hal-support-lib

Android 通信模式通常基于 C/S 模式,即有客户端和服务端,每个服务有对应的代理对象(比如 ActivityManager 相对服务 AMS,就是客户端)。

对于 CarService 也是采用了 C/S 模式Car App 并不会直接通过 CarService 的实例调用相关功能,而是通过对应的 Car API 完成对服务的调用。这里的 CarService 就是服务端,Car API 就是客户端。

Android 原生 CarService 包含了许多功能服务:Car ** Service(C/S 模式中的服务端)它们与 HAL 层的 VehicleHAL 通信,进而通过车载总线(例如 CAN 总线)与车身进行通讯,同时它们还通过 Car API:Car ** Manger(C/S 模式中的客户端)为应用层的 Car App 提供接口,从而让 App 能够实现对车身的控制与状态的显示。

f9ed5d558d7dab0c848a64f8eaa7151f.jpeg

Car***Manager:packages/services/Car/car-lib/src/android/car/

Car***Service:packages/services/Car/service/src/com/android/car/

以下列举 CarService 中核心服务:

Service 端功能Client 端
AppFocusService管理同类应用焦点的服务CarAppFocusManager
CarAudioService汽车音频服务CarAudioManager
CarPackageManagerService汽车包管理服务CarPackageManager
CarDiagnosticService汽车诊断服务CarDiagnosticManager
CarPowerManagerService汽车电源管理服务CarPowerManager
IInstrumentClusterManagerServcie仪表服务IInstrumentClusterManager
CarProjecitonService投屏服务CarProjecitonManager
VmsSubscriberService车辆地图服务VmsSubscriberManager
CarBluetoothService汽车蓝牙服务CarBluetoothManager
CarStorageMonitoringService汽车存储监控服务CarStorageMonitoringManager
CarDrivingStateService汽车驾驶状态服务CarDrivingStateManager
CarUXRestrictionsService汽车用户体验限制服务CarUXRestrictionsManager
CarConfigurationService汽车配置服务CarConfigurationManager
CarTrustedDeviceService授信设备管理CarTrustAgentEnrollmentManager
CarMediaService媒体管理服务CarMediaManager
CarBugreportManagerService错误报告服务CarBugreportManager
2.3 使用 CarService

说明:本文源码分析基于版本:android-12.0.0_r3

前文提到,CarService 需要通过 Car API 为应用层提供接口,所以应用开发者只需要知道如何使用 Car API

第一步:判断平台是否支持车载功能

APP 层在调用 Car API 之前首先会调用 PMS 中的 hasSystemFeature() 方法判断设备是否支持车载功能

if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
     .....
 }

源码路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

@GuardedBy("mAvailableFeatures")
     final ArrayMap<String, FeatureInfo> mAvailableFeatures;
 
     @Override
     public boolean hasSystemFeature(String name, int version) {
         // allow instant applications
         synchronized (mAvailableFeatures) {
             final FeatureInfo feat = mAvailableFeatures.get(name);
             if (feat == null) {
                 return false;
             } else {
                 return feat.version >= version;
             }
         }
     }

mAvailableFeatures 里面的内容是通过读取/system/etc/permissions下面的 xml 文件(对应 SDK 的位置—frameworks/native/data/etc 下的 XML 文件中的 feature 字段)

源码路径:frameworks/native/data/etc/car_core_hardware.xml

<permissions>
     <!-- Feature to specify if the device is a car -->
     <feature name="android.hardware.type.automotive" />
     .....
 </permission>

源码路径:frameworks/native/data/etc/android.hardware.type.automotive.xml

<!-- These features determine that the device running android is a car. -->
 <permissions>
     <feature name="android.hardware.type.automotive" />
 </permissions>

第二步:创建 Car 对象,获取 Manager

Car 作为汽车平台最高等级的 API(packages/services/Car/car-lib/src/android/car/Car.java),为外界提供汽车所有服务和数据的访问

  1. 通过createCar()方法新建 Car 实例

  2. 成功连接时可以通过getCarManager方法获取相关的 Manager 实例。比如 Hvac 通过 getCarManager 方法获取了一个 CarHvacManager,当获取到 manager 后就可以进行相关操作

// 创建 Car 实例
     Car carApiClient = Car.createCar(context);
     // 获取 CarHvacManager
     CarHvacManager manager = (CarHvacManager) mCarApiClient.getCarManager(Car.HVAC_SERVICE);
// 调用 disconnect() 断开连接
      carApiClient.disconnect();

三 CarService 实现原理

想要弄清楚CarService实现方式,首先需要搞明白CarService的启动流程。

CarService 启动流程主要分为以下四个步骤:

  1. SystemServer 启动 CarServiceHelperService 服务

  2. 在调用 startService() 后,CarServiceHelperServiceonStart() 方法通过 bindService 的方式启动 CarService(一个系统级别的 APK,位于 system/priv-app)

  3. 启动 CarService 后首先调用 onCreate(),创建 ICarImpl 对象并初始化,在此时创建了一系列 Car 相关的核心服务,并遍历 init 初始化

  4. 然后调用 onBind 将该 ICarImpl 对象返回给 CarServiceHelperServiceCarServiceHelperService 在内部的一个 Binder 对象 ICarServiceHelperImpl传递给 CarService,建立双向跨进程

3.1 启动 CarServiceHelperService 服务

SystemServer会在startOtherServices()方法中让SystemServiceManager先通过反射的形式创建出StartCarServiceHelperService对象。

源码路径:frameworks/base/services/java/com/android/server/SystemServer.java

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
     ...
     // 仅在 automotive 中启动
     if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
         t.traceBegin("StartCarServiceHelperService");
         final SystemService cshs = mSystemServiceManager
             .startService(CAR_SERVICE_HELPER_SERVICE_CLASS);
         if (cshs instanceof Dumpable) {
             mDumper.addDumpable((Dumpable) cshs);
         }
         if (cshs instanceof DevicePolicySafetyChecker) {
             dpms.setDevicePolicySafetyChecker((DevicePolicySafetyChecker) cshs);
         }
         t.traceEnd();
     }
     ...
 }

然后在SystemServiceManager中调用StartCarServiceHelperServiceonStart()方法。

CarServiceHelperServiceCarService的 SystemService 端的配套服务。

源码路径:frameworks/base/services/core/java/com/android/server/SystemServiceManager.java

public SystemService startService(String className) {
     final Class<SystemService> serviceClass = loadClassFromLoader(className,
             this.getClass().getClassLoader());
     return startService(serviceClass);
 }
 
 public void startService(@NonNull final SystemService service) {
     // Register it.
 mServices.add(service);
     long time = SystemClock.elapsedRealtime();
     try {
         service.onStart();
     } catch (RuntimeException ex) {
         throw new RuntimeException("Failed to start service " + service.getClass().getName()
                 + ": onStart threw an exception", ex);
     }
     warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart");
 }
3.2 绑定 CarService 服务

源码路径:frameworks/opt/car/services/src/com/android/internal/car/CarServiceHelperService.java

private static final String CAR_SERVICE_INTERFACE = "android.car.ICar";
 
     @Override
     public void onStart() {
         EventLog.writeEvent(EventLogTags.CAR_HELPER_START);
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_REBOOT);
         filter.addAction(Intent.ACTION_SHUTDOWN);
         mContext.registerReceiverForAllUsers(mShutdownEventReceiver, filter, null, null);
         mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener);
         mCarWatchdogDaemonHelper.connect();
         Intent intent = new Intent();
         intent.setPackage("com.android.car");  // 绑定包名,设置广播仅对该包有效
         intent.setAction(CAR_SERVICE_INTERFACE);  // 绑定 action,表明想要启动能够响应设置的这个 action 的活动,并在清单文件 AndroidManifest.xml 中设置 action 属性
         // 绑定后回调
         if (!mContext.bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,
                 mHandler, UserHandle.SYSTEM)) {
             Slogf.wtf(TAG, "cannot start car service");
         }
         loadNativeLibrary();
     }

源码路径:packages/services/Car/service/AndroidManifest.xml

sharedUserId 是系统级别的,类似 SystemUI,它编译出来同样是一个 APK 文件

设备文件路径:/system/priv-app/CarService/CarService.apk

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         package="com.android.car"
         coreApp="true"
         android:sharedUserId="android.uid.system"> 
     ......
     <application android:label="@string/app_title"
          android:directBootAware="true"
          android:allowBackup="false"
          android:persistent="true">
 
         <service android:name=".CarService"
              android:singleUser="true"
              android:exported="true">
             <intent-filter>
                 <action android:name="android.car.ICar"/>
             </intent-filter>
         </service>
         ......
     </application>
3.3 CarService 初始化

CarService进入启动时序后,会在onCreate()方法中进行一系列自身的初始化操作,步骤如下:

1)通过 HIDL 接口获取到 HAL 层的 IHwBinder 对象IVehicle,与 AIDL 的用法类似,必须持有 IHwBinder 对象我们才可以与 Vehicle HAL 层进行通信。

2)创建 ICarImpl 对象,并调用init方法,它就是ICar.aidl接口的实现类,我们需要通过它才能拿到其他的 Service 的 IBinder 对象。

3)将ICar.aidl的实现类添加到 ServiceManager 中。

4)设定 SystemProperty,将CarService设定为创建完成状态,只有包含CarService在内的所有的核心 Service 都完成初始化,才能结束开机动画并发送开机广播。

源码路径:packages/services/Car/service/src/com/android/car/CarService.java

@Override
     public void onCreate() {
         LimitedTimingsTraceLog initTiming = new LimitedTimingsTraceLog(CAR_SERVICE_INIT_TIMING_TAG,
                 Trace.TRACE_TAG_SYSTEM_SERVER, CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);
         initTiming.traceBegin("CarService.onCreate");
 
         initTiming.traceBegin("getVehicle");
         // 获取 hal 层的 Vehicle service
         mVehicle = getVehicle();
         initTiming.traceEnd();
         ...
         //创建 ICarImpl 实例
         mICarImpl = new ICarImpl(this,
                 mVehicle,
                 SystemInterface.Builder.defaultSystemInterface(this).build(),
                 mVehicleInterfaceName);
         //然后调用 ICarImpl 的 init 初始化方法
         mICarImpl.init();
 
         linkToDeath(mVehicle, mVehicleDeathRecipient);
         //将该 service 注册到 ServiceManager
         ServiceManager.addService("car_service", mICarImpl);
         //设置 boot.car_service_created 属性
         SystemProperties.set("boot.car_service_created", "1");
 
         super.onCreate();
 
         initTiming.traceEnd(); // "CarService.onCreate"
     }
 
     @Nullable
     private static IVehicle getVehicle() {
         final String instanceName = SystemProperties.get("ro.vehicle.hal", "default");
 
         try {
             //该 service 启动文件 hardware/interfaces/automotive/vehicle/2.0/default/android.hardware.automotive.vehicle@2.0-service.rc
             return android.hardware.automotive.vehicle.V2_0.IVehicle.getService(instanceName);
         } catch (RemoteException e) {
             Slog.e(CarLog.TAG_SERVICE, "Failed to get IVehicle/" + instanceName + " service", e);
         } catch (NoSuchElementException e) {
             Slog.e(CarLog.TAG_SERVICE, "IVehicle/" + instanceName + " service not registered yet");
         }
         return null;
     }

接着再看ICarImpl的实现,如下所示:

1)创建各个核心服务对象

2)把服务对象缓存到 CarLocalServices 中,这里主要是为了方便 Service 之间的相互访问

源码路径:/packages/services/Car/service/src/com/android/car/ICarImpl.java

@VisibleForTesting
     ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
             String vehicleInterfaceName,
             @Nullable CarUserService carUserService,
             @Nullable CarWatchdogService carWatchdogService,
             @Nullable ICarPowerPolicySystemNotification powerPolicyDaemon) {
         ...
         mContext = serviceContext;
         mSystemInterface = systemInterface;
         CarLocalServices.addService(SystemInterface.class, mSystemInterface);
         //创建 VehicleHal 对象
         mHal = constructWithTrace(t, VehicleHal.class,
                 () -> new VehicleHal(serviceContext, vehicle));
         ...
         // 创建核心服务对象,并缓存到 CarLocalServices
         mCarPropertyService = constructWithTrace(t, CarPropertyService.class, () -> new CarPropertyService(serviceContext, mHal.getPropertyHal()));
         mCarDrivingStateService = constructWithTrace(t, CarDrivingStateService.class,() -> new CarDrivingStateService(serviceContext, mCarPropertyService));
         mCarUXRestrictionsService = constructWithTrace(t, CarUxRestrictionsManagerService.class, () -> new CarUxRestrictionsManagerService(serviceContext, mCarDrivingStateService, mCarPropertyService));
         ...
 
         // 将创建的服务对象依次添加到一个 list 中保存起来
         List<CarServiceBase> allServices = new ArrayList<>();
         allServices.add(mFeatureController);
         allServices.add(mCarUXRestrictionsService); // mCarUserService depends on it
         allServices.add(mCarUserService);
         allServices.add(mSystemActivityMonitoringService);
         allServices.add(mCarPowerManagementService);
         allServices.add(mCarPropertyService);
         allServices.add(mCarDrivingStateService);
         allServices.add(mCarOccupantZoneService);
         addServiceIfNonNull(allServices, mOccupantAwarenessService);
         allServices.add(mCarPackageManagerService);
         allServices.add(mCarInputService);
         allServices.add(mGarageModeService);   
         ...
     }
 
     @MainThread
     void init() {
         LimitedTimingsTraceLog t = new LimitedTimingsTraceLog(CAR_SERVICE_INIT_TIMING_TAG,
                 Trace.TRACE_TAG_SYSTEM_SERVER, CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);
 
         t.traceBegin("ICarImpl.init");
 
         t.traceBegin("VHAL.init");
         mHal.init();
         t.traceEnd();
 
         t.traceBegin("CarService.initAllServices");
         //启动的所有服务遍历调用 init 初始化(各个都继承了 CarServiceBase)
         for (CarServiceBase service : mAllServices) {
             t.traceBegin(service.getClass().getSimpleName());
             service.init();
             t.traceEnd();
         }
         t.traceEnd(); // "CarService.initAllServices"
 
         t.traceEnd(); // "ICarImpl.init"
     }

然后将上面 onCreate() 创建的 mICarImpl 对象返回:

  1. onBind() 回调方法会继续传递通过 bindService() 传递来的 intent 对象(即上面的bindServiceAsUser方法)

  2. onUnbind() 会处理传递给 unbindService() 的 intent 对象。如果 service 允许绑定,onBind() 会返回客户端与服务互相联系的通信句柄

源码路径:/packages/services/Car/service/src/com/android/car/CarService.java

@Override
     public IBinder onBind(Intent intent) {
         return mICarImpl;
     }

所以此处的 mICarImpl 会作为 IBinder 返回给CarServiceHelperService.java - bindServiceAsUser方法中的参数 mCarServiceConnection(回调)

3.4 回调 ServiceConnection

ICarImpl 初始化完毕,会作为 IBinder 返回给CarServiceHelperService.java - bindServiceAsUser方法中绑定此服务的 mCarServiceConnection(回调)

mCarServiceConnection 初始化如下:

  1. 其中返回的 ICarImpl 被保存在了 CarServiceHelperServicemCarService

  2. mCarService.transact 跨进程通信,调用 ICar.aidl 中定义的第一个方法 setCarServiceHelper

源码路径:/frameworks/opt/car/services/src/com/android/internal/car/CarServiceHelperService.java

private static final String CAR_SERVICE_INTERFACE = "android.car.ICar";
 private IBinder mCarService;
 private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl();
 
     private final ServiceConnection mCarServiceConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
             if (DBG) {
                 Slogf.d(TAG, "onServiceConnected: %s", iBinder);
             }
             handleCarServiceConnection(iBinder);
         }
 
         @Override
         public void onServiceDisconnected(ComponentName componentName) {
             handleCarServiceCrash();
         }
     };
     
     @VisibleForTesting
     void handleCarServiceConnection(IBinder iBinder) {
         synchronized (mLock) {
             if (mCarServiceBinder == iBinder) {
                 return; // already connected.
             }
             Slogf.i(TAG, "car service binder changed, was %s new: %s", mCarServiceBinder, iBinder);
             //1. 返回的 ICarImpl 被保存在了 CarServiceHelperService 的 mCarServiceBinder
             mCarServiceBinder = iBinder;
             Slogf.i(TAG, "**CarService connected**");
         }
 
         sendSetSystemServerConnectionsCall();
         ...
     }
 
     private void sendSetSystemServerConnectionsCall() {
         Parcel data = Parcel.obtain();
         data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
         data.writeStrongBinder(mHelper.asBinder());
         //将 ICarServiceHelperImpl 类型的对象作为数据跨进程传递
         data.writeStrongBinder(mCarServiceConnectedCallback.asBinder());
         IBinder binder;
         synchronized (mLock) {
             binder = mCarServiceBinder;
         }
         int code = IBinder.FIRST_CALL_TRANSACTION;
         try {
             //2. 跨进程传输
             //对端是 mCarService 即 ICarImpl,调用 binder 的 transact 进行跨进程通信
             //其 code 代表需要调用的对端方法,data 为携带的传输数据
             //FIRST_CALL_TRANSACTION  = 0x00000001,即调用对端 ICar.aidl 中定义的第一个方法 setCarServiceHelper
             if (VERBOSE) Slogf.v(TAG, "calling one-way binder transaction with code %d", code);
             // oneway void setSystemServerConnections(in IBinder helper, in IBinder receiver) = 0;
             binder.transact(code, data, null, Binder.FLAG_ONEWAY);
             if (VERBOSE) Slogf.v(TAG, "finished one-way binder transaction with code %d", code);
         }
         ...
     }

跨进程 setSystemServerConnections

@Override
     public void setSystemServerConnections(IBinder helper, IBinder receiver) {
         Bundle bundle;
         try {
             EventLog.writeEvent(EventLogTags.CAR_SERVICE_SET_CAR_SERVICE_HELPER,
                     Binder.getCallingPid());
             assertCallingFromSystemProcess();
             //将 ICarServiceHelper 的代理端保存在 ICarImpl 内部 mICarServiceHelper
             ICarServiceHelper carServiceHelper = ICarServiceHelper.Stub.asInterface(helper);
             synchronized (mLock) {
                 mICarServiceHelper = carServiceHelper;
             }
             //同时也传给了 SystemInterface
             //此时他们有能力跨进程访问 CarServiceHelperService
             mSystemInterface.setCarServiceHelper(carServiceHelper);
             mCarOccupantZoneService.setCarServiceHelper(carServiceHelper);
             mCarUserService.setCarServiceHelper(carServiceHelper);
             ...
     }
3.5 小结

CarService的启动时序如下所示:

ed4d6d2ea0deddc9601fcfb48a9f891e.jpeg


四 总结

本文讲解了CarService的总体结构、使用方法及启动流程。CarService中实现的功能非常庞大,可以说相比传统手机端的 Android 系统,AAOS 中独特且最重要的部分都在 Framework 的CarService中。

  • 首先 CarService 是一个系统级别的服务 APK,类似 SystemUI,其在开机时由 SystemServer 通过 CarServiceHelperService 启动。

  • CarServiceHelperService 通过绑定服务的方式启动 CarService,启动之后创建了一个 Binder 对象 ICarImpl,并通过 onBind 返回给 system_server 进程。

  • ICarImpl 构造方法中创建了一系列和汽车相关的核心服务,并依次启动这些服务即调用各自 init 方法。ICarImpl 返回给 CarServiceHelperService 之后,CarServiceHelperService 也将其内部的一个 Binder 对象(ICarServiceHelperImpl)传递到了 CarService 进程,自此 CarServicesystem_server 两个进程建立了双向 Binder 通信。

  • ICarImpl 返回给 CarServiceHelperService 之后,CarServiceHelperService 也将其内部的一个 Binder 对象(ICarServiceHelperImpl)传递到了 CarService 进程,自此 CarServicesystem_server 两个进程建立了双向 Binder 通信。

作者:话唠扇贝
链接:https://juejin.cn/post/7353827463632404517

关注我获取更多知识或者投稿

92fc0ea0c024e6aec8e4eff0741415dc.jpeg

a888d23af295829b36f8a5746381c08d.jpeg

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值