通常状况下,出于省电、权限、合理性等因素考虑,给人的感受是不少奇怪的需求安卓能够实现,可是iOS就没法实现!今天要介绍的需求也有这种感受,就是“当 APP 处于后台或锁屏状态时,依旧能够监听到摇一摇,进而触发某些功能,好比:语音播报”。git
在产品经理提出此需求的一瞬间,仿佛周边的空气都凝固了,我也犹如五雷轰顶,愣在原地没法动弹。不禁心想:“苹果爸爸怎么可能容许开发者实现这种功能!这得多费电啊!要是全部 APP 都这么作了,那还了得!” 与此同时,以前网上疯传、远近闻名的的需求--“作一个会根据手机壳颜色而改变主题颜色的APP”,清晰地浮如今脑海中,顿时一万只xx🐎从心中奔腾而过。此时,产品经理解释到,这是我们好多视力障碍用户提的需求,他们常常锁屏或把 APP 退到后台,且由于视力不佳缘由,致使从新找到 APP 并切到前台的操做非常麻烦,因此十分但愿咱们能实现这个功能。程序员
在短暂的心理活动后,秉着“客户第一,产品🐂🍺”的原则,因而回复说:“这功能太少见了,我先在网上看看吧,要是有其余 APP 有相似的功能,麻烦跟我说我参考一下。”而后,就祭出了程序员利器--Google,输入“iOS 后台 摇一摇”,只搜索出来的一个思路:利用 CoreMotion 框架,监听加速计原始数据,而后在 APP 退到后台后,能够实现监听摇一摇的效果。然而,并无完整的代码或 demo 。顿时,Talk is cheap, show me the code!
这句经典台词忽然地出如今脑海中!也看到有人评论说 CoreMotion 的确能够实现跟系统摇一摇相似的效果,可是退到后台或锁屏后,没办法监听到摇一摇事件。github
看到这条评论时,我不由开始怀疑此功能是否真的能够被实现。swift
玩归玩,闹归闹,开始 code,不开玩笑。app
接下来,开始本身的探索之旅。框架
本文 demo 连接为 OCDailyTests/BackgroundShakeTest,可自行下载,方便运行和验证。工具
通过一番 Google,终于找到一款 APP 有相似功能::酷狗音乐 APP,对,就是那个在 PC 端一打开就会大喊 Hello KuGou!
的音乐软件对应的 APP,万万没想到,手机 APP 也是这样,一句Hello KuGou!
把我吓一跳。按以下步骤,在设置里打开此功能后,后台或锁屏时,摇一摇手机,可实现切歌的效果。oop
既然的确有 APP 实现了此功能,那就踏踏实实地探索它多是怎么实现的吧。post
系统摇一摇回调方法:测试
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{ NSLog(@"%s", __FUNCTION__); }
经测试,此方法只有在 APP 处于前台时,才会被回调。APP 处于后台或锁屏时,此方法不会回调。故初步断定此方法不能知足需求。
此时,仍是先根据网上各路大神提供的思路进行尝试,即利用 CoreMotion 框架,监听加速计原始数据,而后在 APP 退到后台后,实现监听摇一摇的效果。
好,咱们先利用 CoreMotion 框架,监听加速计原始数据,实现相似系统摇一摇回调的效果。
因加速计回调比较频繁,所以比较占用资源,故把此功能设计为单例。
快速实现单例效果
//具体实现详见 demo 中文件 #import "HMSingleton.h" @interface MYAccelerometerTool : NSObject HMSingleton_h(MYAccelerometerTool); @end @implementation MYAccelerometerTool HMSingleton_m(MYAccelerometerTool); @end
声明和懒加载运动管理员属性
@property(nonatomic, strong) CMMotionManager *gMotionMnger; - (CMMotionManager *)gMotionMnger{ if (nil == _gMotionMnger) { CMMotionManager *lMnger = [[CMMotionManager alloc] init]; lMnger.accelerometerUpdateInterval = 0.1; [lMnger startAccelerometerUpdates]; _gMotionMnger = lMnger; } return _gMotionMnger; }
声明和实现时间戳属性,用于实现节流效果(为防止频繁回调,每次检测成功后,中止摇动 1s 后才继续响应下次摇一摇。)
@property(nonatomic, strong) NSDate *gDateLastShakeSuc; - (NSDate *)gDateLastShakeSuc{ if (nil == _gDateLastShakeSuc) { _gDateLastShakeSuc = [NSDate distantPast]; } return _gDateLastShakeSuc; }
开始监听摇一摇动做
- (BOOL)startMonitorShake{ if (NO == self.gMotionMnger.isAccelerometerAvailable) { return NO; } //监听中,直接返回YES if (self.gMotionMnger.isAccelerometerActive) { return YES; } [self.gMotionMnger startAccelerometerUpdatesToQueue:[NSOperationQueue new] withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) { CMAcceleration acceleration = accelerometerData.acceleration; //综合x、y两个方向的加速度(z方向速度无心义,用的话,走路上下抖手机时会误触发,系统摇一摇也不会被z轴加速度触发) //当综合加速度大于2.3时,就激活效果(数据越小,用户摇动的动做就越小,越容易激活) double accelerameter = sqrt( pow( acceleration.x , 2 ) + pow( acceleration.y , 2 )); if (accelerameter > 2.3) { //节流效果:距离上次摇一摇成功事件,间隔时间小于1s时,认为无效 NSDate *lCrtDate = [NSDate date]; if ([lCrtDate timeIntervalSinceDate:self.gDateLastShakeSuc] < 1) { self.gDateLastShakeSuc = lCrtDate; return ; } self.gDateLastShakeSuc = lCrtDate; [[NSNotificationCenter defaultCenter] postNotificationName:KNTFY_SHAKE_SUCCESS object:nil]; } }]; return YES; }
为了代码的对称美和可能的相关业务,实现中止监听摇一摇方法
- (void)stopMonitorShake{ [self.gMotionMnger stopAccelerometerUpdates]; self.gMotionMnger = nil; self.gDateLastShakeSuc = nil; }
开始监听摇一摇
BOOL lRes = [[MYAccelerometerTool sharedMYAccelerometerTool] startMonitorShake]; NSLog(@"lRes:%d", lRes); NSAssert(lRes, @"开始监听摇一摇失败");
监听摇一摇成功的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nmShakeSuccess:) name:KNTFY_SHAKE_SUCCESS object:nil]; //在摇一摇的同时,经过观察此方法是否有log,能够判断是否有监听到。 - (void)nmShakeSuccess:(NSNotification *)ntfy{ NSLog(@"%s", __FUNCTION__); }
dealloc方法中取消监听
- (void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self]; }
运行 demo 工程,测试可知,经过上述方法,的确能够在 APP 处于前台时,实现监听摇一摇动做的效果。但是,当把 APP 退到后台或锁屏时,nmShakeSuccess 方法再也不有 log,即:APP 处于后台时,经过监听加速计的方法,默认也没法在 APP 处于后台或锁屏时实现监听效果。这也印证了上文提到的那个评论者的疑问。
但是 Hello KuGou!
明明实现了后台或锁屏时摇一摇的效果啊!难道是须要额外的配置?联想 iOS 处于后台时,默认会把 APP 的服务给挂起(suspended),只有当 APP 经过某种方式(后台定位/播放音乐/蓝牙扫描等)具备后台运行权限时,才能够一直保活。可猜测,也许赋予 APP 具备后台运行的权限后,就能够实现想要的功能了。因而,开始进行验证以下。
由于工做中不少 APP 具备后台定位权限和相关功能,因此本文经过为 APP 申请后台定位权限来验证。
APP 申请后台定位权限
plist 文件中增长”定位请求描述信息“
<key>NSLocationAlwaysUsageDescription</key> <string>咱们须要根据您的定位提供周边搜索和导航服务</string> <key>NSLocationWhenInUseUsageDescription</key> <string>咱们须要根据您的定位提供周边搜索和导航服务</string>
增长”后台定位权限“
<key>UIBackgroundModes</key> <array> <string>location</string> </array>
声明定位管理员属性
@property(nonatomic, strong) CLLocationManager *gMnger;
懒加载定位管理员,请求定位权限、容许后台位置更新
- (CLLocationManager *)gMnger{ if (nil == _gMnger) { _gMnger = [[CLLocationManager alloc] init]; _gMnger.delegate = self; _gMnger.allowsBackgroundLocationUpdates = YES; [_gMnger requestWhenInUseAuthorization]; } return _gMnger; }
代理 3 步走(用于验证后台定位是否生效)
遵照代理协议
@interface ViewController ()<CLLocationManagerDelegate>
指定代理对象
_gMnger.delegate = self;
实现代理方法
#pragma mark - delegate - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations{ NSLog(@"%s", __FUNCTION__); } - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{ NSLog(@"%s", __FUNCTION__); }
APP 后台或锁屏后,测试可否成功监听摇一摇
运行 demo 工程,经测试,把 APP 退到后台或锁屏,或即退到后台又锁屏,都可以检测到摇一摇事件。
这里用 demo APP 和酷狗音乐 APP 进行测试。
经测试,仍是不行。果真,系统摇一摇仍是比较受限的,只能在前台回调。
想要实现”iOS后台锁屏监听摇一摇“功能,
首次,必须知足一个硬性条件:APP 具备某种后台运行的权限。
其次,技术实现上必须使用CoreMotion框架,经过监听加速计回调本身实现对摇一摇事件的监听断定。
最后,可经过增长时间属性,实现对摇一摇事件监听时的节流效果,防止持续摇动时,太过频繁的事件回调。
此外,多 APP 都实现此功能时,摇一摇的效果是:只要摇动力度很大,加速计数据知足 APP 实现的摇一摇断定条件,就能够同时触发多个 APP 各自对应的效果。
所以,若是不是 APP 特别须要此功能,尽可能不要这样实现,毕竟,比较占用系统资源,并且太多 APP 同时实现时,可能会出现效果上的相互干扰。不过,若是合理利用此功能,却能够为特殊用户群体提供极大的便利!
经过探索,知足了视力障碍用户的迫切需求,仍是蛮有成就感的!
偷偷的告诉你们,写到这里时,产品经理还没告诉我他所知道的哪一个 APP 实现了这个功能,可能他太忙,给忘记了吧......
OCDailyTests/BackgroundShakeTest
最后,感谢“技术创做101训练营”!经过参加训练营,让我对写做有了更深刻的认识和更高的内心觉悟。
本文由博客群发一文多发等运营工具平台 OpenWrite 发布