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 时,它​​将确定有两个通知“屏幕已打开”,而不是一个通知“屏幕已关闭”和另一个“屏幕已打开”。

PS 一般来说,所有描述的问题并不特定于屏幕开/关情况。它们对于具有不同状态并使用相同通知名称发送的任何系统范围的通知都是实际的。


更新 1

我没有测试非常快速地打开/关闭屏幕并从 notify_get_state() 获得相同结果的场景。

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

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

4

1 回答 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。因此,对于这两种回调方法,消费者应该打印出2then ,如果这工作正常的话。它不会(正如您所担心的那样):3

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,而不是后台优先级。结果:

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. 其次,该文件的第一段说

达尔文通知 API 参考

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

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

  • 如果您还查看Apple 的 NotificationPoster 示例,您会发现它们不使用notify_get_stateandnotify_set_state来传达状态。他们将通知作为用户信息字典传递。显然,如果您正在观察 Apple 的 iOS 事件,则无法控制事件的发布方式。但是,在您可以创建生产者消费者的应用程序中,我会远离notify_set_state.
于 2013-02-21T00:20:06.050 回答