iOS动作扩展中的后台任务

15

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

  1. 使用 URLSession。这是iOS扩展中处理大型上传或下载的首选方式。但问题是,MailCore2不使用URLSessions来传输数据到邮件服务器。所以这不可用。那么有没有可能以某种方式“包装”这个调用在URLSession中?

  2. 使用 UNUserNotificationCenter 并从扩展中发送本地通知来启动主应用程序中的发送任务。问题:只有在用户单击通知时,才会调用通知的userNotificationCenter didReceive方法。简单的标记通知不会调用委托方法。

  • 使用Background Fetch。在模拟器中运作良好,但在iOS设备上连接到SMTP服务器时会出现错误。我遇到了与github.com/MailCore/mailcore2/issues/252描述的相同的HELO问题。对于此问题,一个解决方案应该是MCOSMTPSession.isUseHeloIPEnabled = true,但这可能并不适用于所有服务器。因此也不是完美的解决方案。

  • 有什么想法,如何处理这样的任务?或如何处理上述解决方案之一?

    编辑:由于这个问题还没有得到任何回答:有什么不清楚或需要补充的信息吗?


    你好,我认为你在这里所做的可以回答我的问题。你能分享一下你的经验吗? - えるまる
    为什么不使用MCOAttachment将附件与您的电子邮件一起发送?并设置您的应用程序在后台运行。 - Francois Nadeau
    你如何设置应用程序在后台运行?我已经使用了MCOAttachment。我尝试使用问题中提到的三个解决方案之一。它们都是我尝试实现后台活动的尝试,但是没有一个能够产生工作结果。 - mixable
    为什么不使用包含应用程序来发送邮件呢?扩展程序可以通过应用组通知包含应用程序使用所需的数据发送电子邮件,这意味着应用程序正在运行 - 但如果需要,您也可以从扩展程序启动包含应用程序。希望能有所帮助! - Mihai Erős
    @mixable 我担心在后台打开应用程序而不可见并不是苹果公司会认同的事情,即使这是可能的,我也不认为这是可行的。 - Mihai Erős
    显示剩余3条评论
    4个回答

    4
    似乎您不能从扩展程序中安排长时间运行的后台任务。请参考此处(某些API对应用程序扩展不可用)
    在您提出的解决方案中,我的建议是尝试使用静默推送通知,您可以在准备数据阶段调用API,然后从服务器发送静默推送,并在接收到静默推送时执行后台任务。

    我很喜欢无声通知。但是根据Vyacheslav的回答,它需要一个额外的服务器来处理推送通知。有什么想法,如果本地通知(例如来自闹钟)也可以是无声的吗? - mixable

    3

    经过多次测试和失败,我找到了以下解决方案,可以在扩展程序的后台执行长时间的任务。即使扩展程序已经完成,这个方法也能正常工作:

    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()中的任务被同步执行。当块的结尾被达到时,线程将终止并结束您的任务的执行。
    类似的方法也适用于主应用程序。有关iOS后台任务的详细信息,请参阅iOS后台任务的简要概述。

    关于共享扩展,上面的自我引用指的是包含函数的什么?我应该使用class ShareViewController: UIViewController还是创建自己的类而不继承任何基础对象? - daniel
    我应该询问这是否适用于任何应用程序扩展,或者至少适用于共享扩展。 - daniel
    对我来说,这适用于共享扩展和操作扩展。对于两者,我都使用一个自定义类继承自UIViewController,就像你已经提到的class ShareViewController: UIViewController一样。因此,self应该指向你的类对象。 - mixable
    看起来你根本没有使用BGTaskScheduler。我想我应该在我的共享扩展中安排代码,背景任务执行的代码将在包含应用程序的主目标中进行。我仍然正在完全阅读这个stackoverflow的问题。我不知道其他答案是否有效。我不知道我想执行的任务是否需要BGProcessingTask,但这是我首先尝试的。 - daniel

    2

    从您的回复来看,您遇到的困难是需要允许后台活动。您可以通过学习此教程来实现。


    谢谢你的回答!我的应用程序已经启用了后台活动。我查看了教程。其中描述了不同的后台模式。其中有两种适用于我想在后台执行的内容/任务:执行有限长度任务 - 这个无法从扩展中使用,因为它需要访问UIApplication.shared。另一个是后台获取 - 这导致了我在问题中描述的问题。 - mixable
    你找到解决这个问题的办法了吗? - cdn34
    @cdn34,请查看我上面的回答:https://dev59.com/0VUL5IYBdhLWcg3wM1ry#60945640 - mixable

    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. 它不完美工作。当电池电量低时,后台模式受到限制。
    别忘了设置项目:

    项目截图

    "Original Answer"翻译成"最初的回答"

    这是一个好主意。唯一的问题是需要额外的服务器来发送推送通知。目前它是一个独立的应用程序,没有其他要求。尽管如此,可能可以使用NSUrlSession将信息发送到服务器。你知道当没有网络连接时,NSUrlSession会怎么做吗?它会重试还是只是放弃请求? - mixable

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接