Unity_IOS_蓝牙通信

一、开场白:

1》首先给大家推荐下关于IOS蓝牙开发的相关资料博客链接(相当不错的文章)

             1、IOS蓝牙开发_ios蓝牙4.0中心模块

             2、iOS - Bluetooth 蓝牙

2》本篇博客Unity_IOS_蓝牙通信,主要核心实现功能就是通过以上两篇文章实现。有时间的朋友可以对其好好阅读。

3》本篇博客重心是Unity与IOS交互,并且实现调用IOS与蓝牙通信功能。

二、效果展示(本案例是Unity IOS 蓝牙通信,连接带有蓝牙钢琴设备)

1)图1:打开准备连接钢琴界面进行蓝牙初始化判断

2)图2:如果打开已经打开蓝牙,即可点击搜索周围蓝牙设备。

3)搜索到,点击连接需要的蓝牙设备。可以进行通信

注释:本案例是连接带有蓝牙功能的钢琴设备。连接上之后,点击钢琴键,就会发送信息,让对应的钢琴键亮。

三、开发流程

1》实现IOS与蓝牙通信,并且实现Unity与IOS交互的IOS层代码。

  1.1)首先创建两个文件ViewController.h 和 ViewController.m。(可通过xcode或者记事本创建都行,文件名称可以不一样,关键在于后缀名。顾名思义一个头文件.h相当于接口类,一个.m文件相当于实现类)

  1.2)首先我们在头文件ViewController.h里面定义需要导入的库,以及声明unity调用ios交互的方法。

//
//  ViewController.h
//  iosPlugins
//  Created by os on 2020/4/10.
//  Copyright © 2020 os. All rights reserved.
//

#import <UIKit/UIKit.h>
//导入CoreBluetooth头文件(蓝牙的核心库)
#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController :UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>

//以下是Unity调用IOS蓝牙接口方法声明
//1、初始化
void _Init();
//2、发现设备
void FindBlueDevice();
//3、连接设备 @deviceIdentifier:蓝牙设备
void ConnectDevice(char* deviceIdentifier);
//4、关闭扫描设备
void CancelFindDevice();
//5、断开设备@deviceIdentifier:蓝牙设备
void DisconnectDevice(char* deviceIdentifier);
@end

  1.3)接下来要实现IOS与蓝牙通信的核心代码。以下代码都是在ViewController.m里实现

     1.3.1:建立中心管理类

#import "ViewController.h"
@interface ViewController(){}
@end
//1、系统蓝牙设备管理对象,可以把他理解为主设备,通过他,可以去扫描和链接外设
CBCentralManager *manager;
//用于保存被发现设备
NSMutableDictionary *peripherals;

@implementation ViewController
//实例化ViewController类(方法前+号可以理解为静态,减号可以理解为非静态或者私有)
static ViewController* gameMgr = nil;
+(ViewController*)instance
{
    if(gameMgr==nil)
    {
        gameMgr = [[ViewController alloc]init];
    }
    return gameMgr;
}
//2、初始化(建立中心管理类,设置代理)
-(id)init{
    self = [super init];
    if(self){
        peripherals = [[NSMutableDictionary alloc] init];//初始化发现设备集合
        //初始化并设置委托和线程队列,最好一个线程的参数可以为nil,默认会就main
        manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
        //执行完中心管理类,会直接进入centralManagerDidUpdateState判断蓝牙开启状态
        //[self centralManagerDidUpdateState:manager];
        
    }
    return self;
}

  1.4)通过判断中心管理器蓝牙状态,如果开启可以进行扫描外设

//3/扫描外设
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
    switch (central.state) {
        case CBManagerStateUnknown:
            NSLog(@">>>CBManager状态未知!");
            OnBlueState("01");
            break;
        case CBCentralManagerStateResetting:
            NSLog(@">>>CBCentralManager状态重置");
            OnBlueState("02");
            break;
        case CBCentralManagerStateUnsupported:
            NSLog(@">>>当前设备不支持蓝牙功能!");
            OnBlueState("03");
            break;
        case CBCentralManagerStateUnauthorized:
            NSLog(@">>>当前设备未授权打开蓝牙");
            OnBlueState("04");
            break;
        case CBCentralManagerStatePoweredOff:
            NSLog(@">>>蓝牙未打开,系统会自动提示打开,所以不用自行提示!");
            OnBlueState("0");
            break;
        case CBCentralManagerStatePoweredOn:
            NSLog(@">>>蓝牙已打开,开始扫描周围蓝牙设备!");
            OnBlueState("1");
            //到这里就可以进行扫描设备调用。这里我们先不去调用,因为要通过unity发送命令去扫描,我们接下来自定义一个扫描的方法。
            break;
        default:
            break;
    }
}

  1.5)扫描外设(自定义一个方法)

//自定义方法,开始扫描外设
-(void)scanNearPerpherals{
    NSLog(@"开始扫描周围设备!");
    if(peripherals != nil){
        [peripherals removeAllObjects];
    }
    [manager scanForPeripheralsWithServices:nil options:nil];
}

  1.6)扫描外设,如果扫描到设备会直接进入下面方法。

//扫描到设备会进入方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
    //找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!
    NSString* name = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
    if(peripherals != nil && peripheral != nil && name != nil){
        NSString *identifier = nil;
        NSString *foundPeripheral = [self findPeripheralName:peripheral];
        if(foundPeripheral == nil)
        {
            identifier = [[NSUUID UUID] UUIDString];
        }
        else
        {
            identifier = foundPeripheral;
        }
        //将扫描到的设备信息(设备名称和identifier用分号拼接起来)
        NSString *message = [NSString stringWithFormat:@"%@;%@",name,identifier];
        NSLog(@"message:%@",message);
        if(message != nil)
        {
            //判断是否包含有,防止重复接收扫描的蓝牙设备
            CBPeripheral * ISperipheral = [peripherals objectForKey:identifier];
            if(ISperipheral == nil)
            {
                const char* devicesInfo = [message cStringUsingEncoding:NSASCIIStringEncoding];//NNString转换为char
                GetBluetoothDevice(devicesInfo);//将扫描到的设备信息通知给unity
                [peripherals setObject:peripheral forKey:identifier];//保存扫描到的设备
            }
       }
        
    }
}

-(NSString *)findPeripheralName:(CBPeripheral*)peripheral
{
    NSString *foundPeripheral = nil;
    NSEnumerator *enumerator = [peripherals keyEnumerator];
    id key;
    while((key = [enumerator nextObject]))
    {
        CBPeripheral *tempPeripheral = [peripherals objectForKey:key];
        if([tempPeripheral isEqual:peripheral])
        {
            foundPeripheral = key;
            break;
        }
    }
    return foundPeripheral;
}

  1.7)连接指定的设备,自定义方法

//连接制定设备,自定义方法
-(void)connectToPeripheral:(NSString *)name{
    if(peripherals != nil && name != nil)
    {
        CBPeripheral * peripheral = [peripherals objectForKey:name];
        if(peripheral != nil)
        {
             [manager connectPeripheral:peripheral options:nil];
        }
    }
}

  1.8)连接成功与否,会进入以下回调的方法。

//连接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@">>>连接到名称为(%@)的设备-成功",peripheral.name);
    //停止扫描
    [self stopScanDevice:manager];
    OnConnected("true");//通知Unity我连接成功了状态
    //设置的peripheral委托CBPeripheralDelegate,设置代理
    [peripheral setDelegate:self];
    //扫描外设Services,成功后会进入方法-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    [peripheral discoverServices:nil];
}
//连接到Peripherals-失败
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@">>>连接到名称为(%@)的设备-失败,原因:%@",[peripheral name],[error localizedDescription]);
    OnConnected("false");
}
//Peripherals断开连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
        NSLog(@">>>外设连接断开连接 %@: %@\n", [peripheral name], [error localizedDescription]);
    OnConnected("false");
}

  1.9)连接设备成功,接下来就是发现蓝牙设备的服务。即上面通过[peripheral discoverServices:nil];方法去调用。当然这里发现服务也是可以过滤指定要发现的服务,比如可以这样写    [peripheral discoverServices:@[firstServiceUUID, secondServiceUUID]];这样的好处,有利于研发自己认定服务进行之后的通信操作。

//扫描到Services
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    //NSLog(@">>>扫描到服务:%@",peripheral.services);
    if (error)
    {
        NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
        return;
    }
    for (CBService *service in peripheral.services) {
        NSLog(@">>>扫描到外设服务:%@",service.UUID);
        //扫描每个service的Characteristics,扫描到后会进入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
        [peripheral discoverCharacteristics:nil forService:service];
    }
}

  2.0)扫描到服务后,即可扫描服务的所有特征Characteristics

//获取外设的Characteristics,获取Characterristics的值,获取Characteris的Descriptor和Descriptor的值
 -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
     if (error)
     {
         NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
         return;
     }

     for (CBCharacteristic *characteristic in service.characteristics)
     {
         NSLog(@"扫描到服务service:%@ 的 特征Characteristic: %@",service.UUID,characteristic.UUID);
         //获取Characteristic的值,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
         //[peripheral readValueForCharacteristic:characteristic];
         //这里我们通过订阅方式,实现实时接收蓝牙设备发送的信息
         [self notifyCharacteristic:peripheral characteristic:characteristic];
         
         //搜索特征的Descriptors(这里面没有用到,暂时屏蔽)
         //搜索Characteristic的Descriptors,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
         //[peripheral discoverDescriptorsForCharacteristic:characteristic];
     }
 }

  2.1)获取的特征charateristic的值

-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    
    //注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据characteristic.value
    if(characteristic.value != NULL)
    {
        NSData *data = characteristic.value;
        if(data != NULL && data.length == 5){
            //将接收到的十六进制数据转成十六进制字符串
            Byte* resultByte = (Byte*)[data bytes];
            //将需要到的数据发送给unity
            NSString *result1 = [NSString stringWithFormat:@"%x",resultByte[2]&0xff];
            NSString *result2 = [NSString stringWithFormat:@"%x",resultByte[3]&0xff];
            const char* char1 = [result1 cStringUsingEncoding:NSASCIIStringEncoding];
            const char* char2 = [result2 cStringUsingEncoding:NSASCIIStringEncoding];
            const char* char3 = _LinkCharByFH(char1,char2);
            OnDisPlayData(char3);
        }
        
    }
    
}

 2.2)搜索到Characteristic的Descriptors,以及获取Descriptors的值。(可以根据自己需求使用)

//搜索到Characteristic的Descriptors
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{

    for (CBDescriptor *d in characteristic.descriptors) {
        NSLog(@"搜索到特征:%@ 的Descriptors :%@",characteristic.UUID,d.UUID);
        //获取到Descriptor的值
        [peripheral readValueForDescriptor:d];
    }

}
//获取到Descriptors的值,协议方法
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
    //打印出DescriptorsUUID 和value
    //这个descriptor都是对于characteristic的描述,一般都是字符串,所以这里我们转换成字符串去解析
    NSLog(@"获取到descriptor uuid:%@ 的值:%@ ",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}

2.3)如果有写数据的需求,可以添加写数据到特征中

//写数据到特征中完成,协议方法
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:( CBCharacteristic *)characteristic error:(NSError *)error{
    NSLog(@"写数据完成到特征:%@ 中完成:%@",characteristic.UUID,characteristic.value);
}
//把数据写到Characteristic中
-(void)writeCharacteristic:(CBPeripheral *)peripheral
            characteristic:(CBCharacteristic *)characteristic
                     value:(NSData *)value{
    NSLog(@"%lu", (unsigned long)characteristic.properties);
    //只有 characteristic.properties 有write的权限才可以写
    if(characteristic.properties & CBCharacteristicPropertyWrite || characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) {
    /*
        最好一个type参数可以为CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,区别是是否会有反馈
    */
    [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
        }else{
            NSLog(@"该字段不可写!");
    }
}

 2.4)订阅Characteristic的通知一般在discoverCharacteristic的代理中,发现了类型是notify的characteristic,直接就可以订阅了。

//订阅Characteristic的通知,一般在discoverCharacteristic的代理中,发现了类型是notify的characteristic,直接就可以订阅了
-(void)notifyCharacteristic:(CBPeripheral *)peripheral
            characteristic:(CBCharacteristic *)characteristic{
    //设置通知,数据通知会进入:didUpdateValueForCharacteristic方法
    NSLog(@"设置通知,数据通知会进入:didUpdateValueForCharacteristic方法");
    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
//如果订阅,成功与否的回调是peripheral:didiUpdateNotificationStateForCharacteristic:error,读取中的错误已error行驶传回

-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:( CBCharacteristic *)characteristic error:(NSError *)error{
    if(error)
    {
        NSLog(@"Error changing notification state:%@",[error localizedDescription]);
    }
    NSLog(@"订阅成功与否characteristic uuid:%@  value:%@",characteristic.UUID,[[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding]);
}

//取消通知
-(void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
             characteristic:(CBCharacteristic *)characteristic{
    NSLog(@"取消通知");
     [peripheral setNotifyValue:NO forCharacteristic:characteristic];
}

  2.5)停止扫描,以及断开连接

//停止扫描
-(void)stopScanDevice:(CBCentralManager *)centralManager
{
    //停止扫描
    if(centralManager != nil)
    [centralManager stopScan];
}
//停止扫描并断开连接
-(void)disconnectPeripheral:(NSString *)name
{
    if(peripherals != nil && name != nil)
    {
        CBPeripheral *peripheral = [peripherals objectForKey:name];
        if(peripheral != nil)
        {
            if(manager != nil)
            {
                //停止扫描
                [manager stopScan];
                 //断开连接
                [manager cancelPeripheralConnection:peripheral];
            }
        }
    }
}

2.6)到这里基本上就已经完成IOS与蓝牙通信的功能。接下来我们把Unity要调用IOS的接口实现。

//蓝牙外部调用部分-----------------start
//1、初始化蓝牙设备
void _Init(){
    [ViewController instance];
}
//2、发现设备
void FindBlueDevice(){
    if(gameMgr!=NULL)//判断蓝牙状态进行扫描
    {
        //[gameMgr centralManagerDidUpdateState:manager];
        [gameMgr scanNearPerpherals];
    }
    else{//否则重新初始化,进行蓝牙扫描
        [ViewController instance];
    }
}
//3、连接设备 @deviceIdentifier:设备identifier
void ConnectDevice(char* deviceIdentifier){
    if(gameMgr!=NULL && deviceIdentifier!=NULL)
    {
        [gameMgr connectToPeripheral:[NSString stringWithFormat:@"%s",deviceIdentifier]];
    }
}
//4、关闭扫描设备
void CancelFindDevice(){
    [gameMgr stopScanDevice:manager];
}
//5、断开设备连接
void DisconnectDevice(char* deviceIdentifier){
    if(gameMgr!=NULL && deviceIdentifier!=NULL)
    {
        [gameMgr disconnectPeripheral:[NSString stringWithFormat:@"%s",deviceIdentifier]];
    }
    
}

//6、IOS发送信息到Unity
//6.1、接收搜索到蓝牙设备信息--
void GetBluetoothDevice(const char* msg)
{
    UnitySendMessage("BluetoothController","GetBluetoothDevice",MakeStringCopy(msg));
}
//6.2、蓝牙连接状态---传递到unity(flag:true表示连接上,flag:false;表示没有连接上)
void OnConnected(char* flag)
{
    UnitySendMessage("BluetoothController","OnConnected",MakeStringCopy(flag));
}
//6.3、蓝牙传递信息过来信息---传递到unity
void OnDisPlayData(const char* msg)
{
    UnitySendMessage("BluetoothController","OnDisPlayData",MakeStringCopy(msg));
}
//6.4、信息提示
void OnShowMessage(const char* msg)
{
    UnitySendMessage("BluetoothController","OnShowMessage",MakeStringCopy(msg));
}
//6.5、蓝牙状态提示
void OnBlueState(const char* msg)
{
    UnitySendMessage("BluetoothController","OnBlueState",MakeStringCopy(msg));
}
//蓝牙外部调用部分-----------------end
@end

//----------------------用到工具类---------start
//两个字符串相连接,中间用分号隔开
char* _LinkCharByFH(const char* str1,const char* str2)
{
    char str[80];
    char* flag = "";
    strcpy(str, str1);
    strcat(str, flag);
    strcat(str, str2);
    return MakeStringCopy(str);
}
//防止内存泄漏,崩溃,这里进行参数转换
char* MakeStringCopy(const char* string){
    if(string == NULL){
        return NULL;
    }
    char* res = (char*)malloc(strlen(string)+1);
    strcpy(res, string);
    return res;
}
//----------------------用到工具类---------end

到这里IOS与蓝牙通信,以及IOS要与Unity交互的接口,已经完成。

2》下面就是要实现Unity端界面的实现,以及Unity与IOS之间方法的传递交互功能。(首先把做好的上面IOS两个文件,放到Unity:Plugins/IOS/文件夹下面)

2.1)首先要创建一个C#脚本,然后挂载到到一个地方,根据自己需求挂载(比如挂载到相机上,或者Canvas上,也可以自定义名称。注意这个要与UnitySendMessage("BluetoothController","OnBlueState",MakeStringCopy(msg)); 里面设置的第一个参数保持一致)

    /// <summary>
    /// 初始化蓝牙设备
    /// </summary>
    [DllImport("__Internal")]
    private static extern void _Init();
    public void Init()
    {
#if !UNITY_EDITOR
#if UNITY_IOS
        _Init();
#endif
#endif
    }

    /// <summary>
    /// 接收ios传递过来,当前主设备蓝牙状态
    /// </summary>
    public void OnBlueState(string msg)
    {
        if (msg == "1")
        {
            IsBlueOpen = true;
        }
        else
        {
            IsBlueOpen = false;
        }
    }

    /// <summary>
    /// 发现设备,即搜索设备
    /// </summary>
    [DllImport("__Internal")]
    private static extern void FindBlueDevice();
    public void findBlueDevice()
    {
#if !UNITY_EDITOR
#if UNITY_IOS
        FindBlueDevice();
#endif
#endif
    }

   /// <summary>
    /// 连接设备
    /// </summary>
    /// <param name="macAddress">是设备的identifier,每个手机显示会不一样</param>
    [DllImport("__Internal")]
    private static extern void ConnectDevice(string _macAddress);
    public void connectDevice(string deviceName,string macAddress,GameObject gObj)
    {
#if !UNITY_EDITOR
#if UNITY_IOS
        ConnectDevice(macAddress);
#endif
#endif
    }

    /// <summary>
    /// 接收设备信息(ios--->unity)
    /// </summary>
    /// <param name="deviceInfo"></param>
    public void GetBluetoothDevice(string deviceInfo)
    {
        try
        {
            if (deviceInfo != "" && deviceInfo.Contains(";"))
            {
                string deviceName = deviceInfo.Split(';')[0];
                string deviceAddress = deviceInfo.Split(';')[1];
                
                if (deviceList != null && deviceList.Contains(deviceAddress))
                {
                    //包含则,不用再添加
                }
                else
                {

                    if (deviceName == "" || deviceName == null || deviceName == "null")
                    {
                        deviceName = "N/A";
                        return;
                    }
                    //判断蓝牙名称是否包含在内(过滤蓝牙名称)
                    if (PublicUtils.FilterBLName(deviceName))
                    {
                        deviceList.Add(deviceAddress);
                    }
                }
            }
        }
        catch (Exception err) {
            Debug.Log(err.Message);
        }
    }
    /// <summary>
    /// 蓝牙连接状态反馈
    /// </summary>
    public void OnConnected(string flag)
    {
        if (flag == "true")
        {
            showToast("蓝牙设备已连接!");
        }
        else
        {
            showToast("蓝牙设备已断开!");
        }
    }
    /// <summary>
    /// 接收钢琴传递参数
    /// </summary>
    /// <param name="msg"></param>
    public void OnDisPlayData(string msg)
    {
        if (msg != "")
        {
            msg = msg.Replace(" ", "");
            showToast("接收传递过来的信息:"+ msg);
            //根据需求进行处理
        }
    }

    /// <summary>
    /// 关闭扫描设备
    /// </summary>
    [DllImport("__Internal")]
    private static extern void CancelFindDevice();
    public void cancelFindDevice()
    {
#if !UNITY_EDITOR
#if UNITY_IOS
        CancelFindDevice();
#endif
#endif
    }

    /// <summary>
    /// 断开设备连接
    /// </summary>
    [DllImport("__Internal")]
    private static extern void DisconnectDevice(string _macAddress);
    public void disconnectDevice(string macAddress)
    {
#if !UNITY_EDITOR
#if UNITY_IOS
        DisconnectDevice(macAddress);
#endif
#endif
    }

到这里Unity端与ios之间的交互已经完成。

接下来就是根据自己的需求设计界面,以及调用相关的方法,进行设计。有好的想法欢迎一起交流沟通。

3》发布或者测试时候,需要注意以下几点,需要注意的几个地方。

   3.1)需要添加蓝牙库。在TARGETS--->Unity-iPhone--->Build Phases--->Link Binary With Libraries--->添加"CoreBluetooth.framework".

   3.2)添加打开蓝牙提示权限。在info.plist里面添加NSBluetoothAlwaysUsageDescription 或者 Privacy - Bluetooth Peripheral Usage Description 权限字段 值设置为:"app需要打开你的蓝牙?"


 

后续会给大家介绍:

1、Unity Android 蓝牙通信功能的实现。

2、Unity Android USB方式连接钢琴的实现。

3、Unity Windows USB方式连接电钢的实现

4、Unity IOS USB方式连接电钢的实现

 

如有问题可以微信咨询,可扫描下方微信添加互相沟通:

如果对你有帮助,请给点赞助支持下奥!

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

叶半欲缺

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

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

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

打赏作者

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

抵扣说明:

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

余额充值