iOS小技能:蓝牙状态的处理(蓝牙关闭及未授权的处理)

3,463 阅读9分钟

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

粉丝福利:搜索#小程序:iOS逆向 ,关注公众号:iOS逆向领取福利【掘金小册5折优惠码】

目前仅剩一张,有效日期至 11月30日 23:59。

前言

应用场景:打印商品价格标签、打印交易小票

特色功能: 实现自动连接最近使用的打印机、统一处理蓝牙状态

解决的问题:人民币¥符号乱码的问题

价格标签打印(品名支持多行显示) 在这里插入图片描述

票据打印(自动实现%ns 自动补齐空格的功能) 在这里插入图片描述

在这里插入图片描述

【打印商品价格标签及打印交易小票】demo源码:https://download.csdn.net/download/u011018979/14920529

I、检测

1.1 监听蓝牙状态


#define kConnecterManager [ConnecterManager sharedInstance]


#pragma mark - ******** 监听蓝牙状态
//block
+ (void)listenBluetoothpoweredState:(void(^)(CBPeripheral * _Nullable peripheral))peripheral {
    
    if (kConnecterManager.bleConnecter == nil) {
        [kConnecterManager centralmanager];

        [kConnecterManager didUpdateState:^(NSInteger state) {
            
            [self setupState:state peripheral:peripheral];


            
            
        }];
    }
         
         else {// 需要获取蓝牙的连接状态
        
             [self setupState:[kConnecterManager centralmanager].state peripheral:peripheral];

        

    }
}

1.2 统一处理蓝牙状态

  • 统一处理蓝牙状态的判断

/**
 用于统一处理蓝牙状态的判断
 @param state <#state description#>
 @param peripheral <#peripheral description#>
 */
+ (void)setupState:(NSInteger)state peripheral:(void(^)(CBPeripheral * _Nullable peripheral))peripheral{
    
    
    switch (state) {
        case CBCentralManagerStateUnsupported:
            NSLog(@"The platform/hardware doesn't support Bluetooth Low Energy.");
            

            
            
            break;
            // CBManagerStateUnauthorized,3//                NSLog(@"手机蓝牙功能关闭,请前往设置打开蓝牙及控制中心打开蓝牙。"); break;
            
#pragma mark - ******** 没有授权

        case CBCentralManagerStateUnauthorized:
            NSLog(@"The app is not authorized to use Bluetooth Low Energy.");
            
            //
            [self setupBLEUnauthorized];

            
            
            break;
            //CBManagerStatePoweredOff
            
            #pragma mark - ******** 电源关闭

        case CBCentralManagerStatePoweredOff:
            NSLog(@"Bluetooth is currently powered off.");
            
            //  第一次系统会提示
            if(QCTSession.shareQCTSession.isNOFirstShowBLEPowOff){
                
                
                [self setupBLEOff];

                
            }else{
                QCTSession.shareQCTSession.isNOFirstShowBLEPowOff= YES;
                
                
            }
            
            
            
            break;
            

        case CBCentralManagerStatePoweredOn:
            [self startScane:peripheral];
            NSLog(@"Bluetooth power on");
            break;
        case CBCentralManagerStateUnknown:
        default:
            break;
    }
    
}

+ (void) setupBLEUnauthorized{
    
    
    // 弹窗提示
    [QCTLocationServiceUtil setupCentralManagerState_not_authorized];
// 关闭蓝牙的时候更新蓝牙状态为断开
    
    [QCTSession clearPrintInfo];
    
    
    // 通知控制器更新以配对设备列表信息
    
    [[NSNotificationCenter defaultCenter] postNotificationName:QCTQCTBluetoothListViewControllerreloadDateNotification object: nil];
    
    
    
}



+ (void) setupBLEOff{
    
    // 弹窗提示
    [QCTLocationServiceUtil setupCentralManagerStatePoweredOff];
// 关闭蓝牙的时候更新蓝牙状态为断开
    
    [QCTSession clearPrintInfo];
    
    
    // 通知控制器更新以配对设备列表信息
    
    [[NSNotificationCenter defaultCenter] postNotificationName:QCTQCTBluetoothListViewControllerreloadDateNotification object: nil];
    
    
    
}

II、处理

2.0 处理未授权的情况

  • setupCentralManagerState_not_authorized
+ (void) setupCentralManagerState_not_authorized{
    

    
    NSString *tips = @"手机蓝牙功能没有权限,请前往设置。";
    
    
    [LBAlertController showAlertTitle:@"无法使用蓝牙" content:tips cancelString:@"确定" cancleBlock :^{
        

        
        
        //        322
        if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]){
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
        }
        
        
        
    }   sureString:nil sureBlock:nil currentController:[QCT_Common getCurrentVC]];
    
    
}

2.1 处理关闭的状态

  • setupCentralManagerStatePoweredOff
    NSString *tips = @"手机蓝牙功能关闭,请前往设置打开蓝牙及控制中心打开蓝牙。";
    
    
    [LBAlertController showAlertTitle:@"无法使用蓝牙" content:tips cancelString:@"确定" cancleBlock :^{
        

        
        
        //        322
        if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]){
            

            
//            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
        }
        
        
        
    }   sureString:nil sureBlock:nil currentController:[QCT_Common getCurrentVC]];

III 基础知识

3.1 跳转设置的3种方式

 方式一:prefs:root=某项服务 适用于 小于 iOS10的系统;

 方式二:prefs:root=bundleID 适用于 大于等于iOS8系统,小于iOS10的系统

 方式三: UIApplicationOpenSettingsURLString
 version >= iOS10,支持跳转到自己应用设置,不支持跳转到系统设置

  • 注意
 NSURL *url = [NSURL URLWithString:@"App-Prefs:root=Bluetooth"];

 
 以上写法,上传APPStore的时候,会被拒,可以修改成以下写法:


 // 将字符串转换成16进制,转换一下,再去调用,成不成功,就看脸了
 NSData *encryptString = [[NSData alloc] initWithBytes:(unsigned char []){0x41, 0x70, 0x70, 0x2d, 0x50, 0x72, 0x65, 0x66, 0x73, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x3d, 0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68} length:24];
 NSString *string = [[NSString alloc] initWithData:encryptString encoding:NSUTF8StringEncoding];

3.2 ConnecterManager的完整代码

  • m
//

#import "ConnecterManager.h"



@interface ConnecterManager(){
    
    /** 当初创建这个只是为了获取状态的监听 检测*/
    ConnectMethod currentConnMethod;
}
@end

@implementation ConnecterManager

/**
 1、只支持前台的蓝牙应用
 若想利用使用这些提示,你需要在调用connectPeripheral:options: 方法时传入如下参数。
 CBConnectPeripheralOptionNotifyOnConnectionKey: 在应用挂起后,与指定的peripheral成功建立连接,则发出通知,进行打印操作?
 CBConnectPeripheralOptionNotifyOnDisconnectionKey: 在应用挂起后,如果与指定的peripheral断开连接,则发出通知,进行重新连接?
 CBConnectPeripheralOptionNotifyOnNotificationKey: 在应用挂起后,指定的peripheral有任何通知都进行提示
 
 2、Core Bluetooth Background Execution Modes: 后台执行模式
 
 有两种蓝牙后台模式,一种为central角色,另一种为peripheral角色。如果应用需要两种角色,则可以声明支持两种模式。声明方式:增加UIBackgroundModes 键,并增加包含下列字符串的array值。
 • bluetooth-central—The app communicates with Bluetooth low energy peripherals using the Core Bluetooth framework.
 • bluetooth-peripheral—The app shares data using the Core Bluetooth framework
 
 
 
 那么应用将可以在后台处理特定的蓝牙相关事件。即使在后台,你仍然可以发现和连接peripherals,可以检索和读写数据。并且当有CBCentralManagerDelegate or CBPeripheralDelegate 代理事件发生时,系统会唤醒应用来处理
 
 需要注意的是,进入后台时,扫描的处理有些区别:
 1, CBCentralManagerScanOptionAllowDuplicatesKey 这个键会被忽略,多次发现同一peripheral会被合并成一个发现事件。
 2,如果所有扫描中的应用都在后台,那么你应用的扫描间隙会延长。结果是,扫描到peripheral的时间可能会延长。
 
 这样做是为了减少辐射节省电量。
 
 The bluetooth-peripheral Background Execution Mode
 
 支持peripheral后台运行的模式
 
 如果要支持peripheral角色的后台模式,你需要在Info.plist中的增加UIBackgroundModes键并在值中包含bluetooth-peripheral值。这样系统会唤醒应用来处理读写和订阅事件。
 
 蓝牙框架(Core Bluetooth framework)不仅允许你的应用被唤醒来处理读写和订阅请求,还允许你的应用在后台状态下发送广播。但你必须注意后台时广播与前台时广播是不同的。即便如此,你必须注意后台与前台时广播处理的区别。特别是当你的应用需要在后台发送广播。
 1,CBAdvertisementDataLocalNameKey 这个键会被忽略,并且peripheral的local name不会被广播
 2,CBAdvertisementDataServiceUUIDsKey 的值中包含的所有service uuids都会被放到“overflow”区域;只有ios设备显示指明在搜索它时才会搜索到这些值。
 3,如果所有的处于广播状态的应用都在后台,广播频率将降低。

 
 @return <#return value description#>
 */
- (CBCentralManager *)centralmanager{
    if (nil == _centralmanager) {
        
        //    // CBCentralManagerOptionShowPowerAlertKey:初始化的时候如果蓝牙没打开会弹出提示框
        // CBCentralManagerOptionRestoreIdentifierKey:用于蓝牙进程被杀掉恢复连接时用的

        //    dispatch_queue_t centeralQueue = dispatch_queue_create("CenterQueue", DISPATCH_QUEUE_SERIAL);

        _centralmanager =[[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_global_queue(0, 0) options:@{CBCentralManagerOptionShowPowerAlertKey      : [NSNumber numberWithBool:YES]}];

        
        
        //假如,选择支持状态保存和还原的应用只有一个CBCentralMnager对象实例实现了central角色,那么在初始化时初始化options中增加CBCentralManagerOptionRestoreIdentifierKey 键,并赋值还原id.
        
        
      
        
        
//
//        在实现application:didFinishLaunchingWithOptions: 这个代理方法时,通过使用参数launchoptions中的键(UIApplicationLaunchOptionsBluetoothCentralsKey or UIApplicationLaunchOptionsBluetoothPeripheralsKey) 可以获得应用在终止时为我们保存的还原id列表。
//
//
//            ...
        //有了还原id列表后,就可以复原出central manager 对象了。注意:当应用被激活时,系统只提供那些应用终止时有蓝牙任务的central and peripheral managers 的还原ids.
        
//       实现还原代理方法
//
//        在重新创建central and peripheral managers之后,需要通过蓝牙系统还原他们的状态。对于central managers,要实现centralManager:willRestoreState: 代理方法,对于peripheral managers 实现peripheralManager:willRestoreState: 方法。
//
        

        
        
        
        
    }
    return _centralmanager;
}


/**
 typedef NS_ENUM(NSInteger, CBManagerState) {
 CBManagerStateUnknown = 0,//手机没有识别到蓝牙,请检查手机
 CBManagerStateResetting,1//手机蓝牙已断开连接,重置中。
 CBManagerStateUnsupported,2//手机不支持蓝牙功能,请更换手机
 CBManagerStateUnauthorized,3//                NSLog(@"手机蓝牙功能关闭,请前往设置打开蓝牙及控制中心打开蓝牙。"); break;

 CBManagerStatePoweredOff,4//                NSLog(@"。"); break;

 CBManagerStatePoweredOn,5//                NSLog(@"蓝牙开启且可用"); break;

 } NS_ENUM_AVAILABLE(10_13, 10_0);
 


 @param central <#central description#>
 */
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    NSLog(@"centralManagerDidUpdateState: %ld",central.state);//5 UIGestureRecognizerStateFailed
}



static ConnecterManager *manager;
static dispatch_once_t once;

+(instancetype)sharedInstance {
    dispatch_once(&once, ^{
        manager = [[ConnecterManager alloc]init];
    });
    return manager;
}

/**
 *  方法说明:连接指定ip和端口号的网络设备
 *  @param ip 设备的ip地址
 *  @param port 设备端口号
 *  @param connectState 连接状态
 *  @param callback 读取数据接口
 */
-(void)connectIP:(NSString *)ip port:(int)port connectState:(void (^)(ConnectState state))connectState callback:(void (^)(NSData *))callback {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        if (_ethernetConnecter == nil) {
            currentConnMethod = ETHERNET;
            [self initConnecter:currentConnMethod];
        }
        [_ethernetConnecter connectIP:ip port:port connectState:connectState callback:callback];
    });
}

/**
 *  方法说明:扫描外设
 *  @param serviceUUIDs 需要发现外设的UUID,设置为nil则发现周围所有外设
 *  @param options  其它可选操作
 *  @param discover 发现的设备
 */
-(void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options discover:(void(^_Nullable)(CBPeripheral *_Nullable peripheral,NSDictionary<NSString *, id> *_Nullable advertisementData,NSNumber *_Nullable RSSI))discover{
    [_bleConnecter scanForPeripheralsWithServices:serviceUUIDs options:options discover:discover];
}

/**
 *  方法说明:更新蓝牙状态
 *  @param state 蓝牙状态
 */
-(void)didUpdateState:(void(^)(NSInteger state))state {
    if (_bleConnecter == nil) {
        currentConnMethod = BLUETOOTH;
        [self initConnecter:currentConnMethod];
    }
    [_bleConnecter didUpdateState:state];
}

-(void)initConnecter:(ConnectMethod)connectMethod {
    switch (connectMethod) {
        case BLUETOOTH:
            _bleConnecter = [BLEConnecter new];
            _connecter = _bleConnecter;
            break;
        case ETHERNET:
            _ethernetConnecter = [EthernetConnecter new];
            _connecter = _ethernetConnecter;
            break;
        default:
            break;
    }
}

/**
 *  方法说明:停止扫描
 */
-(void)stopScan {
    [_bleConnecter stopScan];
}

/**
 *  连接
 */
-(void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *,id> *)options timeout:(NSUInteger)timeout connectBlack:(void(^_Nullable)(ConnectState state)) connectState{
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self->_bleConnecter connectPeripheral:peripheral options:options timeout:timeout connectBlack:connectState];

    });
    
    
    
}

-(void)connectPeripheral:(CBPeripheral * _Nullable)peripheral options:(nullable NSDictionary<NSString *,id> *)options {
    [_bleConnecter connectPeripheral:peripheral options:options];
}

-(void)write:(NSData *_Nullable)data progress:(void(^_Nullable)(NSUInteger total,NSUInteger progress))progress receCallBack:(void (^_Nullable)(NSData *_Nullable))callBack {
    [_bleConnecter write:data progress:progress receCallBack:callBack];
}

-(void)write:(NSData *)data receCallBack:(void (^)(NSData *))callBack {
#ifdef DEBUG
    NSLog(@"[ConnecterManager] write:receCallBack:");
#endif
    _bleConnecter.writeProgress = nil;
    [_connecter write:data receCallBack:callBack];
}

-(void)write:(NSData *)data {
#ifdef DEBUG
    NSLog(@"[ConnecterManager] write:");
#endif
    _bleConnecter.writeProgress = nil;
    [_connecter write:data];
}



/**
 <code class="language-objective-c">
 //停止扫描并断开连接
 -(void)disconnectPeripheral:(CBCentralManager *)centralManager
 peripheral:(CBPeripheral *)peripheral{
 //停止扫描
 [centralManager stopScan];
 //断开连接
 [centralManager cancelPeripheralConnection:peripheral];
 }
 </code>
 */
-(void)close {
    if (_connecter) {
        [_connecter close];
    }
    switch (currentConnMethod) {
        case BLUETOOTH:
            _bleConnecter = nil;
            break;
        case ETHERNET:
            _ethernetConnecter = nil;
            break;
    }
}

@end
  • h
//

#import <Foundation/Foundation.h>
#import "BLEConnecter.h"
#import "EthernetConnecter.h"
#import "Connecter.h"

/**
 *  @enum ConnectMethod
 *  @discussion 连接方式
 *  @constant BLUETOOTH 蓝牙连接
 *  @constant ETHERNET 网口连接(wifi连接)
 */
typedef enum : NSUInteger{
    BLUETOOTH,
    ETHERNET
}ConnectMethod;

#define kConnecterManager [ConnecterManager sharedInstance]

@interface ConnecterManager : NSObject

/**
 目前用于判断蓝牙状态
 */
@property(nonatomic,strong)  CBCentralManager *centralmanager;

@property(nonatomic,strong)BLEConnecter *bleConnecter;
@property(nonatomic,strong)EthernetConnecter *ethernetConnecter;
@property(nonatomic,strong)Connecter *connecter;

+(instancetype)sharedInstance;

/**
 *  方法说明:连接指定ip和端口号的网络设备
 *  @param ip 设备的ip地址
 *  @param port 设备端口号
 *  @param connectState 连接状态
 *  @param callback 读取数据接口
 */
-(void)connectIP:(NSString *)ip port:(int)port connectState:(void (^)(ConnectState state))connectState callback:(void (^)(NSData *))callback;

/**
 *  方法说明:关闭连接
 */
-(void)close;

/**
 *  方法说明: 向输出流中写入数据(只适用于蓝牙)
 *  @param data 需要写入的数据
 *  @param progress 写入数据进度
 *  @param callBack 读取输入流中的数据
 */
-(void)write:(NSData *_Nullable)data progress:(void(^_Nullable)(NSUInteger total,NSUInteger progress))progress receCallBack:(void (^_Nullable)(NSData *_Nullable))callBack;

/**
 *  方法说明:向输出流中写入数据
 *  @param callBack 读取数据接口
 */
-(void)write:(NSData *)data receCallBack:(void (^)(NSData *))callBack;

/**
 *  方法说明:向输出流中写入数据
 *  @param data 需要写入的数据
 */
-(void)write:(NSData *)data;

/**
 *  方法说明:停止扫描
 */
-(void)stopScan;

/**
 *  方法说明:更新蓝牙状态
 *  @param state 蓝牙状态
 */
-(void)didUpdateState:(void(^)(NSInteger state))state;

/**
 *  方法说明:连接外设
 *  @param peripheral 需连接的外设
 *  @param options 其它可选操作
 *  @param timeout 连接时间
 *  @param connectState 连接状态
 */
-(void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *,id> *)options timeout:(NSUInteger)timeout connectBlack:(void(^_Nullable)(ConnectState state)) connectState;

/**
 *  方法说明:连接外设
 *  @param peripheral 需连接的外设
 *  @param options 其它可选操作
 */
-(void)connectPeripheral:(CBPeripheral * _Nullable)peripheral options:(nullable NSDictionary<NSString *,id> *)options;

/**
 *  方法说明:扫描外设
 *  @param serviceUUIDs 需要发现外设的UUID,设置为nil则发现周围所有外设
 *  @param options  其它可选操作
 *  @param discover 发现的设备
 */
-(void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options discover:(void(^_Nullable)(CBPeripheral *_Nullable peripheral,NSDictionary<NSString *, id> *_Nullable advertisementData,NSNumber *_Nullable RSSI))discover;




@end

IV 、see also

更多内容请关注 #小程序:iOS逆向,只为你呈现有价值的信息,专注于移动端技术研究领域;更多服务和咨询请关注公众号:iOS逆向