我该如何检测iPhone上的锁定/解锁事件?假设仅对越狱设备可行,请指出正确的API。
在这里,锁定事件指的是显示或隐藏锁定屏幕(可能需要密码解锁,也可能不需要)。
com.apple.springboard.lockstate
com.apple.springboard.lockcomplete
注意: 根据海报对我在这里回答的一个类似问题的评论,这也适用于未越狱的手机。
要使用它,请像这样注册事件(这仅为上面第一个事件注册,但您也可以为lockcomplete
添加观察者):
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
(void*)self, // observer (can be NULL)
lockStateChanged, // callback
CFSTR("com.apple.springboard.lockstate"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
其中 lockStateChanged
是您的事件回调函数:
static void lockStateChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
NSLog(@"event received!");
if (observer != NULL) {
MyClass *this = (MyClass*)observer;
}
// you might try inspecting the `userInfo` dictionary, to see
// if it contains any useful info
if (userInfo != nil) {
CFShow(userInfo);
}
}
lockstate
事件在设备锁定和解锁时发生,但lockcomplete
事件仅在设备锁定时触发。另一种确定事件是锁定还是解锁事件的方法是使用notify_get_state()
。您将获得锁定与解锁的不同值,如此处所述。
NSDistributedNotificationCenter
。在这种情况下,我们正在监听我们的代码不生成的通知。我们无法控制iOS使用哪个通知中心。事实证明,iOS使用Darwin通知系统在锁定状态更改时发布事件。我们无法控制它。我的答案只是展示了如何监听此事件。这有意义吗? - NateCFNotificationCenterAddObserver()
API是公开的,所以应该没问题。com.apple.springboard.lockstate
字符串是未公开的,所以可能会有问题。但是,我猜它会被批准。如果你担心,你可以隐蔽化那个字符串。 - Nate简略回答:
无论何时应用程序处于什么状态,都会调用“应用程序将要进入非活动状态”方法...通过我的所有测试,即使您的应用程序在后台保持唤醒状态,也没有办法确定屏幕是否已锁定(CPU速度不报告,总线速度保持不变,mach_time分母/分子不变)...
然而,当设备锁定时,苹果似乎会关闭加速度计...启用锁定屏幕时的 iPhone 加速度计 (经过测试,在 iPhone 4 上运行 iOS4.2 出现这种情况)
因此...
在您的应用程序代理中:
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(@"STATUS - Application will Resign Active");
// Start checking the accelerometer (while we are in the background)
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:1]; // Ping every second
_notActiveTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(deviceDidLock) userInfo:nil repeats:NO]; // 2 seconds for wiggle
}
//Deprecated in iOS5
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
NSLog(@"STATUS - Update from accelerometer");
[_notActiveTimer invalidate];
_notActiveTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(deviceDidLock) userInfo:nil repeats:NO];
}
- (void)deviceDidLock
{
NSLog(@"STATUS - Device locked!");
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
_notActiveTimer = nil;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(@"STATUS - Application did become active");
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
[_notActiveTimer invalidate];
_notActiveTimer = nil;
}
我知道...这有点像是一个hack,但迄今为止它对我非常有效。如果你发现任何阻止它工作的问题,请更新。
有一种更好的方法来区分任务切换和屏幕锁定导致的 applicationWillResignActive:
回调,甚至不需要使用加速度计状态等未记录的功能。
当应用程序移动到后台时,首先向应用程序代理发送 applicationWillResignActive:
,然后发送 applicationDidEnterBackground:
。当应用程序被按下锁定按钮或接收到电话呼入中断时,后者方法不会被调用。我们可以使用这些信息来区分两种情况。
假设您希望在屏幕锁定时在 screenLockActivated
方法中回调。以下是魔法代码:
- (void)applicationWillResignActive:(UIApplication*)aApplication
{
[self performSelector:@selector(screenLockActivated)
withObject:nil
afterDelay:0];
}
- (void)applicationDidEnterBackground:(UIApplication*)aApplication
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (void)screenLockActivated
{
NSLog(@"yaay");
}
解释:
默认情况下,我们假设每次调用 applicationWillResignActive:
都是由于活动状态转换为非活动状态(例如锁屏),但我们大方地让系统在超时时间内(在此例中为单个运行循环周期)证明相反。通过延迟对 screenLockActivated
的调用。如果屏幕被锁定,则系统在不触碰其他委托方法的情况下完成当前运行循环周期。然而,如果这是一个活动到后台的状态转换,它还会在循环结束前调用 applicationDidEnterBackground:
,这使我们可以从那里简单地取消之前安排的请求,从而防止在不应该调用它的情况下进行调用。
享受吧!
applicationProtectedDataWillBecomeUnavailable:
和applicationProtectedDataDidBecomeAvailable:
通知,以高概率确定使用密码/TouchID身份验证的设备何时锁定/解锁。要确定设备是否使用密码/TouchID,可以查询LAContext
。
注意事项:此方法依赖于“受保护的数据不可用”与手机被锁定的时间重合。当手机使用TouchID并按下睡眠/锁定按钮时,手机会被锁定,受保护的数据变得不可用,并且立即需要密码才能再次解锁。这意味着受保护的数据不可用基本上表示手机已被锁定。如果某人仅使用密码,则在这种情况下并不一定正确,因为他们可以将“需要密码”的时间设置为任何时间,从立即到4小时之类的时间。在这种情况下,手机将报告能够处理受保护的数据,但锁定手机不会导致受保护的数据在相当长的时间内变得不可用。
UIApplicationWillResignActiveNotification
和UIApplicationDidEnterBackgroundNotification
两个生命周期事件之间的时间差异会有明显的变化,具体取决于触发它们的内容。
(此测试是在iOS 10中进行的,可能会在未来的版本中更改)
按Home键会导致两者之间出现显着的延迟(即使启用了减少动作设置)。15:23:42.517 willResignActive
15:23:43.182 didEnterBackground
15:23:43.184 difference: 0.666346
在应用程序打开时锁定设备会在两个事件之间创建一个更为微不足道的延迟(<~0.2s):
15:22:59.236 willResignActive
15:22:59.267 didEnterBackground
15:22:59.267 difference: 0.031404
UIApplicationProtectedDataWillBecomeUnavailable
,发现当屏幕锁定时它通常不会触发——尽管偶尔会触发。然而,每次设备解锁时我都会收到UIApplicationProtectedDataDidBecomeAvailable
的通知。有什么想法吗? - Vikas Yendluri在iOS 8中,当你锁定屏幕或按下主页按钮时,所有这些操作都会使应用程序推到后台,但你不知道哪个操作导致了这种结果。我的解决方案与Nits007ak相同,使用notify_register_dispatch来获取状态。
#import <notify.h>
int notify_token
notify_register_dispatch("com.apple.springboard.lockstate",
¬ify_token,
dispatch_get_main_queue(),
^(int token)
{
uint64_t state = UINT64_MAX;
notify_get_state(token, &state);
if(state == 0) {
NSLog(@"unlock device");
} else {
NSLog(@"lock device");
}
}
);
如果设置了密码,您可以在AppDelegate中使用这些事件
-(void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application
{
}
- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application
{
}
-(void)registerAppforDetectLockState {
int notify_token;
notify_register_dispatch("com.apple.springboard.lockstate", ¬ify_token,dispatch_get_main_queue(), ^(int token) {
uint64_t state = UINT64_MAX;
notify_get_state(token, &state);
if(state == 0) {
NSLog(@"unlock device");
} else {
NSLog(@"lock device");
}
NSLog(@"com.apple.springboard.lockstate = %llu", state);
UILocalNotification *notification = [[UILocalNotification alloc]init];
notification.repeatInterval = NSDayCalendarUnit;
[notification setAlertBody:@"Hello world!! I come becoz you lock/unlock your device :)"];
notification.alertAction = @"View";
notification.alertAction = @"Yes";
[notification setFireDate:[NSDate dateWithTimeIntervalSinceNow:1]];
notification.soundName = UILocalNotificationDefaultSoundName;
[notification setTimeZone:[NSTimeZone defaultTimeZone]];
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
});
}
经过多次尝试,发现监控空白屏幕、完全锁定和锁定状态事件可以提供一致的锁屏指示器。您需要监控状态转换。
// call back
void displayStatusChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
// notification comes in order of
// "com.apple.springboard.hasBlankedScreen" notification
// "com.apple.springboard.lockcomplete" notification only if locked
// "com.apple.springboard.lockstate" notification
AppDelegate *appDelegate = CFBridgingRelease(observer);
NSString *eventName = (__bridge NSString*)name;
NSLog(@"Darwin notification NAME = %@",name);
if([eventName isEqualToString:@"com.apple.springboard.hasBlankedScreen"])
{
NSLog(@"SCREEN BLANK");
appDelegate.bDeviceLocked = false; // clear
}
else if([eventName isEqualToString:@"com.apple.springboard.lockcomplete"])
{
NSLog(@"DEVICE LOCK");
appDelegate.bDeviceLocked = true; // set
}
else if([eventName isEqualToString:@"com.apple.springboard.lockstate"])
{
NSLog(@"LOCK STATUS CHANGE");
if(appDelegate.bDeviceLocked) // if a lock, is set
{
NSLog(@"DEVICE IS LOCKED");
}
else
{
NSLog(@"DEVICE IS UNLOCKED");
}
}
}
-(void)registerforDeviceLockNotif
{
// screen and lock notifications
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.apple.springboard.hasBlankedScreen"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.apple.springboard.lockcomplete"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.apple.springboard.lockstate"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.backgroundTaskIdentifier =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
}];
[self registerforDeviceLockNotif];
}
如果您的应用正在运行,并且用户锁定设备,您的应用委托将接收到“applicationWillResignActive:”的调用。如果在锁定时应用程序正在运行,则在解锁设备时将收到对“applicationDidBecomeActive:”的调用。但是,如果用户接到电话,然后选择忽略它,您的应用程序也会收到相同的调用。据我所知,您无法区分。
如果您的应用程序在这些时间中没有运行,则无法通知您,因为您的应用程序未运行。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationEnteredForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
然后我在视图控制器中添加了以下选择器。当屏幕解锁时,将调用此选择器。
- (void)applicationEnteredForeground:(NSNotification *)notification {
NSLog(@"Application Entered Foreground");
}