zoukankan      html  css  js  c++  java
  • 摘录:iOS ExternalAccessory框架初探

    原文来自 http://www.cocoachina.com/ios/20170307/18845.html

    由于这方面文章是外部设备链接有关的,为了方便记忆,所以就转载了 ,未经作者授权转载,如果作者不同意联系我,立即删除

    这个框架能做什么

    顾名思义:External:外部的;Accessory:配件。应该是和外部设备相关的一个框架。

    ExternalAccessory框架,就是可以用来和Lightning接口的硬件,或者蓝牙(2.1)设备进行连接、通讯的这么一个框架。(当然,也可以和30-pin接口的硬件连接、通讯——不过现在几乎没有这种接口的设备了吧~)

    就是你现在有一个Lightning耳机(iPhone7, 7Plus的耳机~),或者有一个蓝牙2.1的音箱,你要写一个App去控制这些设备,你要选用的框架,就是ExternalAccessory。

    比如我前公司,帮美国公司代工的一款蓝牙2.1的音箱,写了一个App进行控制(灯光、音效);还有现在公司,做Lightning设备的App,用来对耳机进行简单的控制、固件升级。这都需要用到ExternalAccessory框架。

    框架简介

    ExternalAccessory框架的主要功能,就是提供一个管道,让外围设备可以和基于iOS系统的设备进行通讯。

    主要的几个类:

    • EAAccessory:表示你连接的设备。

    • EAAccessoryManager:有一个重要的属性connectedAccessories,用来获取已经连接上手机的设备。

    • EASession:这个类主要用来建立通道,让App和设备可以进行数据的传输(发送和接收)

    设备的连接

    其实设备的连接、断开,都是系统自动完成的。

    EAAccessoryManager类中有一个属性connectedAccessories(一个array),里面就已经包含了所有已经连接的外围设备(EAAccessory对象)。像什么设备名称、制造厂商、硬件型号、固件型号等等信息,都可以在EAAccessory对象中拿得到。

    但是,ExternalAccessory框架,并不会自动帮你监控设备的断开、连接状态。如果你想拿到设备连接、断开的回调,则需要手动敲一些代码了:

    拿到连接、断开的回调

    需要注册通告,即调用EAAccessoryManager的方法registerForLocalNotifications。

    当有硬件连接,ExternalAccessory框架就会发送EAAccessoryDidConnectNotification这个通告,当有硬件断开连接,就会发出EAAccessoryDidDisconnectNotification通告。所以,要监听、接收这两个通告。

    // 注册通告
    [[EAAccessoryManager sharedAccessoryManager] registerForLocalNotifications];
    
    // 监听EAAccessoryDidConnectNotification通告(有硬件连接就会回调Block)
    [[NSNotificationCenter defaultCenter] addObserverForName:EAAccessoryDidConnectNotification
                                                      object:nil
                                                       queue:nil
                                                  usingBlock:^(NSNotification * _Nonnull note) {
    
                                                      // 从已经连接的外设中查找我们的设备(根据协议名称来查找)
                                                      [self searchOurAccessory];
    }];
    
    [[NSNotificationCenter defaultCenter] addObserverForName:EAAccessoryDidDisconnectNotification
                                                      object:nil
                                                       queue:nil
                                                  usingBlock:^(NSNotification * _Nonnull note) {
                                                      // Do something what you want
    }];

    此外,硬件断开连接,除了通告回调,框架还提供了Delegate的回调方式,遵守EAAccessoryDelegate协议,并实现accessoryDidDisconnect:这个可选方法(这个协议中的唯一一个方法),也可以拿到硬件断开连接的回调。(好奇怪,Apple为什么单单只弄这么一个方法?)

    识别硬件

    好了,我们知道硬件连接进行了,那怎么知道是不是我们的硬件呢?

    苹果公司将这个能识别硬件身份的东东叫做「协议」。本质上就是一个字符串,一个由反向域名组成的字符串,例如om.apple.myProtocol。

    而这个协议(字符串)的定义,是由硬件的生产厂商定义的,所以App开发人员,要和厂商沟通拿到这部分的资料。

    所以我们要做几件事件:

    • 导入框架(这个不用说了吧~)#import

    • 在Info.plist中,增加UISupportedExternalAccessoryProtocols这个key,然后值赋为协议名称(就是那个反向域名字符串)。(其实是一个array,所以这里可以支持多个协议,不分顺序)

    • 在硬件已经连接的回调中,遍历所有已经连接的设备,根据协议名称找到自己的硬件(实现上述代码的searchOurAccessory方法):

    // 从已经连接的外设中查找我们的设备(根据协议名称来查找)
    - (void)searchOurAccessory {
        NSMutableString *info = [[NSMutableString alloc] init];
    
        // search our device
        for (EAAccessory *accessory in [EAAccessoryManager sharedAccessoryManager].connectedAccessories) {
    
            if ([kSPKLightingHeadphoneProtocolString isEqualToString:[accessory.protocolStrings firstObject]] == YES) {
    
                // 硬件的协议字符串和硬件厂商提供的一致,这个就是我们要找的设备了!
    
                // log:可以打印一下该硬件的相关资讯
                for (NSString *proStr in accessory.protocolStrings) {
                    [info appendFormat:@"protocolString = %@
    ", proStr];
                }
                [info appendFormat:@"
    "];
                [info appendFormat:@"manufacturer = %@
    ", accessory.manufacturer];
                [info appendFormat:@"name = %@
    ", accessory.name];
                [info appendFormat:@"modelNumber = %@
    ", accessory.modelNumber];
                [info appendFormat:@"serialNumber = %@
    ", accessory.serialNumber];
                [info appendFormat:@"firmwareRevision = %@
    ", accessory.firmwareRevision];
                [info appendFormat:@"hardwareRevision = %@
    ", accessory.hardwareRevision];
    
                // Log...
            }
        }
    }

    另外,监视硬件连接的通告Block回调,NSNotification * _Nonnull note这个参数,其实是包含了EAAccessory对象,我们也可以直接通过EAAccessoryKey这个key拿到EAAccessory对象,再对比协议字符串是否相同,从而直接拿到已经连接的硬件,无须遍历connectedAccessories数组。

    传输数据(指令)

    创建EASession、打开输入、输出通道

    App和外围设备通讯、数据传输,靠的是NSInputStream和NSOutputStream对象,而这两个对象是EASession的两个属性。所以我们要创建EASession对象,谓曰:打开传输通道()。

    • 遵守NSStreamDelegate协议,类似:@interface YourClassName(),用于后面拿到相关回调。

    • 创建EASession并打开输入、输出通道,类似如下代码:

    - (BOOL)openSession {
        // 根据已经连接的EAAccessory对象和这个协议(反向域名字符串)来创建EASession对象,并打开输入、输出通道 
        self.session = [[EASession alloc] initWithAccessory:self.accessory forProtocol: kSPKLightingHeadphoneProtocolString];
        if(self.session != nil) {
            // open input stream
            self.session.inputStream.delegate = self;
            [self.session.inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
            [self.session.inputStream open];
    
            // open output stream
            self.session.outputStream.delegate = self;
            [self.session.outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
            [self.session.outputStream open];
        }
        else {
            NSLog(@"Failed to create session");
        }
    
        return (nil != self.session);
    }

    到此为止,就完整创建了一个包含accessory对象、并已经可以进行数据发送和接收的EASession对象了。

    stream:handleEvent:回调:

    不过,虽然数据传输通道已经打开了,但是怎么发送、接收数据呢?或者说,怎么知道什么时候可以发送数据,什么时候要接收数据?

    注意我们刚刚遵守了NSStreamDelegate协议,这里就是利用delegate回调来监听input stream和output stream的数据。

    // delegate回调的方法
    - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
        switch (eventCode) {
            case NSStreamEventNone:
                break;
            case NSStreamEventOpenCompleted:
                break;
            case NSStreamEventHasBytesAvailable:
                //NSLog(@"Input stream is ready");
                // 接收到硬件数据了,根据指令定义对数据进行解析。
                [self readFromDevice];
                break;
            case NSStreamEventHasSpaceAvailable:
                //NSLog(@"Output stream is ready");
                // 可以发送数据给硬件了
                [self writeToDevice];
                break;
            case NSStreamEventErrorOccurred:
                break;
            case NSStreamEventEndEncountered:
                break;
            default:
                break;
        }
    }
    • HasBytesAvailable:表示stream中有数据需要读取(硬件发送了数据给App)

    • HasSpaceAvailable:表示stream中可以接收数据的写入(App发送了数据给硬件)——当然,不是每次都需要等到这个回调执行,App才能发送数据给硬件,你可以判断stream的hasBytesAvailable属性,如果为Yes,照样可以直接发送数据给硬件。类似如下:

    BOOL isAvailable = self.session.outputStream.hasSpaceAvailable;
    if (isAvailable == YES) {
        [self writeToDevice];
    }

    发送数据、接收数据的具体方法:

    • 发送数据:

    outputStream的write:maxLength:方法,类似如下:

    [self.session.outputStream write:[self.writeData bytes] maxLength:self.writeDataLen];
    • 接收数据:

    inputStream的read:maxLength:方法,类似如下:

    [self.session.inputStream read:buffer maxLength:SPK_INPUT_DATA_BUFFER_LEN];

    到此,我们用ExternalAccessory框架,进行了从识别硬件连接、获取硬件、打开传输通道、发送数据、接收数据的完整过程。

    调试、Debug

    我们开发的是一个Lightning接口设备的App,当手机连接硬件时,就没办法连接电脑进行调试,当手机连接电脑时,就没办法连接硬件进行测试。所以整个开发调试、Debug无从下手。网站上咨询了苹果,也在StackOverflow上提问,都没有得到解决方案。

    后来我就脑洞大开,把需要打印的日志收集起来,通过一个TextView,显示到App上做调试用(如下图)。也算是一个权宜之计,谁有更好的办法么~

    Log.JPG

  • 相关阅读:
    java的多线程学习,第二记
    java多线程的学习
    长城
    2018-12-6
    mysql的笔记
    springboot用jpa生成表,没有外键
    idea 使用方法
    Oracle数据库中文乱码问题
    JAVA-Could not create the Java virtual machine java启动失败
    log4j日志如何在ssh中配置?
  • 原文地址:https://www.cnblogs.com/tangranyang/p/6522897.html
Copyright © 2011-2022 走看看