是否有可能使用NSTimer唤醒越狱的iPhone深度睡眠?

dre*_*wmm 4 iphone objective-c jailbreak ios

注意:编辑中的下方是简单的代码,可以在没有原始程序完全复杂的情况下生成问题.

我正在尝试为越狱的iOS编写一个闹钟应用程序.我将UI设置为用于安排警报的独立应用程序,然后将警报信息保存到磁盘.保存文件由始终运行的启动守护程序读取,该守护程序处理实际调度警报.

我正在安排警报(编辑:在守护进程中)(NSDate *fireDate之前计算):

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];
Run Code Online (Sandbox Code Playgroud)

编辑:上面的代码在一个名为的方法中运行,该方法createTimers被调用reloadData.reloadData从共享保存文件中读取有关计时器的信息,并在AMMQRDaemonManager初始化函数中调用它,以及每当管理器收到notify_postUI应用程序更新了保存文件的通知时.

soundAlarm:方法(编辑:也在守护进程)为:

- (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
}
Run Code Online (Sandbox Code Playgroud)

这很好用,并显示电话是否已解锁或锁定的警报.但如果手机被锁定足够长的时间进入深度睡眠状态,那么计时器就无法启动.

我不需要它必须打开屏幕(虽然那会很好),因为除了显示警报之外我还会播放声音,但我确实需要定时器才能启动,以便我知道何时启动声音.

有任何想法吗?


编辑:这是main守护进程的功能.

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;
}
Run Code Online (Sandbox Code Playgroud)

(不包括注册来自主应用程序的通知以了解何时读取保存文件等的代码,但我不认为这是相关的).


编辑(再次):我已经为运行循环添加了一个计时器[NSDate distantFuture].这似乎可以更长时间地保留计时器(计时器在手机被锁定后的45分钟内安排下来,然后唤醒手机)但不是无限期(计时器预定7分钟,手机锁定30秒后没有响起).


编辑:我构建了以下玩具示例来说明问题,而不必担心与代码的其他部分的交互.

我编译了这段代码,用SSH连接,然后运行它,然后锁定了我的手机.如果我更改dateByAddingTimeInterval:480dateByAddingTimeInterval:30,我得到以下输出:

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
Run Code Online (Sandbox Code Playgroud)

但是当它设置为480时,我等待超过8分钟并且只看到第一行:

2013-03-31 12:08:09.331 daemontimertest[6049:707] daemon-timer-test: launched
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

MyClass.m:

#import "MyClass.h"

@implementation MyClass

- (void)fireTimer:(NSTimer *)theTimer {
    NSLog(@"daemon-timer-test: timer fired");
}

@end
Run Code Online (Sandbox Code Playgroud)

编辑(2013年3月31日下午5点50分):我添加了以下代码玩具应用程序代码,以纳入Nate使用GCD dispatch_after功能的建议,但它似乎受到相同的时间限制.另外请注意,安装了主UI应用程序并安装/Applications了守护程序/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.");
    });
Run Code Online (Sandbox Code Playgroud)

编辑(3/31 5:54 PM):另一个快速说明.以下几行显示(不是连续)在syslog之前,它似乎进入深度睡眠状态,并且在我唤醒手机之前没有其他消息.我选择了看起来可能相关的那些; 最后一条消息是深度睡眠前发送到syslog的最后一条消息.

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
Run Code Online (Sandbox Code Playgroud)

Nat*_*ate 9

简答

是的,这是可能的(我已经完成了).

我尝试了几种不同的方式,我是不是能得到我的守护/ NSTimer失败在你所描述的方式.但是,我还没有看到定义你的应用程序的所有文件/代码,所以至少还有一件我关心的事情.

保持守护进程活着

如果您查看Apple文档中的NSRunLoop运行:

如果没有输入源或定时器附加到运行循环,则此方法立即退出; 否则,它通过反复调用runMode:beforeDate:来运行NSDefaultRunLoopMode中的接收器.换句话说,这种方法有效地开始了一个无限循环,它处理来自运行循环的输入源和定时器的数据.

从运行循环中手动删除所有已知输入源和计时器并不能保证运行循环将退出.OS X可以根据需要安装和删除其他输入源,以处理针对接收者线程的请求.因此,这些来源可以防止运行循环退出.

在您为守护main程序显示的代码中,您没有(直接)创建任何计时器.当然,我不知道你做了什么[[AMMQRDaemonManager alloc] init],所以也许我错了.然后你使用:

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
Run Code Online (Sandbox Code Playgroud)

开始运行循环.问题是,如果此时没有计时器,我不确定你的守护进程会活着.如果你看一下上面的第二段,它也表明它可能会保持活着,所以也许这就是为什么当我尝试使用你的代码时我没有看到我的守护进程死亡的原因.

您的评论说,当警报响起时,您会看到守护进程处于活动状态.但是,我想知道你的守护进程是否真的死了,然后重新启动了.也许你也可以向我们展示你用于启动守护进程的那个.plist文件(进入/System/Library/LaunchDaemons).

一个快速实验,可能是自动启动守护进程.只需从LaunchDaemons文件夹中卸载plist文件,并确保终止该进程.然后,从命令行手动启动它,ssh到手机:

$ /Applications/MyApp.app/MyDaemon
Run Code Online (Sandbox Code Playgroud)

然后,观察命令行.你会看到它是否会死,并且由于它实际上并没有被运行launchd,如果它死了就不会重新启动它.

解?

如果事实证明你确实遇到了它的问题,那么我会尝试添加一个计时器,它总是在守护进程时启动.如果你看看我的另一个例子,或者Chris Alvares的守护进程教程,它会显示出来.在守护进程中main(),您设置一个NSTimer以触​​发run:方法.在该run:方法中,您可以使用while循环和sleep()调用.或者只是安排计时器以某个缓慢的间隔重复.

我也不确定你的整个应用程序是如何工作的.它只是调度(NSTimer)警报的工具吗?如果是这样,可能在任何时候都可能没有设置警报.也许是另一种解决方案,您可以将守护程序配置为仅查看数据文件,而不是使用UIApplicationto notify_post()将新计时器传递给守护程序.该会写出来的数据文件,每当有一个新的计时器.然后,iOS可以唤醒你的守护进程安排. UIApplicationNSTimer

无论如何,这可能是与原始问题不同的问题,但它也可能是构建闹钟守护程序的更有效方法,因为如果没有活动警报,它实际上不需要运行.

如果这些想法无法帮助你修复它([AMMQRDaemonManager init]可能会有所帮助),请发布更多信息.

更新

还有两个建议:

  • 确保安装了您的应用程序(守护程序和UI)/Applications.这是越狱应用程序的正常位置,但我只是想确保您没有在沙箱区域中安装它.

  • 尝试使用GCD块替换您的NSTimer实现(对于警报,您可以保留main()守护进程keepalive计时器):

   // you have used notify_post() to tell the daemon to schedule a new alarm:
   double delayInSeconds = 1000.0;
   dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
   dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
      // put timer expiration code here
   });
Run Code Online (Sandbox Code Playgroud)

更新II

我还注意到,在原始alarm:回调中,您使用CFUserNotificationReceiveResponse()无限超时.这意味着如果用户没有关闭弹出窗口,则定时器回调将无法完成,我相信这意味着没有随后安排的定时器回调可以触发.也许,您应该将所有CFUserNotification代码放入自己的方法(例如showPopup)中,然后让您的计时器回调如下:

- (void)soundAlarm:(NSTimer *)theTimer {
   dispatch_async(dispatch_get_main_queue(), ^(void) {
       [self showPopup];
   });
}
Run Code Online (Sandbox Code Playgroud)

然后,有主程序(在你放在Dropbox上的代码中).我建议更改你的计时器(你直接从中调用main())作为重复计时器,间隔相对较小,而不是使用火灾日期distantFuture.如果你愿意,你可以做任何事情.这只是一个心跳.

main.m文件:

NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:[NSDate date]
                                                interval:5*60   // 5 minutes
                                                  target:obj
                                                selector:@selector(heartbeat:)
                                                userInfo:nil
                                                 repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:singleTimer
                             forMode:NSRunLoopCommonModes];
Run Code Online (Sandbox Code Playgroud)

MyClass.m:

- (void)heartbeat:(NSTimer *)theTimer {
   NSLog(@"daemon-timer-test: heartbeat timer fired");
}
Run Code Online (Sandbox Code Playgroud)

我最后的评论是我不使用syslogd.我想知道你的测试是否有任何失败,不是因为计时器没有运行,而是因为NSLog语句没有出现在你的日志文件中.我已经完成了所有测试,我实际上在命令行运行守护程序可执行文件,ssh到手机中,我只是看控制台NSLog输出.从可能的故障点列表中删除...