iOS操作扩展中的后台任务

时间:2018-06-05 06:06:41

标签: ios swift ios-app-extension mailcore2

我正在开发一个应用的动作扩展,为用户准备一些数据,然后使用MailCore2将此信息发送到SMTP服务器。准备数据的速度非常快,但发送电子邮件可能需要一些时间(取决于其大小)。这就是为什么我正在寻找一种在后台处理发送活动的方法。但这导致了不同解决方案的一些问题:

  1. 使用 URLSession 。这是在iOS扩展中处理大型上传或下载的第一种方法。但问题是MailCore2不使用URLSessions将数据传输到邮件服务器。所以这不可用。或者是否有可能以某种方式将此调用“包装”在URLSession中?

  2. 使用 UNUserNotificationCenter 并在数据准备后从扩展程序发送本地通知。我们的想法是在收到此通知后在主应用程序中启动发送任务。问题:仅当用户单击通知时才会调用通知userNotificationCenter didReceive方法。简单的徽章通知不会调用委托方法。

  3. 使用后台抓取。这在模拟器中工作正常,但在iOS设备上连接到SMTP服务器时出错。我遇到了与github.com/MailCore/mailcore2/issues/252中描述的相同的HELO问题。对于此问题,解决方案应为MCOSMTPSession.isUseHeloIPEnabled = true,但这可能不适用于所有服务器。所以也不是一个完美的解决方案。

  4. 任何想法,如何处理这样的任务?或者如何处理上述解决方案之一?

    编辑,因为此问题尚未收到任何答案:哪些不清楚或需要哪些其他信息?

4 个答案:

答案 0 :(得分:4)

您似乎 不能 在扩展程序中在后台安排长时间运行的任务。请参阅here(Some APIs Are Unavailable to App Extensions)

在您建议的解决方案中,我的建议是尝试使用 silent push notifications ,您可以在准备数据阶段调用api,然后从服务器发送静默推送并执行静默推送到达时执行后台任务。

答案 1 :(得分:2)

从您的回答来看,您的困难在于您需要允许后台活动。您可以通过following a tutorial such as this one完成此操作。

答案 2 :(得分:2)

最有用的解决方案是使用推送通知。 我在自己的应用程序中使用的解决方案:

  1. 创建一个简单的服务器,该服务器可以检索一个简单的请求:设备令牌和您希望唤醒设备的时间。
  2. 当应用程序进入后台模式时,将请求发送到服务器以稍后唤醒设备。例如。 5分钟/ 20分钟等 func applicationWillEnterForeground(_ application: UIApplication) {内的AppDelegate
  3. 别忘了让应用程序尽可能在后台运行。

例如,

   @UIApplicationMain
    class AppDelegate: UIResponder {
    var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
    func application(_ application: UIApplication,
                         didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                         fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
            if UIApplication.shared.applicationState != .active {
                doFetch(completionHandler: completionHandler)
            } else {
                // foreground here
                completionHandler(UIBackgroundFetchResult.noData)
            }
        }
    func application(_ application: UIApplication,
                         performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
            doFetch(completionHandler: completionHandler)
        }
    private func doFetch(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    sendTheRequestToWakeApp()
    backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
                self?.endBackgroundTask()
            }
/// do work here
    completionHandler(UIBackgroundFetchResult.noData)
    }
    private func endBackgroundTask() {
            print("Background task ended.")
            UIApplication.shared.endBackgroundTask(backgroundTask)
            backgroundTask = UIBackgroundTaskInvalid
        }
    private func sendTheRequestToWakeApp() {
    /// Implement request using native library or Alamofire. etc.
    }
    }

在服务器端使用简单的时间或循环。

缺点

  1. 需要互联网
  2. 它并不完美。电池电量不足时,背景模式将受到限制。

别忘了设置项目:

proejct screenshot

答案 3 :(得分:1)

经过多次测试和失败之后,我发现以下解决方案可在扩展程序的后台执行长期执行的任务。即使扩展名已经完成,它也能按预期工作:

func performTask()
{
    // Perform the task in background.
    let processinfo = ProcessInfo()
    processinfo.performExpiringActivity(withReason: "Long task") { (expired) in
        if (!expired) {
            // Run task synchronously.
            self.performLongTask()
        }
        else {
            // Cancel task.
            self.cancelLongTask()
        }
    }
}

此代码使用ProcessInfo.performExpiringActivity()在另一个线程中执行任务。 performLongTask()中的任务必须同步执行,这一点很重要。当到达块的末尾时,线程将终止并结束任务的执行。

主应用程序中也有类似的方法。在background tasks in iOS的小摘要中对此进行了详细说明。