锁定解锁事件 iPhone

37

我该如何检测iPhone上的锁定/解锁事件?假设仅对越狱设备可行,请指出正确的API。

在这里,锁定事件指的是显示或隐藏锁定屏幕(可能需要密码解锁,也可能不需要)。


“lock/unlock events” 是什么意思? - Rog
10个回答

23
你可以使用 Darwin通知 来监听事件。在我测试的越狱 iOS 5.0.1 iPhone 4 上,我认为其中一个这样的事件可能就是你所需要的:
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()。您将获得锁定与解锁的不同值,如此处所述


1
我在iOS 8的iPhone6上使用过这个,仍然有效。只要记住这是C而不是Objective-C,另外这个函数是静态的,所以如果你想在你的视图控制器中改变一些东西,你可以将它作为观察者传递下去,而不是NULL。也要注意引用计数。 - Raul Huerta
1
@thibautnoah,为什么不呢?没有其他直接获取此信息的方式。下面加速度计的答案会导致显著的电池损耗。 - Nate
1
你的代码发送通知时,如果你愿意,可以选择使用NSDistributedNotificationCenter。在这种情况下,我们正在监听我们的代码不生成的通知。我们无法控制iOS使用哪个通知中心。事实证明,iOS使用Darwin通知系统在锁定状态更改时发布事件。我们无法控制它。我的答案只是展示了如何监听此事件。这有意义吗? - Nate
1
@RahulPatel,我不是在苹果工作的人,所以我不能保证。CFNotificationCenterAddObserver()API是公开的,所以应该没问题。com.apple.springboard.lockstate字符串是未公开的,所以可能会有问题。但是,我猜它会被批准。如果你担心,你可以隐蔽化那个字符串。 - Nate
1
@Moxarth,评论区不是进行长时间讨论和提出多个单独问题的地方。如果您在使用中遇到了问题,应该在这里发布一个新的问题,并展示您正在尝试使用的代码。您也可以链接到这个问题和答案,但这对我来说并不足够提供帮助所需的信息。 - Nate
显示剩余8条评论

13

简略回答:

无论何时应用程序处于什么状态,都会调用“应用程序将要进入非活动状态”方法...通过我的所有测试,即使您的应用程序在后台保持唤醒状态,也没有办法确定屏幕是否已锁定(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,但迄今为止它对我非常有效。如果你发现任何阻止它工作的问题,请更新。


@BadPirate 我尝试了这段代码,即使设备被锁定,也能获取加速度计数据。 - Geek
哎呀,@Geek,我猜这可能与新的iPhone 5s有关,以及它的低功耗运动芯片(在锁定后可能会继续运行)...还有其他人可以验证吗? - BadPirate
@BadPirate 我在 iPhone 4 上进行了测试。 - Geek
我不确定这个在iOS8上是否可行,因为加速计被用于健康应用程序的锁定状态下,而自iPhone 5S以来,苹果公司专门为此类事情配备了低功耗处理器。我更喜欢Nate的答案,因为它是锁定事件的实际观察者,即使它比使用普通的NSNotifications略微低级。 - Raul Huerta
我该如何使用这段代码在未打开我的应用程序时检查设备的锁定状态? - Mandeep
显示剩余2条评论

7

有一种更好的方法来区分任务切换和屏幕锁定导致的 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:,这使我们可以从那里简单地取消之前安排的请求,从而防止在不应该调用它的情况下进行调用。

享受吧!


1
不幸的是,有时您需要区分手机被锁定和应用程序被通知中心选项卡或任务切换选项卡置于非活动状态的情况。 - fspirit
4
我现在无法在 iOS 5.1 上重现这个问题,无论是通过锁屏还是其他方式,applicationDidEnterBackground 方法总是会被调用。 - overboming

7
截至撰写本文时,有两种相当可靠的方法可以检测设备锁定:

数据保护

通过启用Data Protection entitlement,您的应用程序可以订阅applicationProtectedDataWillBecomeUnavailable:applicationProtectedDataDidBecomeAvailable:通知,以高概率确定使用密码/TouchID身份验证的设备何时锁定/解锁。要确定设备是否使用密码/TouchID,可以查询LAContext注意事项:此方法依赖于“受保护的数据不可用”与手机被锁定的时间重合。当手机使用TouchID并按下睡眠/锁定按钮时,手机会被锁定,受保护的数据变得不可用,并且立即需要密码才能再次解锁。这意味着受保护的数据不可用基本上表示手机已被锁定。如果某人仅使用密码,则在这种情况下并不一定正确,因为他们可以将“需要密码”的时间设置为任何时间,从立即4小时之类的时间。在这种情况下,手机将报告能够处理受保护的数据,但锁定手机不会导致受保护的数据在相当长的时间内变得不可用。

生命周期定时

如果您的应用程序处于前台,则UIApplicationWillResignActiveNotificationUIApplicationDidEnterBackgroundNotification两个生命周期事件之间的时间差异会有明显的变化,具体取决于触发它们的内容。 (此测试是在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

感谢@Warpling。我一直在监听UIApplicationProtectedDataWillBecomeUnavailable,发现当屏幕锁定时它通常不会触发——尽管偶尔会触发。然而,每次设备解锁时我都会收到UIApplicationProtectedDataDidBecomeAvailable的通知。有什么想法吗? - Vikas Yendluri
1
@VikasYendluri 有几件事情需要考虑,可能会影响到这个问题,比如设备是否设置为立即需要密码(如果使用任何生物识别身份验证方法,则应该是真的),以及您的应用程序是否在后台执行任何操作,防止它在后台挂起。 - Warpling

5

在iOS 8中,当你锁定屏幕或按下主页按钮时,所有这些操作都会使应用程序推到后台,但你不知道哪个操作导致了这种结果。我的解决方案与Nits007ak相同,使用notify_register_dispatch来获取状态。

#import <notify.h>
        int notify_token
        notify_register_dispatch("com.apple.springboard.lockstate",
                             &notify_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");
                                 }
                             }
                             );

只要应用程序在前台或后台运行,而不是挂起,您就可以获得此事件。您可以使用notify_token作为notify_get_state的参数,在任何地方获取当前状态,当您想要知道状态且屏幕状态不改变时,这非常有用。

1
在 Swift 中有没有做这件事的方法? - Hugo Alonso
@VanditMehta 在正确的状态下移回前台后,它仍会被调用。 - Andrey Chernukha

4

如果设置了密码,您可以在AppDelegate中使用这些事件

-(void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application
{
}

- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application
{
}

在所有的帖子中,我认为这是关于锁定状态的最佳答案。 - William Grand

4
只需在使用此代码之前导入 #import notify.h。享受吧!
-(void)registerAppforDetectLockState {

    int notify_token;
        notify_register_dispatch("com.apple.springboard.lockstate", &notify_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];

    });
}

它只在你的应用程序保持后台活动时才能工作,就像使用后台位置模式的应用程序一样。 - Zee
是的,确切地说。如果应用程序一直在后台运行,您可以检测锁定和解锁。 - Nits007ak
但是只有在您的应用程序处于前台且设备已锁定时才能正常工作。 - Abhishek Thapliyal
如何为此添加后台支持? - Vandit Mehta
你可以使用位置管理器使应用程序在后台继续运行。 - Nits007ak
你解决了吗?有没有可能监控设备锁屏?我的意思是,当用户输入错误密码时,我们的应用程序应该做出反应或进入某些通知或方法,而它处于后台状态。这种功能在iOS中是否可以实现?在Android中是可以的,所以有人知道怎么做吗?请通过这个指导我。 - Moxarth

2

经过多次尝试,发现监控空白屏幕、完全锁定和锁定状态事件可以提供一致的锁屏指示器。您需要监控状态转换。

// 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];
}

我们能否也有 Swift 版本呢? - Abhishek Thapliyal

1

如果您的应用正在运行,并且用户锁定设备,您的应用委托将接收到“applicationWillResignActive:”的调用。如果在锁定时应用程序正在运行,则在解锁设备时将收到对“applicationDidBecomeActive:”的调用。但是,如果用户接到电话,然后选择忽略它,您的应用程序也会收到相同的调用。据我所知,您无法区分。

如果您的应用程序在这些时间中没有运行,则无法通知您,因为您的应用程序未运行。


有用,但我认为用户只想检测“锁定”事件。这些方法将在用户按下“主页”按钮或“锁定”按钮时触发。 - user298261

-3
最简单的获取屏幕锁定和解锁事件的方法是在您的视图控制器中使用NSNotificationCenter添加事件观察者。我在viewdidload方法中添加了以下观察者,具体操作如下:
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(applicationEnteredForeground:)
                                             name:UIApplicationWillEnterForegroundNotification
                                           object:nil];

然后我在视图控制器中添加了以下选择器。当屏幕解锁时,将调用此选择器。

 - (void)applicationEnteredForeground:(NSNotification *)notification {
    NSLog(@"Application Entered Foreground");
    }

如果您想检测屏幕锁定的事件,可以将UIApplicationWillEnterForegroundNotification替换为UIApplicationDidEnterBackgroundNotification

1
这是不正确的。这也将检测到应用程序在应用程序切换后返回前台。 - jcaron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接