不会吧,这也行?iOS后台锁屏监听摇一摇

背景介绍

一般情况下,出于省电、权限、合理性等因素考虑,给人的感觉是很多奇怪的需求安卓可以实现,但是iOS就无法实现!今天要介绍的需求也有这种感觉,就是“当 APP 处于后台或锁屏状态时,依旧可以监听到摇一摇,进而触发某些功能,比如:语音播报”。

在产品经理提出此需求的一瞬间,仿佛周边的空气都凝固了,我也犹如五雷轰顶,愣在原地无法动弹。不由心想:“苹果爸爸怎么可能允许开发者实现这种功能!这得多费电啊!要是所有 APP 都这么做了,那还了得!” 与此同时,之前网上疯传、远近闻名的的需求--“做一个会根据手机壳颜色而改变主题颜色的APP”,清晰地浮现在脑海中,顿时一万只xx从心中奔腾而过。此时,产品经理解释到,这是咱们好多视力障碍用户提的需求,他们经常锁屏或把 APP 退到后台,且因为视力不佳原因,导致重新找到 APP 并切到前台的操作很是麻烦,所以十分希望我们能实现这个功能。

在短暂的心理活动后,秉着“客户第一,产品”的原则,于是回复说:“这功能太少见了,我先在网上看看吧,要是有其他 APP 有类似的功能,麻烦跟我说我参考一下。”然后,就祭出了程序员利器--Google,输入“iOS 后台 摇一摇”,只搜索出来的一个思路:利用 CoreMotion 框架,监听加速计原始数据,然后在 APP 退到后台后,可以实现监听摇一摇的效果。然而,并没有完整的代码或 demo 。顿时,Talk is cheap, show me the code!这句经典台词突然地出现在脑海中!也看到有人评论说 CoreMotion 的确可以实现跟系统摇一摇类似的效果,但是退到后台或锁屏后,没办法监听到摇一摇事件。

看到这条评论时,我不禁开始怀疑此功能是否真的可以被实现。

玩归玩,闹归闹,开始 code,不开玩笑。

接下来,开始自己的探索之旅。

本文 demo 链接为 OCDailyTests/BackgroundShakeTest https://github.com/Dast1Woop/OCDailyTests.git,可自行下载,方便运行和验证。

探索过程

其他 APP 有没有类似功能

经过一番 Google,终于找到一款 APP 有类似功能::酷狗音乐 APP,对,就是那个在 PC 端一打开就会大喊 Hello KuGou!的音乐软件对应的 APP,万万没想到,手机 APP 也是这样,一句Hello KuGou!把我吓一跳。按如下步骤,在设置里打开此功能后,后台或锁屏时,摇一摇手机,可实现切歌的效果。

既然的确有 APP 实现了此功能,那就踏踏实实地探索它可能是怎么实现的吧。

系统提供的摇一摇回调能否满足

系统摇一摇回调方法:

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
    NSLog(@"%s", __FUNCTION__);
}

经测试,此方法只有在 APP 处于前台时,才会被回调。APP 处于后台或锁屏时,此方法不会回调。故初步判定此方法不能满足需求。

**其他方法能否实现 **

此时,还是先根据网上各路大神提供的思路进行尝试,即利用 CoreMotion 框架,监听加速计原始数据,然后在 APP 退到后台后,实现监听摇一摇的效果。

好,我们先利用 CoreMotion 框架,监听加速计原始数据,实现类似系统摇一摇回调的效果。

利用 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 申请后台定位权限来验证。
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 退到后台或锁屏,或即退到后台又锁屏,都能够检测到摇一摇事件。

多 APP 都实现此功能时,摇一摇是何效果

这里用 demo APP 和酷狗音乐 APP 进行测试。

  • 同时打开这两个 APP,其中酷狗音乐 APP 打开后台摇一摇切歌的功能。

  • 酷狗音乐 APP 开始放歌,退到后台。

  • demo APP 打开后,退到后台。

  • 摇一摇,查看效果:

  • 当摇动的力度不是很大时,demo APP 回调方法会被触发;

  • 当摇动的力度很大时,demo APP 回调方法和酷狗 APP 切歌会同时被触发;

  • 由此可见,如果多个 APP 同时实现了此功能时,那么后台或锁屏摇一摇时,只要满足了某个 APP 实现的加速计相关判定条件,就可以同时触发多个 APP 对应的效果。

后台定位权限 + 系统摇一摇,是否可行?

经测试,还是不行。果然,系统摇一摇还是比较受限的,只能在前台回调。

文章小结

想要实现”iOS后台锁屏监听摇一摇“功能,

首次,必须满足一个硬性条件:APP 具有某种后台运行的权限。

其次,技术实现上必须使用CoreMotion框架,通过监听加速计回调自己实现对摇一摇事件的监听判定。

最后,可通过增加时间属性,实现对摇一摇事件监听时的节流效果,防止持续摇动时,太过频繁的事件回调。

此外,多 APP 都实现此功能时,摇一摇的效果是:只要摇动力度很大,加速计数据满足 APP 实现的摇一摇判定条件,就可以同时触发多个 APP 各自对应的效果。

因此,如果不是 APP 特别需要此功能,尽量不要这样实现,毕竟,比较占用系统资源,而且太多 APP 同时实现时,可能会出现效果上的相互干扰。不过,如果合理利用此功能,却可以为特殊用户群体提供极大的便利!

通过探索,满足了视力障碍用户的迫切需求,还是蛮有成就感的!

偷偷的告诉大家,写到这里时,产品经理还没告诉我他所知道的哪个 APP 实现了这个功能,可能他太忙,给忘记了吧......


https://mp.weixin.qq.com/s/hGzR9J-N6VI5qwObHChzKQ

iOS中的内嵌汇编

写一篇在iOS上使用汇编的文章的想法在脑袋里面停留了很久了,但是迟迟没有动手。虽然早前在做启动耗时优化的工作中,也做过通过拦截objc_msgSend并插入汇编指令来统计方法调用耗时的工作,但也只仅此而已。刚好最近的时间项目在做安全加固,需要写更多的汇编来提高安全性(文章内汇编使用指令集为ARM64),也就有了本文

发布于:11天以前  |  58次阅读  |  详细内容 »

不会吧,这也行?iOS后台锁屏监听摇一摇

一般情况下,出于省电、权限、合理性等因素考虑,给人的感觉是很多奇怪的需求安卓可以实现,但是iOS就无法实现!今天要介绍的需求也有这种感觉,就是“当 APP 处于后台或锁屏状态时,依旧可以监听到摇一摇,进而触发某些功能,比如:语音播报”。

发布于:1月以前  |  126次阅读  |  详细内容 »

iOS 稳定性:App 被终止的原因

本次 session 主要内容如下: 介绍了后台应用终止的常见原因,并提供了一些优化建议 介绍了 MetricsKit 提供的在代码中获取诊断和性能数据的方法 介绍了 Xcode Metrics Ogranizer 提供的关于线上用户性能数据的可视化报告

发布于:1月以前  |  155次阅读  |  详细内容 »

Vue中Axios的封装和API接口的管理

在vue项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中。他有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御XSRF等。所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护,直接推荐我们使用axios库。如果还对axios不了解的,可以移步axios文档。

发布于:1月以前  |  158次阅读  |  详细内容 »

iOS 持续集成:更完备的 App Store Connect API

时隔两年 App Store Connect API 有了更新,WWDC 2018 推出了 App Store Connect API ,用于自动化一些 App Store Connect 后台操作。这次更新包含了 app 元数据相关的API,补上了原来缺失的重要一环, 使得几乎可以通过 App Store Connect API 完成 App Store Connect 上的所有操作。今后开发、证书配置、用户管理、测试、发布全流程都可以通过 API 完成。

发布于:1月以前  |  200次阅读  |  详细内容 »

iOS 性能优化:优化 App 启动速度

苹果是一家特别注重用户体验的公司,过去几年一直在优化 App 的启动时间,特别是去年的 WWDC 2019 keynote[1] 上提到,在过去一年苹果开发团队对启动时间提升了 200%

发布于:1月以前  |  182次阅读  |  详细内容 »

让你的应用远离越狱:iOS 14 App Attest 防护功能

当越狱在 iOS 设备第一次流行起来时,iOS 开发人员会尝试各种方法来保护自己的应用程序,以让应用免受盗版等不确定因素的困扰。有许多方法可以做到这一点,包括检查 Cydia 是否存在、检测应用程序是否可读取自身沙箱之外的文件、在检测到调试器时让应用程序崩溃等等。

发布于:1月以前  |  197次阅读  |  详细内容 »

探秘 iOS 14 的 WidgetKit

Widget Extension 提供了 small, medium, large 三个尺寸,不同尺寸可以展示不同的数据、不同的界面,开发者也可以锁定自己APP的 Widget 只有某类尺寸,相同的widget也能重复添加。作为添加在主屏幕上的控件,苹果用了 “At a glance” 来形容 widget ,所以 widget extension 是无法交互的,它能做的只有展示一些信息与点击两个作用,点击后就会引导至app,同时为了性能与耗电量的考虑,Widget extension 也不能展示视频和动态图像。

发布于:1月以前  |  215次阅读  |  详细内容 »

iOS14 Widget 万字指北,先人一步获得顶级流量

2020 年 6 月 22 日,苹果召开了第一次线上的开发者大会 - WWDC20。这次发布会上宣布了ARM架构Mac芯片(拳打Intel)、iOS 14 ATT(脚踢Facebook),可谓是一次载入史册(我是爸爸)的发布会了,当然还发布了被称为下一个顶级流量入口的Widget。踩着八月的尾巴,本次我们就来探究一下Widget。本文会从Widget初窥和Widget开发两个维度和章节来探究一下Widget, 其中初窥章节会带您简单的了解一下Widget,适合应用决策者阅读; 开发章节会带着您一步一步的完成设计开发Widget,适合程序员阅读。

发布于:1月以前  |  276次阅读  |  详细内容 »

OCRunner:完全体的iOS热修复方案

为了能够实现一篇文章的思路:Objective-C源码 -> 二进制补丁文件 ->热更新(具体是哪篇我忘了)。当时刚好开始了oc2mango翻译器的漫漫长路(顺带为了学习编译原理,嘻嘻),等基本完成以后,就开始肝OCRunner:完全兼容struct,enum,系统C函数调用,魔改libffi,生成补丁文件等,尽可能兼容Objective-C,为了做一个直接运行OC的快乐人。

发布于:1月以前  |  228次阅读  |  详细内容 »

iOS APP图标版本化

在我们的项目开发过程中,需要频繁打包给测试人员去测试,有时候我们都不知道测试机上安装的版本是否是最新的,这样会造成很多不必要的麻烦和成本。因此我们需要将buildNumber以水印的方式打在APPIcon上,可以很直观的知道当前是哪一个版本。

发布于:1月以前  |  212次阅读  |  详细内容 »

百度App iOS工程化实践: EasyBox破冰之旅

百度App从单一的搜索工具发展到今天以搜索和Feed流为双引擎的综合性内容消费服务平台,其复杂程度已然不可同日而语矣。 作为一个日活过亿的超级App,业务规模庞大,相关技术人员超过千人,客户端支持主流的移动技术,涉及近百业务方,技术形态复杂,各种组件近三百个,代码百万量级,由此带来的工程化问题是技术团队的一个极大挑战。

发布于:1月以前  |  232次阅读  |  详细内容 »

最多阅读

快速配置 Sign In with Apple 1年以前  |  3447次阅读
开篇 关于iOS越狱开发 1年以前  |  2458次阅读
给数组NSMutableArray排序 1年以前  |  2405次阅读
APP适配iOS11 1年以前  |  2379次阅读
在越狱的iPhone设置上使用lldb调试 1年以前  |  2329次阅读
UITableViewCell高亮效果实现 1年以前  |  2232次阅读
使用 GPUImage 实现一个简单相机 1年以前  |  2225次阅读
App Store 审核指南[2017年最新版本] 1年以前  |  2203次阅读
所有iPhone设备尺寸汇总 1年以前  |  2126次阅读
使用ssh访问越狱iPhone的两种方式 1年以前  |  2043次阅读
关于Xcode不能打印崩溃日志 1年以前  |  2015次阅读
使用ssh 访问越狱iPhone的两种方式 1年以前  |  1901次阅读
UIDevice的简单使用 1年以前  |  1742次阅读
为对象添加一个释放时触发的block 1年以前  |  1703次阅读
使用最高权限操作iPhone手机 1年以前  |  1668次阅读