12

由于iOS框架不允许本地通知在发布之前执行代码,我正在寻找一种在越狱设备上实现它的方法。

  • 是否在越狱设备上内置了功能来安排代码执行而无需用户交互?
  • 代码应下载更新并确定用户是否应收到通知。
  • 不想使用推送通知,这需要外部服务器将它们推送给用户。

更新

好吧,我已经设法创建了一个在启动时启动并保持自身运行的守护进程。但是,发布通知需要UIApplication对象。根据文档,这个单例是由UIApplicationMain()方法创建的,对于常规应用程序,该方法由main(). 由于我希望由守护程序发布通知,因此单例为零。

我可以创建一个实例UIApplication吗?或者以其他方式发布通知?

我已经尝试调用UIApplicationMain()然后在应用程序委托中发布通知,以及终止应用程序,但这会暂时显示黑屏;我猜它启动了应用程序。此外,当应用程序无法启动时(当手机尚未完全启动时),它会导致守护程序崩溃。

这是代码的草图

int main(){
   if(launchedBySpringBoard || launchedBynotification)
      UIApplicationMain(...);
   else if(launchedByDaeamon)
      StartRunLoop();
}

void triggerdByRunLoopEveryXhours(){
    downloadData();
    if(isNewData())
       postNotification();
}
4

3 回答 3

12

...或者以其他方式发布通知?

的。您可以使用触发通知(不一定UILocalNotification)的后台(启动)守护程序来完成这项工作。当通知向用户显示警报时,您的守护程序可以决定是否打开普通的 UI 应用程序。

构建启动守护程序。

这是我找到的最好的教程。启动守护程序在手机启动时启动,并一直作为非图形后台进程运行。从那里,您可以安排检查更新。(我有一个HelloDaemon在方法中完成所有工作的类run:):

int main(int argc, char *argv[]) {
    @autoreleasepool {
        HelloDaemon* daemon = [[HelloDaemon alloc] init];
        
        // start a timer so that the process does not exit.
        NSTimer* timer = [[NSTimer alloc] initWithFireDate: [NSDate date]
                                                  interval: 1.0
                                                    target: daemon
                                                  selector: @selector(run:)
                                                  userInfo: nil
                                                   repeats: NO];
        
        NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
        [runLoop addTimer: timer forMode: NSDefaultRunLoopMode];
        [runLoop run];
    }    
    return 0;
}

守护进程可以NSTimer正常使用,因此安排另一个计时器(在 内run:)检查更新以随时下载。

从守护进程通知用户

如果守护进程决定应该通知用户,那么您可以:

1)打开完整的 UI 应用程序。

#include <dlfcn.h>
#define SBSERVPATH "/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices"

-(void) openApp {
  
    // the SpringboardServices.framework private framework can launch apps,
    //  so we open it dynamically and find SBSLaunchApplicationWithIdentifier()
    void* sbServices = dlopen(SBSERVPATH, RTLD_LAZY);
    int (*SBSLaunchApplicationWithIdentifier)(CFStringRef identifier, Boolean suspended) = dlsym(sbServices, "SBSLaunchApplicationWithIdentifier");
    int result = SBSLaunchApplicationWithIdentifier(CFSTR("com.mycompany.AppName"), false);
    dlclose(sbServices);
}

此代码需要com.apple.springboard.launchapplications您的守护程序授权才能成功使用它。 有关添加权利的信息,请参见此处您的守护程序可执行文件需要一个 entitlements.xml 文件,如下所示:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>com.apple.springboard.launchapplications</key>
        <true/>
    </dict>
</plist>

2)从你的守护进程中显示一个简单的警报窗口,通知用户该事件,并提示他们打开 UI 应用程序

#include "CFUserNotification.h"

-(void) showAlert {
  
    NSMutableDictionary* dict = [NSMutableDictionary dictionary];
    [dict setObject: @"Alert!" forKey: (__bridge NSString*)kCFUserNotificationAlertHeaderKey];
    [dict setObject: @"Updates Ready!" forKey: (__bridge NSString*)kCFUserNotificationAlertMessageKey];
    [dict setObject: @"View" forKey:(__bridge NSString*)kCFUserNotificationDefaultButtonTitleKey];
    [dict setObject: @"Cancel" forKey:(__bridge NSString*)kCFUserNotificationAlternateButtonTitleKey];
    
    SInt32 error = 0;
    CFUserNotificationRef alert =
    CFUserNotificationCreate(NULL, 0, kCFUserNotificationPlainAlertLevel, &error, (__bridge CFDictionaryRef)dict);
    
    CFOptionFlags response;
    // we block, waiting for a response, for up to 10 seconds
    if((error) || (CFUserNotificationReceiveResponse(alert, 10, &response))) {
        NSLog(@"alert error or no user response after 10 seconds");
    } else if((response & 0x3) == kCFUserNotificationAlternateResponse) {
        // user clicked on Cancel ... just do nothing
        NSLog(@"cancel");
    } else if((response & 0x3) == kCFUserNotificationDefaultResponse) {
        // user clicked on View ... so, open the UI App
        NSLog(@"view");
        [self openApp];
    }
    CFRelease(alert);
}

您需要一个CFUserNotification.h标题才能按照我上面的方式使用代码。您可以通过谷歌搜索找到一个,或在此处查看一个。这个较旧的 wiki 文档还显示了一些有关CFUserNotification从 iOS 应用程序使用的有用信息。

我从上面的 KennyTM 链接到的答案还显示了如何使警报弹出显示,即使设备被锁定。

于 2013-03-16T22:44:53.037 回答
5

首先,让我说 BigLex 提供了非常有趣的信息。但是,我从未尝试为越狱的 iphone 编写守护程序。所以,我不知道限制(看起来有一些限制 - 比如 UIApplication sharedApplication 是零。

几个想法:

背景

1)如果您计划通过 Cydia 分发(意味着应用程序最终将位于系统卷上),您可以使用两种未记录的后台模式:

“continuos”(这个会一直在后台运行)“unboundedTaskCompletion”(这个会有无限时间,如果你会做 [UIApplication beginBackgroundTaskWithExpirationHandler]

您可以在此处查看使用 continouse 的示例 Info.plist。

2)还有其他方法可以获得永久背景(甚至不需要越狱设备)。

例如,常见的方法是在循环上运行静音音频。是如何执行此操作的示例。

请注意,此方法不会被 App Store 接受。

3) 在这种情况下,如果您使用路线 1) 或 2),您将可以访问 [UIApplication sharedApplication) 来发布本地通知

4)您可能有兴趣查看Backgrounder。我相信它为越狱设备实现了后台功能。但是,它可能已过时。

UIApplication 的守护进程问题

5)关于守护进程的问题。如果你仔细阅读那篇文章,你会看到

首先要注意的是,使用 UIApplication 类来启动你的守护进程是不好的(它占用的内存比我们需要的要多),所以我们要编写自己的 main 方法。

因此,那里的代码针对内存进行了优化。但是,我很确定您可以将其替换为常见的 iOS 应用程序代码:

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

结果,我认为您应该拥有 UIApplication 单例并且应该能够发布本地通知。

是的......它会消耗额外的 X 千字节内存,但谁在乎(如果你没有运行 100 个这样的守护进程)

于 2013-03-16T21:30:19.157 回答
2

只是猜测,这不是一个真正的答案,但也许您可以使用 MobileSubstrate 的挂钩功能来挂钩操作系统的通知处理过程,并告诉操作系统执行一些代码来检查通知是否来自您的应用程序,如果是这样的话,检查更新并决定是否应该显示通知?

或者,也许您可​​以启动一个后台进程,每隔 X 分钟检查是否有任何更新,如果有,则立即设置本地通知。不知道你怎么能做到这一点。

于 2013-02-22T13:23:00.737 回答