【问题标题】:Getting state for system wide notifications in iOS and OS X在 iOS 和 OS X 中获取系统范围通知的状态
【发布时间】:2013-02-11 20:26:47
【问题描述】:

我正在尝试编写一个代码来处理 iOS 上的打开/关闭屏幕(您可以查看 this 问题,其中讨论了类似的问题)。我为这个问题添加了 OSX 标签,因为 OSX 具有相同的系统范围通知工具。下面描述的问题是继承到通知工具(相对于 iOS 或 OSX)。

有一种众所周知的方法可以注册系统范围的通知com.apple.springboard.hasBlankedScreen,以便在屏幕关闭或打开时接收通知。

仅供参考(这里是用于注册系统范围通知的 API):

  • notify_post、notify_check_ notify_get_state 和 friends
  • CFNotificationCenterPostNotification、CFNotificationCenterAddObserver 和friends(内部使用 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() 获得相同结果的场景。

但是,当我收到两个通知 com.apple.springboard.lockstate(通过 CFNotificationCenterAddObserver 订阅)并且我使用另一个 API 获取当前设备锁定状态并收到时,我遇到了类似的情况两个通知的值相同。

所以我只是假设 notify_get_state 也会返回相同的值。但是,我认为这是有根据的猜测。 notify_get_state 的输入参数对于两个调用将是相同的(它不会改变)。而且我不认为系统存储应该由 notify_get_state 返回的 FIFO 状态队列。

【问题讨论】:

  • 所以,你已经测试过了,在快速关闭/打开屏幕的情况下,你会收到两个hasBlankedScreen通知,你调用notify_get_state(),两个调用返回相同的值(都是0,或者都是1)?我只是想验证您是否已经对此进行了测试,并且已经看到了这种情况。
  • 我更新了答案(因为很难将所有细节都放在评论中)。
  • @Nate:一般来说,状态与通知回调解耦的核心问题。我不知道如何处理。
  • 在不了解用例的情况下,很难理解为什么这是个问题。预期用途可能是在设备处于睡眠状态时关闭硬件,在这种情况下,只要您可以处理“重复”就不是问题(例如,手机没有足够长的睡眠时间来启动 关掉相机;现在关掉它只会是浪费。

标签: ios macos architecture nsnotificationcenter iphone-privateapi


【解决方案1】:

所以,我做了一个非常简单的实验。我在调试器之外的越狱 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 的事件。所以,消费者应该打印出2 然后3,对于both回调方法,如果这工作正常的话。它没有(正如你所担心的那样):

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() 消费者的队列优先级更改为high,而不是background 优先级。结果:

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
  • 静态notifyCallback() 方法似乎被回调首先,除非GCD 回调块被放在高优先级队列中。即便如此,通常还是先调用静态回调函数。很多时候,第一个回调方法得到了正确的状态(2),而第二个没有。因此,如果您必须忍受这个问题,您可能会选择似乎表现最好的回调机制(或者至少,在您的应用中自己制作原型)。
  • 我不能说suspensionBehavior 参数有很大的不同。也就是说,根据 iOS 发布事件的方式,他们可能会使用像 CFNotificationCenterPostNotification 这样可能会忽略消费者行为请求的调用。
  • 如果您查看this Apple document,您会看到两件事。

    1. 首先,notify_set_state 不是原始 API 的一部分
    2. 其次,该文件的第一段说

达尔文通知 API 参考

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

所以,也许我们只是在尝试做一些与原始设计不一致的事情:(

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

【讨论】:

  • 非常感谢。优秀的答案。此外,有趣的是 Apple NotificationPoster 示例不使用 notify_get_state 和 notify_set_state,但他们的 iOS 使用。
  • 对于未来的读者,App Store 应用不再允许这样做:forums.developer.apple.com/message/225493#225493
  • @Warpling,谢谢。很高兴知道。如果您有时间,也许还可以在问题下方发表评论,因为此通知是 问题 的一部分。可能会提高知名度。
猜你喜欢
  • 2011-06-12
  • 1970-01-01
  • 1970-01-01
  • 2012-07-28
  • 1970-01-01
  • 1970-01-01
  • 2010-11-20
  • 2014-10-11
  • 1970-01-01
相关资源
最近更新 更多