Android-Bluetooth

132 篇文章 96 订阅

这篇文章将所有的android蓝牙的东西都集合在这里,主要分一下几个部分,所有分析都基于Bluedroid:
(1) Android Bluedroid蓝牙基本框架
(2) 主要目录及文件结构
(3) 蓝牙主要的支持的profile
(4) 蓝牙基本流程分析:蓝牙启动, 蓝牙搜索, 蓝牙连接, 蓝牙传输文件,蓝牙通话和播放音乐

(1) Android Bluedroid蓝牙基本框架

 

bluetooth.jpg

 

根据上图,代码调用过程一般基于下面的流程
APP->Framework---->(通过Binder)BluetoothService-->(通过JNI)调用Native Bluetooth->bluedroid->hci

(2) 模块的主要目录以及文件结构:
APP:
Settings

packages/apps/Settings/src/com/android/settings/bluetooth
主要文件说明:
BluetoothEnabler.java   界面上蓝牙开启、关闭的开关就是它了, 
BluetoothSettings.java  主界面,用于管理配对和连接设备
LocalBluetoothManager.java  提供了蓝牙API上的简单调用接口,这里只是开始。
CachedBluetoothDevice.java   描述蓝牙设备的类,对BluetoothDevice的再封装
BluetoothPairingDialog.java  那个配对提示的对话框

Phone

packages/apps/services/Telecom/src/com/android/server/telecom/BluetoothManager.java
这里是通话调用蓝牙Audio, 
connectBluetoothAudio
disconnectBluetoothAudio

Framework:

/frameworks/base/core/java/android/bluetooth/
BluetoothA2dp.java A2DP的功能实现
BluetoothAdapter.java 蓝牙action的定义,虚拟设备属性以及操作方法
BluetoothAudioGateway.java 蓝牙语音网关
BluetoothClass.java 蓝牙设备类型的定义
BluetoothDevice.java 蓝牙设备属性
BluetoothDevicePicker.java 定义远程蓝牙设备的特性,比如需要认证,设备类型
BluetoothHeadset.java 定义蓝牙headset功能的属性以及接口
BluetoothInputStream.java 蓝牙流接口的实现(输入流)
BluetoothOutputStream.java 蓝牙流接口的实现(输出流)
BluetoothServerSocket.java 蓝牙socket服务端具备的方法
BluetoothSocket.java 蓝牙socket的封装
BluetoothUuid.java 蓝牙uuid的定义以及uuid的解析

(3) 蓝牙主要的支持的profile
HFP/HSP
A2DP
AVRCP
PBAP
DUN
OPP
PAN

(4) 蓝牙基本流程分析:
a. 蓝牙开启:
首先是BluetoothManagerService的启动, 这个服务也是在SystemServer中启动的, 在开机后,SystemServer进程由zygote进程fork出来后,会启动一系列的service,这里面就有BluetoothManagerService:

frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
    BluetoothManagerService bluetooth = null;
    //初始化一个BluetoothManagerService;
    bluetooth = new BluetoothManagerService(context);
    ServiceManager.addService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, bluetooth);
}

看一下BluetoothManagerService的构造函数
BluetoothManagerService(Context context) {
    loadStoredNameAndAddress();  //读取蓝牙打开默认名称和地址
        if (isBluetoothPersistedStateOn()) {  //判断蓝牙是否打开
            mEnableExternal = true;  //如果蓝牙打开,这个设为true,等boot完后,开启enable 蓝牙的过程
        }
}

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
  else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
                synchronized(mReceiver) {
                    if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) {
                        //Enable
                        if (DBG) Log.d(TAG, "Auto-enabling Bluetooth.");
                        sendEnableMsg(mQuietEnableExternal);
                    }
                }
}
广播中接收Boot是否完成, 完成后调用sendEnable来开启蓝牙,mEnableExternal这个值就在这里用到了。

b. 点击蓝牙开关开启蓝牙流程
Settings菜单点击菜单开启蓝牙:
APP层路径packages/apps/Settings/src/com/android/settings/bluetooth
BluetoothSettings.java

     public void onActivityCreated(Bundle savedInstanceState) {
        mSwitchBar = activity.getSwitchBar();  //界面上的swtichbar控件
        mBluetoothEnabler = new BluetoothEnabler(activity, mSwitchBar);  //传送给BluetoothEnabler, 通过BluetoothEnable控制
        mBluetoothEnabler.setupSwitchBar();
    }

BluetoothEnabler.java

     private final LocalBluetoothAdapter mLocalAdapter;   //localBluetoothAdapter
     public BlutoothEnable(){  //构造函数,定义了localBluetoothManager和LocalBluetoothAdapter
        LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);
        mLocalAdapter = manager.getBluetoothAdapter();   
     }
     public void resume(Context context) {
        // Bluetooth state is not sticky, so set it manually
        handleStateChanged(mLocalAdapter.getBluetoothState());  //设置初始状态
        mSwitchBar.addOnSwitchChangeListener(this);             //设置switch监听
    }
     public void onSwitchChanged(Switch switchView, boolean isChecked) {
        if (mLocalAdapter != null) {
            mLocalAdapter.setBluetoothEnabled(isChecked);  //
        }   
    }

LocalBluetoothAdapter.java

 public void setBluetoothEnabled(boolean enabled) {
    private final BluetoothAdapter mAdapter;
    boolean success = enabled? mAdapter.enable(): mAdapter.disable();  调用BluetoothAdapter的enable/disable
}

通过调用BluetoothAdapter的函数,就调用到了framework的接口:
LocalBluetoothAdapter.java

 private final IBluetoothManager mManagerService;
 public static synchronized BluetoothAdapter getDefaultAdapter() {
   IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);    //这边会返回一个managerService的代理 
 }
 public boolean enable() {
   return mManagerService.enable(ActivityThread.currentPackageName());  //通过代理调用BluetoothManagerService的enable.
  }

BluetoothManagerService

 class BluetoothManagerService extends IBluetoothManager.Stub{
    private final BluetoothHandler mHandler;
    private IBluetooth mBluetooth;
    public boolean enable(String callingPackage) {
      //这里会获取蓝牙的权限
      synchronized(mReceiver) {
            //通过发送消息的方式enable
            sendEnableMsg(false);     
      }  
    }

    private void sendEnableMsg(boolean quietMode) {
        //这边回去发送消息, quietMode决定打开后是否需要AutoConnect,这边是false
        mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE,
                             quietMode ? 1 : 0, 0));
    }
   //Handler是BluetoothManagerService的内部类,这边处理接收的命令
    private class BluetoothHandler extends Handler {
    public void handleMessage(Message msg) {
        case MESSAGE_ENABLE:
        //调用handleEnable函数处理
        handleEnable(msg.arg1 == 1);  
        break;
        case MESSAGE_BLUETOOTH_SERVICE_CONNECTED:
            mBluetooth = IBluetooth.Stub.asInterface(service);
         break;
    }
    }

private void handleEnable(boolean quietMode) {
    synchronized(mConnection) {
                try {
                    //传进来是false,所以走enable,即开启会重新连接
                    if (!mQuietEnable) {
                        //调用mBluetooth的enable,mBluetooth上面有定义,是IBluetooth的代理, server端在package/apps/Bluetooth里面
                        if(!mBluetooth.enable()) { 
                        }
                    }
                    else {
                        if(!mBluetooth.enableNoAutoConnect()) {
                        }
                    }
                } catch (RemoteException e) {
                    Log.e(TAG,"Unable to call enable()",e);
                }
      }
    }
}

之前调用IBluetooth的service端的部分:
AdapterService.java

 static {  //这里添加了so库,并定义了一些 native函数
        System.load("/system/lib/libbluetooth_jni.so");
        classInitNative();
 }
 private static class AdapterServiceBinder extends IBluetooth.Stub {
    public boolean enable() {
        return service.enable(); //这边又要看这个service,这个service即是 AdapterService
    }
}

boolean enable() {
        return enable (false);
}
public synchronized boolean enable(boolean quietMode) {
   //这边会发送一个消息到状态机AdapaterState
   Message m =
                 mAdapterStateMachine.obtainMessage(AdapterState.USER_TURN_ON);
   mAdapterStateMachine.sendMessage(m);
}

void processStart() {
  //启动蓝牙的部分, 如果是第一次开启的话, 走下面的路径, 继续想state状态机发送AdapterState.STARTED状态
        if (!mProfilesStarted && supportedProfileServices.length >0) {
            //Startup all profile services
            setProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_ON);
        }else {
            debugLog("processStart() - Profile Services alreay started");
            mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.STARTED));
        }
}


AdapterState.java 状态机文件,负责跟AdapterService的沟通

 private class OffState extends State {
        public boolean processMessage(Message msg) {
                switch(msg.what) {
                   case USER_TURN_ON:
                     adapterService.processStart();  //adpaterService调用processStart函数
                   break;
  }
}

最后,看一下状态机里面的函数

 private class PendingCommandState extends State {
        public boolean processMessage(Message msg) {
    boolean ret = adapterService.enableNative(); //这边就调用到jni的地方了
}

看一下简单的流程图, 大体一致,但也稍微有不同的地方

 

Bluetooth_on.jpg

 

这边只到了JNI,下面JNI如何调用CPP的呢~
这边调用packages/apps/Bluetooth/jni下面的jni文件:
com_android_bluetooth_btservice_AdapterService.cpp

static jboolean enableNative(JNIENv* env, jobject obj){
  int ret = sBluetoothInterface->enable();
}

在然后好像真的没有了, 后面进入到hal,调用Bluedroid协议栈了
hardware/libhardware/include/hardware/bluetooth.h

typedef struct{
  int(*enable)(void);
}bt_interface_t;

Bluedroid协议栈位于external/bluetooth/bluedroid
bluetooth.c

static int enable(void){
  return btif_enable_bluetooth(); 
}

btif_core.c

bt_status_t btif_enable_bluetooth(void)
{
    bte_main_enable();
}

bte_main.c
void bte_main_enable(){
BTE_Init();
GKI_create_task(); //创建GKI task
bte_hci_enable();
GKI_run();
}

c. 蓝牙搜索:
APP测:
BluetoothSettings.java

//蓝牙状态改变监听
public void onBluetoothStateChanged(int bluetoothState) { 
    super.onBluetoothStateChanged(bluetoothState);
    updateContent(bluetoothState);     
}

private void updateContent(int bluetoothState) {
    case BluetoothAdapter.STATE_ON:  //蓝牙开启完成后,开始扫描
        if (!mInitialScanStarted) {
          startScanning();
        }
    break;
    case BluetoothAdapter.STATE_TURNING_ON:  //蓝牙开启中,将scanStart设置为false
        mInitialScanStarted = false;
    break;
}
这边的BluetoothAdapter.STATE有4个状态:
STATE_OF  =  10;
STATE_TURNING_ON = 11;
STATE_ON = 12;
STATE_TURNING_OFF = 13;

private void startScanning() {
        mLocalAdapter.startScanning(true);//调用localAdapter的startScanning函数
}

LocalBluetoothAdapter.java

void startScanning(boolean force) {
    // If we are playing music, don't scan unless forced.
    //这里回去判断a2dp profile,如果正在a2dp存在,则不能扫描
    A2dpProfile a2dp = mProfileManager.getA2dpProfile();
    if (a2dp != null && a2dp.isA2dpPlaying()) {
      return;
    }
    A2dpSinkProfile a2dpSink = mProfileManager.getA2dpSinkProfile();
     if ((a2dpSink != null) && (a2dpSink.isA2dpPlaying())){
      return;
      }
     if (mAdapter.startDiscovery()) {  //调用framwork的接口
        mLastScan = System.currentTimeMillis();
      }
}

Framework:
BluetoothAdapter.java

private IBluetooth mService;  //这么快就用到IBluetooth了。。。
BluetoothAdapter(IBluetoothManager managerService) {
        try {
            //构造函数里直接就获取mService,这个mService跟managerService是一样一样的,是IBluetooth的客户端代理,跟Service(AdapterService)通讯
            mService = managerService.registerAdapter(mManagerCallback);
        } catch (RemoteException e) {Log.e(TAG, "", e);}
}

public boolean startDiscovery() {
    if (mService != null) return mService.startDiscovery();  //开始扫描
}

还是来看一下mService是如何获取的
BluetoothManagerService.java

public IBluetooth registerAdapter(IBluetoothManagerCallback callback){
        synchronized(mConnection) {
            return mBluetooth;  //就是返回BluetoothManagerService的mBluetooth
        }
}

Service (Service的目录在packages/apps/bluetooth里面)
AdapterService.java

private static class AdapterServiceBinder extends IBluetooth.Stub {
    public boolean startDiscovery() {
        AdapterService service = getService();
        return service.startDiscovery();
    }
}
这里有个内部类AdapterServiceBinder,是远程通讯的服务端:
直接调用了AdapterService的startDiscovery函数
boolean startDiscovery() {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                       "Need BLUETOOTH ADMIN permission");
        //do not allow new connections with active multicast
        A2dpService a2dpService = A2dpService.getA2dpService();
        if (a2dpService != null &&
                a2dpService.isMulticastOngoing(null)) {
            Log.i(TAG,"A2dp Multicast is Ongoing, ignore discovery");
            return false;
        }
        return startDiscoveryNative();
}
这边也是简单粗暴的,直接返回startDiscoveryNative(),调用JNI函数

JNI
JNI的文件需要到packages/apps/bluetooth/jni下面,adapterservice对应的jni文件为
com_android_bluetooth_btservice_AdapterService.cpp

static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {
    int ret = sBluetoothInterface->start_discovery();
}

HAL
hal主要看hanrdware/libhardware/include/hardware/bluetooth.h

int (*start_discovery)(void);

//Bluedroid协议栈部分
bluetooth.c

static int statrt_discovery(void){
   retun btif_dm_start_discovery();
}

btif_dm.c

这个函数start device discovery/inquiry
bt_status_t btif_dm_start_discovery(void){
  /*find nearby devices*/
BTA_DmSearch(...);
}

d. 蓝牙扫描结果返回
扫描结果反馈到上层则是从协议栈->hal->bluetoothService->APP
external/bluetooth/bluedroid/btif/src/
btif_dm.c

static void btif_dm_search_devices_evt (UINT16 event, char *p_param)
{
        case BTA_DM_INQ_RES_EVT:
                /* Callback to notify upper layer of device */
                //调用注册的callback, device_found_cb
                HAL_CBACK(bt_hal_cbacks, device_found_cb,num_properties, properties);
    break;
}

HAL
bluetooth.h

//
typedef void (*device_found_callback)(int num_properties,
                                         bt_property_t *properties);

JNI
com_android_bluetooth_btservice_AdapterService.cpp

 static void device_found_callback(int num_properties, bt_property_t *properties) {
    if (sJniCallbacksObj) {
        callbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback, addr);
    }
}

Service

 //deviceFoundCallback
RemoteDevices.java
    void deviceFoundCallback(byte[] address) {
        Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);    //send broadcast
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.putExtra(BluetoothDevice.EXTRA_CLASS,
                new BluetoothClass(Integer.valueOf(deviceProp.mBluetoothClass)));
        intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);
        intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);

        mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);
    }
这里调用发送广播,Action_Found给到上层

APP(Settings)
BluetoothEventManager.java

     BluetoothEventManager(LocalBluetoothAdapter adapter,
            CachedBluetoothDeviceManager deviceManager, Context context) {
        mLocalAdapter = adapter;
        mDeviceManager = deviceManager;
        mAdapterIntentFilter = new IntentFilter();
        mProfileIntentFilter = new IntentFilter();
        mHandlerMap = new HashMap<String, Handler>();
        mContext = context;

        // Bluetooth on/off broadcasts
        addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());

        // Discovery broadcasts
        addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
        addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
        addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
        addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
        addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());

        // Pairing broadcasts
        addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
        addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler());

        // Fine-grained state broadcasts
        addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
        addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());

        // Dock event broadcasts
        addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());

        mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter);

        setDefaultBtName();
    }
这边注册广播接收器
addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());

private class DeviceFoundHandler implements Handler {
        public void onReceive(Context context, Intent intent,
                BluetoothDevice device) {
            if (cachedDevice == null) {
                cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
                Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
                        + cachedDevice);
                // callback to UI to create Preference for new device
                dispatchDeviceAdded(cachedDevice);
            }
        }
    }
DeviceFoundHandler接收广播,并调用dispatchDeviceAdded(cachedDevice);刷新上层显示

private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
        synchronized (mCallbacks) {
            for (BluetoothCallback callback : mCallbacks) {
                callback.onDeviceAdded(cachedDevice);
            }
        }
}
调用callback.onDeviceAdded(cachedDevice); 回调返给上层

ListFragment DeviceListPreferenceFragment.java

public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
  、、、
}
DeviceListPreferenceFragment继承BluetoothCallback接口,重写onDeviceAdded.

蓝牙连接:

蓝牙OPP文件传输:
起点自函数packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java

if (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {
    if (!isBluetoothAllowed()) {
        Intent in = new Intent(this, BluetoothOppBtErrorActivity.class);
        in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        in.putExtra("title", this.getString(R.string.airplane_error_title));
        in.putExtra("content", this.getString(R.string.airplane_error_msg));
        startActivity(in);
        finish();
        return;
    }
    if (action.equals(Intent.ACTION_SEND)) {
        Thread t = new Thread(new Runnable() {
            public void run() {
                BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
                    .saveSendingFileInfo(type,stream.toString(), false);
                //Done getting file info..Launch device picker and finish this activity
                launchDevicePicker();
                finish();
            }
        });
        t.start();
        return;
    }
}
isBluetoothAllowed()判断Bluetooth是否允许,如果不允许,弹出Error.
如果返回true,则开启个线程, 
launchDevicePicker 加载蓝牙列表

 private final void launchDevicePicker() {
        if (!BluetoothOppManager.getInstance(this).isEnabled()) {
            if (V) Log.v(TAG, "Prepare Enable BT!! ");
            Intent in = new Intent(this, BluetoothOppBtEnableActivity.class);
            in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(in);
        } else {
            if (V) Log.v(TAG, "BT already enabled!! ");
            Intent in1 = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
            in1.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
            in1.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
            in1.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
                    BluetoothDevicePicker.FILTER_TYPE_TRANSFER);
            in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE,
                    Constants.THIS_PACKAGE_NAME);
            in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS,
                    BluetoothOppReceiver.class.getName());
            if (V) {Log.d(TAG,"Launching " +BluetoothDevicePicker.ACTION_LAUNCH );}
            startActivity(in1);
        }
}
BluetoothOppManager.getInstance(this).isEnabled()判断蓝牙是否打开,如果打开,则跳转到列表

DevicePickerFragment.java

     @Override
    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
        mLocalAdapter.stopScanning();
        LocalBluetoothPreferences.persistSelectedDeviceInPicker(
                getActivity(), mSelectedDevice.getAddress());
        if ((btPreference.getCachedDevice().getBondState() ==
                BluetoothDevice.BOND_BONDED) || !mNeedAuth) {
            sendDevicePickedIntent(mSelectedDevice);
            finish();
        } else {
            super.onDevicePreferenceClick(btPreference);
        }
    }
    private void sendDevicePickedIntent(BluetoothDevice device) {
        mDeviceSelected = true;
        Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        if (mLaunchPackage != null && mLaunchClass != null) {
            intent.setClassName(mLaunchPackage, mLaunchClass);
        }
        getActivity().sendBroadcast(intent);
    }
这边发送一个selectd的广播,搜关键字查询广播接收。

BluetoothOppReceiver.java

public void onReceive(Context context, Intent intent) {
    else if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {
    // Insert transfer session record to database
            mOppManager.startTransfer(remoteDevice);
    }
}


 public void startTransfer(BluetoothDevice device) {
        if (V) Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);
        InsertShareInfoThread insertThread;

            insertThread = new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile,
                    mUriOfSendingFile, mNameOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles,
                    mIsHandoverInitiated);

        }

        insertThread.start();
    }
private class InsertShareInfoThread extends Thread {
    public void run() {
        insertSingleShare();
    }
}
上面步骤即是接收广播后的操作~最终调用insertSingleShare来操作,后面的步骤略掉。

蓝牙接电话与听音乐:



作者:唐僧不爱洗头_f7b5
链接:https://www.jianshu.com/p/b68b281f9020
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值