是否可以使用NSTimer将越狱的iPhone从深度睡眠中唤醒?
注意:编辑中的下方是简单的代码,可以在没有原始程序完全复杂的情况下生成问题。
Note: further down in the edits there's simple code that generates the problem without the full complexity of my original program.
我正在尝试编写警报 - 适用于越狱iOS的时钟应用程序。我将UI设置为用于安排警报的独立应用程序,然后将警报信息保存到磁盘。保存文件由始终运行的启动守护程序读取,该守护程序处理实际调度警报。
I'm trying to code an alarm-clock app for jailbroken iOS. I have a UI set up as a standalone application for scheduling the alarms, which then saves the alarm information to disk. The save file is read by a launch daemon that's always running, which deals with actually scheduling the alarms.
我正在调度警报(编辑:在守护程序中) )( NSDate * fireDate
先前已计算):
I'm scheduling the alarms as so ( in the daemon) (NSDate *fireDate
is calculated earlier):
NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:fireDate
interval:0
target:self
selector:@selector(soundAlarm:)
userInfo:alarm
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:singleTimer
forMode:NSRunLoopCommonModes];
[self.timers addObject:singleTimer];
[singleTimer release];
编辑:上面的代码在一个叫做的方法中运行 createTimers
,由 reloadData
调用。 reloadData
从共享保存文件中读取有关计时器的信息,并在 AMMQRDaemonManager
的init函数中调用它,如以及当经理获得通知(使用 notify_post
)时,UI应用程序已更新保存文件。
the above code runs in a method called createTimers
, which gets called by reloadData
. reloadData
reads information about the timers from the shared save file, and it gets called in AMMQRDaemonManager
's init function, as well as whenever the manager gets a notification (with notify_post
) that the UI app has updated the save file.
soundAlarm:
方法(编辑:也在守护程序中)是:
The soundAlarm:
method ( also in the daemon) is:
- (void)soundAlarm:(NSTimer *)theTimer {
NSLog(@"qralarmdaemon: sounding alarm");
extern CFStringRef kCFUserNotificationAlertTopMostKey;
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(dict, kCFUserNotificationAlertTopMostKey, kCFBooleanTrue);
CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, CFSTR("Title"));
CFDictionaryAddValue(dict,kCFUserNotificationDefaultButtonTitleKey, CFSTR("OK"));
SInt32 err = 0;
CFUserNotificationRef notif = CFUserNotificationCreate(NULL,
0, kCFUserNotificationPlainAlertLevel, &err, dict);
CFOptionFlags response;
if((err) || (CFUserNotificationReceiveResponse(notif, 0, &response))) {
// do stuff
} else if((response & 0x3) == kCFUserNotificationDefaultResponse) {
// do stuff
}
CFRelease(dict);
CFRelease(notif);
// Do some other stuff
}
这是有效的很棒,并显示电话是否已解锁或锁定的警报。但是如果手机被锁定足够长的时间进入深度睡眠状态,那么计时器就无法启动。
This works great, and shows the alert whether the phone is unlocked or locked. But if the phone is locked for a sufficient period of time to enter deep sleep then the timer just fails to fire.
我不需要它就必须转动屏幕on(虽然那会很好),因为除了显示警报之外我还会播放声音,但是我确实需要定时器才能启动声音。
I don't need it to necessarily turn the screen on (though that would be nice) since I'll also be playing sound in addition to displaying the alert, but I do need the timer to fire so that I know when to start the sound.
任何想法?
编辑:这是 main
守护进程的函数。
Here is the main
function for the daemon.
int main(int argc, char **argv, char **envp) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"qralarmdaemon: launched");
AMMQRDaemonManager *manager = [[AMMQRDaemonManager alloc] init];
NSTimer *keepRunningTimer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture]
interval:1000
target:manager
selector:@selector(keepRunning:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:keepRunningTimer
forMode:NSRunLoopCommonModes];
// Execute run loop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
[manager release];
NSLog(@"qralarmdaemon: exiting");
[pool release];
return 0;
}
(不包括注册来自主应用程序的通知以获知的代码何时读取保存文件等,但我不认为这是相关的。)
(Not included is the code that registers for notifications from the main app to know when to read in the save file, etc, but I don't think that's relevant).
编辑(再次) :我在运行循环中添加了一个计时器,该计时器在 [NSDate distantFuture]
时触发。这似乎可以更长时间地保留计时器(计时器在手机被锁定后45秒后计划停止,然后唤醒手机)但不是无限期(计时器预定7分钟,手机锁定后30秒没有响起)。
EDIT (again): I've added a timer to the run loop that fires at [NSDate distantFuture]
. This seems to preserve the timers longer (a timer scheduled 1 min 45 secs after the phone was locked went off, and woke up the phone) but not indefinitely (a timer scheduled 7 min, 30 seconds after the phone was locked did not go off).
编辑:我构建了以下玩具示例来说明问题,而不必担心与我的代码的其他部分。
I've constructed the following toy example that illustrates the problem, without having to worry about interactions with other parts of my code.
我编译了这段代码,SSH进去了,然后运行它,然后锁定了我的手机。如果我将 dateByAddingTimeInterval:480
更改为 dateByAddingTimeInterval:30
,我会得到以下输出:
I compiled this code, SSH'd in, and ran it, then locked my phone. If I change the dateByAddingTimeInterval:480
to dateByAddingTimeInterval:30
, I get the following output:
2013-03-31 12:21:25.555 daemontimertest[6160:707] daemon-timer-test: launched
2013-03-31 12:21:56.265 daemontimertest[6160:707] daemon-timer-test: timer fired
但是当它设置为480,我等待超过8分钟,只能看到第一行:
But when it's set to 480, I wait more than 8 minutes and only see the first line:
2013-03-31 12:08:09.331 daemontimertest[6049:707] daemon-timer-test: launched
main.m
:
#import "MyClass.h"
int main(int argc, char **argv, char **envp) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"daemon-timer-test: launched");
MyClass *obj = [[MyClass alloc] init];
NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:[[NSDate date] dateByAddingTimeInterval:480]
interval:0
target:obj
selector:@selector(fireTimer:)
userInfo:nil
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:singleTimer
forMode:NSRunLoopCommonModes];
// Execute run loop
[[NSRunLoop currentRunLoop] run];
[pool release];
return 0;
}
MyClass.m
:
#import "MyClass.h"
@implementation MyClass
- (void)fireTimer:(NSTimer *)theTimer {
NSLog(@"daemon-timer-test: timer fired");
}
@end
编辑(2013年3月31日下午5:50):我添加了以下代码玩具应用程序代码,以纳入Nate建议使用GCD的 dispatch_after
功能,但它似乎受到相同的时间限制。另外需要注意的是,主UI应用程序安装在 / Applications
中,守护程序安装在 / usr / bin
中。
EDIT (3/31/13 5:50 EDT): I've added the following code the toy app code to incorporate Nate's suggestion of using GCD's dispatch_after
functionality, but it appears subject to the same time constraints. As an additional note, the main UI app is installed in /Applications
and the daemon is installed in /usr/bin
.
double delayInSeconds = 10.0;
NSLog(@"daemon-timer-test: delay is %f",delayInSeconds);
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(@"daemon-timer-test: time has passed.");
});
编辑(3/31 5:54 PM) :另一个快速说明。以下几行显示(不是连续)在syslog进入深度睡眠之前,并且在我唤醒手机之前没有其他消息。我选择了看起来可能相关的那些;最后一条消息是深度睡眠前发送到系统日志的最后一条消息。
EDIT (3/31 5:54 PM): Another quick note. The following lines show up (not consecutively) in syslog right before it appears to go into deep sleep and there are no more messages before I wake the phone up. I've selected the ones that look like they may be relevant; the last message is the very last one sent to syslog before deep sleep.
Mar 31 17:34:23 Andrew-MacKie-Masons-iPhone lockdownd[50]: 002c1000 -[hostWatcher handleSleepNotification:service:messageArgument:]: <hostWatcher: 0x1cd59890> [CC535EDB-0413-4E5E-A844-4DA035E7217C 169.254.2.141:54757] [fd=13]: kIOMessageCanSystemSleep
Mar 31 17:34:23 Andrew-MacKie-Masons-iPhone lockdownd[50]: 002c1000 -[hostWatcher handleSleepNotification:service:messageArgument:]: <hostWatcher: 0x1cd59890> [CC535EDB-0413-4E5E-A844-4DA035E7217C 169.254.2.141:54757] [fd=13]: kIOMessageSystemWillSleep
...
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone lockdownd[50]: 00343000 __63-[hostWatcher handleSleepNotification:service:messageArgument:]_block_invoke_0: Allowing Sleep
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone powerd[42]: PM scheduled RTC wake event: WakeImmediate inDelta=645.40
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone powerd[42]: Idle Sleep Sleep: Using BATT (Charge:76%)
...
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone kernel[0]: en0::stopOutputQueues
...
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone kernel[0]: pmu wake events: menu
我已经解决了一种适合我的方法。根据我与Nate的长期交流(我肯定无法在没有他帮助的情况下解决发生的事情),这似乎在某些系统上自动发生,而在其他系统上则不然。我手机上的问题似乎是 powerd
让手机进入某种深度睡眠状态,暂停 NSTimer
s并且不允许它们正常射击。
I've worked out a method that works for me. As per my long exchange with Nate (and I definitely wouldn't have been able to work out what was going on without his help), this seems to happen automatically on some systems, but not on others. The problem on my phone seemed to be that powerd
was putting the phone into some sort of deep sleep that paused the NSTimer
s and didn't allow them to fire properly.
我没有禁用深度睡眠(我怀疑它具有负面功率影响)我安排了一个电源事件:
Rather than disabling deep sleep (which I suspect has negative power implications) I scheduled a power event:
NSDate *wakeTime = [[NSDate date] dateByAddingTimeInterval:(delayInSeconds - 10)];
int reply = IOPMSchedulePowerEvent((CFDateRef)wakeTime, CFSTR("com.amm.daemontimertest"), CFSTR(kIOPMAutoWake));
这可以在警报响起前10秒成功唤醒手机。 (间隔时间不准确。我希望它足够短,手机不会再回来睡觉了,但足够长,如果手机需要一点时间才能唤醒定时器仍然可以在合适的时间进行。我可能会将它缩短到3或4秒。
This successfully wakes the phone 10 seconds before the alarm is supposed to go off. (The interval isn't precise. I wanted it to be short enough that the phone wouldn't go back to sleep, but long enough that if the phone takes a moment to wake up the timer can still go at the right time. I'll probably shorten it to just 3 or 4 seconds.
剩下的问题是警报的 NSTimer
它本身不会自动更新,所以在电话睡眠的任何时间段都会迟到。为了解决这个问题,你可以在电话唤醒时取消并重新安排NSTimer。我是通过注册电源通知来做到这一点的。管理系统在电源状态发生变化时发布:
The remaining problem is that the NSTimer
for the alarm itself won't update automatically, and so it'll be late by whatever period the phone was asleep for. To fix this you can cancel and reschedule the NSTimer whenever the phone wakes up. I did this by registering for a notification that the power management system posts whenever the power state changes:
int status, notifyToken;
status = notify_register_dispatch("com.apple.powermanagement.systempowerstate",
¬ifyToken,
dispatch_get_main_queue(), ^(int t) {
// do stuff to cancel currently running timer and schedule a new one here
});
这里的低效率是通知在睡眠和醒来时发布,但我还没有能够找到另一种选择。
The inefficiency here is that the notification is posted both on sleeps and wakes, but I haven't been able to find an alternative yet.
我希望这对任何正在努力解决这个问题的人都有帮助。
I hope this is helpful to anyone else who was struggling with this issue.