在iOS和OS X中获取系统范围通知的状态

8
我正在编写一段代码,用于处理iOS屏幕的开关(您可以查看这个问题,其中讨论了类似的问题)。我在此问题中包括OSX标签,因为OSX具有相同的系统广播通知功能。下面描述的问题是广播通知功能所固有的问题(而不是iOS或OSX)。
已知有一种注册系统广播通知com.apple.springboard.hasBlankedScreen的方法,以便在屏幕开关时接收通知。
仅供参考(以下是用于注册系统广播通知的API):
  • notify_post、notify_check_ notify_get_state和其他
  • CFNotificationCenterPostNotification、CFNotificationCenterAddObserver和其他(内部使用notify_post等)
但是,这种方法存在两个相关问题:
  • 关闭和打开屏幕的通知都使用相同的名称(com.apple.springboard.hasBlankedScreen)
  • 观察者不会在通知中收到状态。
因此,我们需要实现一些解决方案来区分屏幕的开关(因为将调用相同的通知回调,而且参数中没有状态)。 总的来说,核心问题是状态与通知回调分离。我不知道如何优雅地处理这个问题。 我想出了两种直接的方法(它们各自都有缺陷)。并寻求其他方法或对此方法的改进的想法。

计数解决方案

我们可以实现一个计数器,计算我们已经收到了多少通知,并根据此信息知道它是打开还是关闭屏幕的通知(基于我们计数器的奇偶性)。
但是,它有两个缺点:

1) 如果系统(由于未知的设计时原因)发送了相同名称的其他通知,我们的逻辑将被破坏,因为它会破坏奇偶检查。

2) 我们还需要正确设置初始状态。所以在代码中我们将有类似以下的代码:

counter = getInitialState(); 
registerForNotification();
在这种情况下,我们有一个竞态条件。如果系统在我们执行getInitialState()之后但在registerForNotification()之前发送通知并更改状态,那么我们将得到错误的计数器值。
如果我们执行以下代码:
registerForNotification();
counter = getInitialState(); 
在这种情况下,我们有另一个竞态条件。如果系统在我们执行registerForNotification()之后但在getInitialState()之前发送通知并更改状态,我们将获得一个计数器,将进入通知回调并增加计数器(这将使其不正确)。
确定接收通知时的状态解决方案
在这种情况下,我们不存储任何计数器,而是在通知回调中使用API notify_get_state获取当前状态。
这有它自己的问题:
1) 通知被异步地传递到应用程序。因此,如果您快速关闭和打开屏幕,则可以在屏幕已经打开时接收到两个通知。因此,notify_check将获取当前状态(而不是通知发送时的状态)。
结果,当应用程序在通知回调中使用notify_get_state时,它将确定存在两个通知"屏幕已打开",而不是一条通知"屏幕已关闭"和另一条"屏幕已打开"。
附:总的来说,所有描述的问题并不特定于开/关屏幕的情况。它们对具有不同状态并使用相同通知名称发送的任何系统范围通知都是有效的。
更新1
我没有测试过非常快速地打开/关闭屏幕并从notify_get_state()获得相同结果的情况。
然而,我有一个类似的场景,当我通过CFNotificationCenterAddObserver订阅了两个通知com.apple.springboard.lockstate,并使用另一个API获取当前设备锁定状态时,我收到了相同的值。
因此,这只是我的假设,即notify_get_state也会返回相同的值。但是,我认为这是合理的猜测。notify_get_state的输入参数对于两个调用将是相同的(它不会改变)。并且我不认为系统存储应由notify_get_state返回的FIFO队列状态。

所以,您已经测试过了,在快速开关屏幕的情况下,您可以收到两个 hasBlankedScreen 通知,其中您调用了notify_get_state() 并且这两个调用返回相同的值(都是 0 或者都是 1)?我只是想确认您已经测试过这一点,并看到了这种情况发生。 - Nate
我更新了答案(因为在评论中很难放置所有细节)。 - Victor Ronin
1
@Nate:一般来说,核心问题是状态与通知回调解耦。我不知道如何处理这个问题。 - Victor Ronin
不知道使用情况,很难看出这是个问题。预期的用途可能是在设备休眠时关闭硬件,只要您能处理“重复”,这就不是问题(例如,手机没有休眠足够长的时间才开始关闭相机;现在关闭它只会浪费资源)。 - tc.
1
对于未来的读者:这已不再适用于App Store应用程序 - Warpling
1个回答

10

所以,我做了一个非常简单的实验。我在越狱的iOS 6.1 iPhone 5上运行它,在调试器之外。

代码

我建立了一个消费者应用程序,其中包含以下代码:

#define EVENT "com.mycompany.bs"

- (void)registerForNotifications {
    int result = notify_register_dispatch(EVENT,
                                          &notifyToken,
                                          dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0l),
                                          ^(int info) {
                                              uint64_t state;
                                              notify_get_state(notifyToken, &state);
                                              NSLog(@"notify_register_dispatch() : %d", (int)state);
                                          });
    if (result != NOTIFY_STATUS_OK) {
        NSLog(@"register failure = %d", result);
    }
    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
                                    NULL, // observer
                                    notifyCallback, // callback
                                    CFSTR(EVENT), // event name
                                    NULL, // object
                                    CFNotificationSuspensionBehaviorDeliverImmediately);
}

static void notifyCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
    uint64_t state;
    notify_get_state(notifyToken, &state);
    NSLog(@"notifyCallback(): %d", (int)state);
}

所以,正如您所看到的,它使用了两种不同的方法来注册相同的自定义事件。我启动这个应用程序,让它注册事件,然后将其放入后台(按下主页按钮)。

然后,生产者应用程序让我通过按一个按钮来生成事件:

double delayInSeconds = 0.001;

dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0l);
dispatch_async(q, ^(void) {
    notify_set_state(notifyToken, 2);
    notify_post(EVENT);        
});

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, q, ^(void){
    notify_set_state(notifyToken, 3);
    notify_post(EVENT);
}); 

结果

接下来我运行了生产者应用程序,手动每两秒生成一对事件。如您所见,生产者快速发布状态为2的事件,然后立即发布另一个状态为3的事件。如果一切正常,消费者应该在两个回调方法中打印出23。但事实并非如此(正如您所担心的):

Feb 13 21:46:12 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3
Feb 13 21:46:12 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:12 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:12 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:18 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3
Feb 13 21:46:18 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:18 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:18 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:22 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3
Feb 13 21:46:22 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:22 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:22 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:26 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 2
Feb 13 21:46:26 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:26 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:26 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:30 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3
Feb 13 21:46:30 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:30 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:30 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:33 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3
Feb 13 21:46:33 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:33 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:33 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:36 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 2
Feb 13 21:46:36 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:36 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:36 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

我尝试改变一个消费者注册方法,使用CFNotificationSuspensionBehaviorCoalesce(而不是立即交付)。 结果:

Feb 13 21:48:17 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3
Feb 13 21:48:17 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:17 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:17 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:20 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:20 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:20 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:20 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:24 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:24 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 2
Feb 13 21:48:24 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:24 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:29 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:29 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:29 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:29 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:32 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:32 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 2
Feb 13 21:48:32 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:32 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:35 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:35 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 2
Feb 13 21:48:35 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:35 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:38 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:38 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 2
Feb 13 21:48:38 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:38 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:39 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:39 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:39 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:39 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

我随后尝试将notify_register_dispatch()消费者的队列优先级从后台优先级更改为。 结果如下:

Feb 13 21:49:51 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3
Feb 13 21:49:51 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:49:51 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:49:51 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:49:53 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:49:53 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:49:53 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3
Feb 13 21:49:53 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3

Feb 13 21:49:55 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:49:55 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:49:55 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:49:55 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:49:59 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:49:59 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:49:59 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:49:59 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:50:01 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:50:01 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:01 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:01 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:50:04 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:50:04 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:50:04 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:04 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:50:06 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:50:06 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:50:06 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:06 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:50:09 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:50:09 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:50:09 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:09 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:50:10 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:50:10 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:10 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:10 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

结论

  • 正如您怀疑的那样,存在问题,并且不仅仅是关于SBGetScreenLockStatus调用的问题。有时,消费者从未看到状态设置为2
  • 如果我将生产者时间延迟增加到5毫秒,我从未看到过这个问题。因此,这可能只是在非常接近的时间内发生的事件的问题。锁定/解锁屏幕可能并不重要。显然,较慢的手机(iPhone<5)会有不同的响应。
  • 静态的notifyCallback()方法似乎首先被回调,除非将GCD回调块放置在高优先级队列中。即使如此,通常也会首先调用静态回调函数。很多时候,第一个被回调的方法得到了正确的状态(2),而第二个则没有。因此,如果您必须忍受这个问题,您可以选择看起来表现最佳的回调机制(或至少,在您的应用程序内对其进行原型设计)。
  • 我不能说suspensionBehavior参数有多大的差异。话虽如此,取决于iOS如何发布事件,它们可能正在使用像CFNotificationCenterPostNotification这样的调用来忽略消费者的行为请求。
  • 如果您查看这个 Apple 文档,您会看到两件事情。

    1. 首先,notify_set_state不是最初的API的一部分。
    2. 其次,在该文档的第一段中,它说

Darwin Notification API Reference

这些例程允许进程交换无状态通知事件。

因此,也许我们正在尝试做一些与原始设计不一致的事情 :(

  • 如果您还看Apple's NotificationPoster example,则会发现他们不使用notify_get_statenotify_set_state传递状态。他们将它作为用户信息字典随通知一起传递。显然,如果您观察Apple的iOS事件,则无法控制事件的发布方式。但是,在您可以创建生产者消费者的应用程序中,我会避免使用notify_set_state

非常感谢。回答得非常好。另外,有趣的是,苹果的 NotificationPoster 示例并没有使用 notify_get_state 和 notify_set_state,但他们的 iOS 却使用了。 - Victor Ronin
2
对于未来的读者,App Store 应用程序不再允许这样做:https://forums.developer.apple.com/message/225493#225493 - Warpling
1
@Warpling,谢谢。很好知道。如果您有一分钟的时间,也许可以在问题下面发表评论,因为这个通知是问题的一部分。可能会增加可见性。 - Nate

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