Получение состояния для системных уведомлений в iOS и OS X
Я пытаюсь написать код, который будет обрабатывать включение / выключение экрана на 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 будет одинаковым для двух вызовов (он не изменяется). И я не думаю, что система хранит очередь состояний FIFO, которая должна быть возвращена notify_get_state.
1 ответ
Итак, я построил очень простой эксперимент. Я запустил это на взломанном iOS 6.1 iPhone 5, вне отладчика.
Код
Я создал потребительское приложение со следующим кодом:
#define EVENT "com.mycompany.bs"
- (void)registerForNotifications {
int result = notify_register_dispatch(EVENT,
¬ifyToken,
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
, для обоих методов обратного вызова, если это работает отлично. Это не так (как вы боялись)
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, вы увидите две вещи.
- Первый,
notify_set_state
не был частью очень оригинального API - Во-вторых, самый первый абзац в этом документе гласит:
- Первый,
Справочник по Darwin Notification API
Эти процедуры позволяют процессам обмениваться событиями уведомления без сохранения состояния.
Так что, может быть, мы просто пытаемся сделать что-то, что не согласуется с оригинальным дизайном:(
- Если вы также посмотрите на пример Apple NotificationPoster, вы увидите, что они не используют
notify_get_state
а такжеnotify_set_state
передать состояние. Они передают его с уведомлением в виде словаря информации о пользователе. Очевидно, что если вы наблюдаете события Apple на iOS, вы не можете контролировать их публикацию. Но в приложениях, где вы можете создать производителя и потребителя, я бы держался подальше отnotify_set_state
,