iOS 操作扩展中的后台任务

Posted

技术标签:

【中文标题】iOS 操作扩展中的后台任务【英文标题】:Background task in iOS action extension 【发布时间】:2018-11-14 12:40:23 【问题描述】:

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

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

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

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

任何想法,如何处理这样的任务?或者如何处理上面提到的解决方案之一?

编辑:因为这个问题还没有得到任何答案:什么不清楚或需要哪些额外信息?

【问题讨论】:

嗨,我想你在这里所做的可以回答我的问题。 Can you please share your experience? 为什么不使用 MCOAttachment 将附件与您的电子邮件一起发送?并将您的应用设置为在后台运行。 如何设置应用程序在后台运行?我已经使用 MCOAttachment。我尝试使用问题中提到的三种解决方案之一。所有这些都是我实现后台活动的尝试。但它们都没有产生有效的结果。 问题,当扩展程序运行时,您的原始应用程序是否在后台? 不,应用程序没有在后台运行(或者至少不能保证)。该扩展可以访问处理其任务所需的所有代码/库。问题是:如何在后台运行这段代码(=发送邮件),让用户无需等待完成。 【参考方案1】:

您似乎无法通过扩展在后台安排长时间运行的任务。请参阅here(Some APIs Are Unavailable to App Extensions)

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

【讨论】:

我心系无声通知。但作为 Vyacheslav 的回答,它需要额外的服务器来处理推送通知。有什么想法,如果本地通知(如闹钟)也可以静音?【参考方案2】:

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

【讨论】:

感谢您的回答!我的应用程序已启用后台活动。我检查了教程。描述了不同的背景模式。其中两个适合我想在后台执行的内容/任务:Executing Finite-Length Tasks - 这个不能从扩展中使用,因为它需要访问UIApplication.shared。另一个是 Background fetch - 这导致了我在问题中描述的问题。 您找到解决此问题的方法了吗? @cdn34 检查我上面的答案:***.com/a/60945640/768352【参考方案3】:

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

    创建一个可以检索简单请求的简单服务器:设备令牌和您想要唤醒设备的时间。 当应用程序进入后台模式时,向服务器发送请求以稍后唤醒设备。例如。 5分钟/20分钟等 func applicationWillEnterForeground(_ application: UIApplication) AppDelegate。 不要忘记让应用程序尽可能在后台运行。

例如,

   @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.
    
    

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

缺点,

    需要互联网 它并不完美。当电池电量不足时,后台模式会受到限制。

别忘了设置项目:

【讨论】:

这是个好主意。唯一需要发送推送通知的附加服务器。目前它是一个独立的应用程序,没有额外的要求。不过,可以使用 NSUrlSession 将信息发送到服务器。你知道没有互联网连接时 NSUrlSession 的行为吗?它会重试还是直接退出请求?【参考方案4】:

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

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的小总结中有详细描述。

【讨论】:

以上是关于iOS 操作扩展中的后台任务的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发简单的实现后台任务(诸如后台播放音乐,定时器,后台定位等)

IOS 13中的iOS后台任务不起作用

在iOS中的后台任务中调用Javascript函数

ios后台和android后台区别 iOS/Android系统多任务浅析

ios后台任务定时器

后台任务运行时未调用 iOS applicationWillEnterForeground 和黑屏