美文网首页
Background Modes详细解析(一) —— 几种Mod

Background Modes详细解析(一) —— 几种Mod

作者: 刀客传奇 | 来源:发表于2022-11-05 18:04 被阅读0次

    版本记录

    版本号 时间
    V1.0 2022.11.06 星期日

    前言

    Background Modes我们在程序中总会用到,包括语音、定位更新、后台任务以及远程通知等,这个模块我们就一起来学习下。

    开始

    Background Modes是我们常用的模式,比如语音、定位更新、后台任务以及远程通知等。Xcode里的后台模式如下所示:

    在本教程中,您将创建一个使用音频播放、位置更新、关键任务和后台拉取的应用程序,以了解最常见的后台模式。本文来自翻译

    2010年,随着iOS 4的发布,苹果开始允许应用程序在后台工作,并从那时起不断发展和改进后台模式。iOS限制使用后台操作来改善用户体验和延长电池寿命。你的应用可以在后台运行特定的情况,包括:播放音频,更新位置和从服务器获取最新的内容。

    如果你的任务不属于允许的类别,后台模式可能不适合你。如果你试图使用超出其作用范围的后台模式来操纵系统,你可能会面临App Store的拒绝。

    在本后台模式教程中,你将了解你的应用程序可以在后台做的四件事:

    • Play audio - 播放音频:允许应用程序在后台继续播放音频。
    • Receive location updates - 接收位置更新:允许应用程序在后台接收位置更改。
    • Complete finite-length critical tasks - 完成有限长度的关键任务:允许应用程序在移动到后台后继续完成关键任务。
    • Background Fetch - 后台获取:在iOS调度的时间表上执行后台更新。

    在深入研究之前,我们先来快速浏览一下iOS的基本后台模式:

    • Audio, AirPlay, and Picture in Picture - 音频,AirPlay,和图片中的图片:当应用程序在后台时播放音频和视频。
    • Location Updates - 位置更新:在后台时继续接收位置更新。
    • Voice over IP - IP语音:通过因特网发送和接收语音。
    • External accessory communication - 外部配件通信:通过lightning接口与外部配件通信。
    • Using Bluetooth LE accessories - 使用蓝牙LE配件:在后台与蓝牙LE配件通信。
    • Acting as a Bluetooth LE accessory - 充当蓝牙LE配件:允许应用程序为配件提供蓝牙LE信息。
    • Background fetch - 后台拉取:执行数据刷新。
    • Remote notitifications - 远程通知:发送和接收远程通知。
    • Background processing - 后台处理:执行较长的关键进程。

    您将向示例应用程序添加上述模式中的四种——音频、定位、后台处理和后台获取(audio, location, background processing and background fetches)。如果你只对其中的一些模式感兴趣,可以随意跳过,只玩你感兴趣的模式。

    注意:要获得完整的效果,您应该在真实的设备上进行操作。在模拟器中,当你忘记一个步骤时,应用程序可能会在后台运行。然后当你切换到真正的设备时,它可能根本无法工作。

    在您可以在物理设备上运行项目之前,您必须设置您的development team,如下所示:

    构建并运行示例项目来感受一下Sleepless,这是一个从不休息的应用程序,因为它在后台做事情。有四个tab —— 每个覆盖一个模式:

    您要添加的第一个capabilitybackground audio


    Playing Audio

    在物理设备上构建并运行Sleepless。导航到audio tab,播放音乐,然后通过返回主屏幕把应用程序放在后台。音乐将停止播放。

    打开 AudioModel.swift

    该应用程序利用AVQueuePlayer对歌曲进行排队并按顺序播放。模型观察播放器的currentItem值以提供视图的更新。

    1. Giving Credit Where Credit Is Due

    最初的项目包括来自incompetech.com的音频文件,这是一个流行的免版税音乐网站。你可以免费使用带有版权的音乐。这三首歌都是Kevin MacLeod写的:

    “Feelin Good” Kevin MacLeod (incompetech.com)
    Licensed under Creative Commons: By Attribution 3.0 License
    http://creativecommons.org/licenses/by/3.0/

    “Iron Bacon” Kevin MacLeod (incompetech.com)
    Licensed under Creative Commons: By Attribution 3.0 License
    http://creativecommons.org/licenses/by/3.0/

    “What You Want” Kevin MacLeod (incompetech.com)
    Licensed under Creative Commons: By Attribution 3.0 License
    http://creativecommons.org/licenses/by/3.0/

    谢谢你美妙的音乐,Kevin!

    注意:在苹果的UIKit文档中查看Execution States for Apps,了解更多关于active state和其他的信息。

    2. Testing Audio in the Background

    为什么当应用程序进入后台时音乐停止了?好吧,缺了一个关键的部分!

    大多数后台模式都不能工作,除非你启用特定的功能,表明应用程序想要在后台运行代码。特例是关键任务完成,任何应用程序都可以执行。

    当激活时,音频后台模式告诉iOS继续播放音频,即使应用程序在后台。没错,音频后台模式实际上是自动的。你只需要激活它。

    返回Xcode,执行以下操作:

    接下来,双击Background Modes以添加此功能。展开Background Modes功能,然后勾选Audio, AirPlay, and Picture in Picture以启用background audio

    在物理设备上构建并运行应用程序。像以前一样启动音乐,然后离开应用程序。这一次音频将继续。就这么简单!

    接下来,你将使用Location updates后台模式继续接收位置更新,即使应用程序是在后台。


    Receiving Location Updates

    首先,构建并运行应用程序。选择Location tab并点击Start。什么也没有发生,因为你错过了一些重要的步骤。你现在要改变了。

    1. Enabling Location Updates

    打开LocationModel.swift。这是为LocationView提供位置数据的代码。您将对init()做一个简单的更改。替换以下两行:

      mgr.requestWhenInUseAuthorization()
      mgr.allowsBackgroundLocationUpdates = false
    

     mgr.requestAlwaysAuthorization()
     mgr.allowsBackgroundLocationUpdates = true
    

    第一行请求位置更新,即使应用程序没有在使用。第二个请求甚至在后台进行更新。

    回到Signing & Capabilities界面,勾选Location updates框,让iOS知道你的应用程序想在后台接收位置更新。

    除了勾选这个框,iOS还要求你在Info.plist中设置一个键向用户解释为什么你需要后台更新。如果不包含这一点,位置请求将会无声地失败。

    打开Info.plist。并添加Privacy — Location Always and When In Use Usage DescriptionPrivacy — Location When In Use Usage Description的键。然后输入The app will show your location on a map作为两个键的value

    现在,构建并运行,切换到Location tab,点击Start

    当它第一次加载时,你会看到你写进你的位置隐私原因的消息。

    点击Allow while using app,在外面或大楼周围散步——尽量不要因为抓口袋妖怪而分心。

    位置更新应该开始出现。如果没有,将应用再次发送到后台,以触发Always提示进行位置跟踪。你也可以使用Settings应用程序,在Privacy ▸ Location Services ▸ Sleepless设置中启用Sleepless应用程序始终跟踪。

    如果你将应用程序发送到后台,你仍然会看到控制台中发生的位置更新。

    一段时间后,你应该会看到如下内容:

    2. Testing Location Mode in the Background

    如果你退出应用程序,你应该看到应用程序更新了控制台日志中的位置。再次打开它,可以看到地图上所有的大头针,显示你在步行过程中去过的地方。

    如果你正在使用模拟器,你也可以使用它来模拟移动!点击Features ▸ Location菜单:

    非常简单,对吧?打开第三个选项卡和第三个后台模式!


    Completing Critical Tasks Upon Moving to the Background

    下一个后台模式的正式名称是Extending Your App’s Background Execution Time,任务完成说起来容易一点!

    从技术上讲,这根本不是后台模式。你不需要在Capabilities中声明你的应用程序使用它。它是一个API,当你的应用程序在后台时,允许你在有限的时间内运行任意代码,给你更多的时间来完成关键任务,如保存数据。

    1. When to Use Task Completion

    Completion后台模式的一个有效用例是完成一些关键任务,例如保存用户的输入或发布一个事务。有很多可能性。

    由于代码是任意的,你可以使用这个API做几乎任何事情:执行冗长的计算,对图像应用过滤器,渲染一个复杂的3D网格 —— 任何!你的想象力是极限,只要你记住你只有一些时间,而不是无限的时间。稍后,您将设置一个在后台运行的冗长计算,因此您可以看到这个API是如何工作的。

    iOS决定了你的应用程序移到后台后的时间。你被授予的时间没有保证,但你总是可以检查UIApplication.shared.backgroundTimeRemaining。这会告诉你还剩下多少时间。

    一般的,基于观察的共识是你大约有30秒。同样,没有保证,API文档甚至没有给出一个估计——所以不要依赖这个数字。你可能有5分钟或5秒钟的时间,所以你的应用程序需要为中断做好准备。当你的时间快到的时候,iOS会给你回调信号。

    2. Setting Up a Completion Task

    这里有一个每个计算机科学专业的学生都应该熟悉的常见任务:计算 Fibonacci Sequence中的数字。这里的扭转是,你将应用程序移动到后台后计算这些数字。

    打开CompleteTaskModel.swift,看看已经有什么了。按照目前的情况,该视图将按顺序计算斐波那契数列并显示结果。

    如果你现在挂起一个实际设备上的应用程序,计算将停止,并在应用程序再次激活时恢复到原来的位置。你的任务是创建一个后台任务,这样计算就可以一直运行,直到iOS说“时间到!”

    你首先需要添加以下内容到CompleteTaskModel:

    var backgroundTask: UIBackgroundTaskIdentifier = .invalid
    

    此属性标识要在后台运行的任务请求。

    接下来,在resetcalculation()之前向CompleteTaskModel添加以下方法:

    func registerBackgroundTask() {
      backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
        print("iOS has signaled time has expired")
        self?.endBackgroundTaskIfActive()
      }
    }
    

    registerBackgroundTask()告诉iOS,当应用移动到后台时,你需要更多的时间来完成你正在做的事情。返回的值是这个任务的标识符,这样你就可以告诉iOS你什么时候完成了。在这个调用之后,如果你的应用程序移动到后台,它仍然会得到CPU时间,直到你调用endBackgroundTask(_:)

    好吧,至少有一些CPU时间。

    3. Ending the Completion Task

    如果你在后台一段时间后没有调用endBackgroundTask(_:), iOS将调用当你调用beginBackgroundTask(expirationHandler:)时定义的闭包。这使您有机会停止执行代码。

    因此,调用endBackgroundTask(_:)来告诉系统您已经完成是一个好主意。如果你不调用它并在这个块运行后继续执行代码,iOS将终止你的应用程序!

    将这个方法添加到registerBackgroundTask()下面:

    func endBackgroundTaskIfActive() {
      let isBackgroundTaskActive = backgroundTask != .invalid
      if isBackgroundTaskActive {
        print("Background task ended.")
        UIApplication.shared.endBackgroundTask(backgroundTask)
        backgroundTask = .invalid
      }
    }
    

    这将结束后台任务,如果它是主动注册的,并将其ID重置为invalid

    4. Registering and Ending Background Tasks

    现在,对于重要的部分:更新onChangeOfScenePhase(_:)来注册和结束后台任务,这取决于应用程序是移动到后台还是活动状态。

    用以下语句替换这两个case语句:

    case .background:
      let isTimerRunning = updateTimer != nil
      let isTaskUnregistered = backgroundTask == .invalid
    
      if isTimerRunning && isTaskUnregistered {
        registerBackgroundTask()
      }
    case .active:
      endBackgroundTaskIfActive()
    

    当切换到后台background状态时,这将在任务正在运行但未注册时注册它。当切换到活动active状态时,它将结束后台任务。

    beginPauseTask()中,在updateTimer = nil之后添加这一行:

    endBackgroundTaskIfActive()
    

    现在,当用户停止计算时,你调用endBackgroundTask(_:)来告诉iOS你不需要任何额外的CPU时间。

    注意:每次调用beginBackgroundTask(expirationHandler:)时调用endBackgroundTask(_:)是很重要的。如果你调用beginBackgroundTask(expirationHandler:)两次,并且只对其中一个任务调用endBackgroundTask(_:),你仍然会得到CPU时间,直到你使用第二个后台任务的标识符第二次调用endBackgroundTask(_:)

    构建并运行,然后切换到第三个选项卡。

    点击Play并观看应用程序计算这些甜蜜的斐波那契值。将应用发送到后台,但要观察Xcode控制台的输出。当剩下的时间减少时,你的应用程序应该继续更新数字。

    在大多数情况下,这个时间从30秒开始,一直到5秒。如果你在达到5秒时等待时间过期——或者你看到的任何值——iOS就会调用过期block

    你的应用程序应该很快停止产生输出。然后,如果你回到应用程序,计时器应该会再次启动,斐波那契疯狂将继续。

    在前台和后台之间切换,看看如何通过每次切换获得额外的时间块。

    下面是本教程的最后一个主题:background fetch


    Background Fetch

    Background fetch是在iOS 7中引入的。它可以让你的应用程序显示最新的同时最小化对电池寿命的影响。从iOS 13开始,苹果引入了一个新的后台任务调度程序API,提供了显著的改进。

    例如,假设你正在应用程序中实现一个新闻feed。在后台获取之前,你将在应用程序每次启动时刷新feed

    遗憾的是,当刷新时,用户会看到几秒钟的旧标题。你知道,有些人会试图挖掘一个故事,结果却发现它消失了,取而代之的是一个不相关的故事。看起来不太好。

    如果当用户打开你的应用时,最新的标题就会神奇地出现在那里,不是更好吗?这是后台获取给你的能力。

    当启用时,系统利用使用模式来确定何时触发后台获取。例如,如果用户在上午9点打开你的新闻应用,background fetch可能会在上午9点之前发生。系统决定发出background fetch的最佳时间,由于这个原因,它不适合进行关键更新。

    1. Understanding Background Fetch

    Background fetchBGTaskScheduler控制,这是一个复杂的系统,用于平衡所有影响用户体验的因素,如性能、使用模式、电池寿命等。

    Background fetch通常涉及从外部来源(如网络服务)获取信息。在本后台模式教程中,您将获取当前时间,而不使用网络。

    为了实现background fetch,你需要完成这些任务——但现在不要做:

    • 在你的应用程序的CapabilitiesBackground Modes中勾选Background fetch
    • Info.plist添加标识符。请为您的刷新任务。
    • 在你的应用程序代理中调用BGTaskScheduler.register(forTaskWithIdentifier:using:launchHandler:)来处理后台获取。
    • 创建一个BGAppRefreshTaskRequest,为何时执行指定一个earliestBeginDate
    • 使用BGTaskScheduler.submit(_:)提交请求。

    与后台完成任务类似,您有一个很短但不确定的时间框架来执行background fetch。共识的数字是最大30秒,但计划更少。如果您需要下载大型资源作为获取的一部分,请使用URLSession的后台传输服务。

    2. Implementing Background Fetch

    是时候开始了。首先,简单的部分:在Signing & Capabilities下选中Background fetch能力。

    接下来,打开Info.plist。并点击+来添加一个新的标识符。

    向下滚动并选择Permitted background task scheduler identifiers。展开项目,然后点击新标识符旁边的+以添加条目。

    输入com.mycompany.myapp.task.refresh获取标识符的值。

    注意:在您的实际项目中,您将反向使用您公司的URL作为标识符的根,添加您的应用程序名称和描述性元素,如task.refresh。可以定义多种类型的刷新任务,每种任务都有自己的标识符。

    接下来,你需要一个AppDelegate类,因为iOS希望在application(_:didFinishLaunchingWithOptions:)任务之间注册你的获取task

    App文件夹中,添加一个新的Swift文件AppDelegate.swift。然后将现有代码替换为:

    import UIKit
    import BackgroundTasks
    
    class AppDelegate: UIResponder, UIApplicationDelegate {
      static var dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        formatter.timeStyle = .long
        return formatter
      }()
    
      var window: UIWindow?
    
      func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
      ) -> Bool {
        return true
      }
    }
    

    这段代码为刷新时间戳定义了一个日期格式化器。它还包括一个application(_:didFinishLaunchingWithOptions:)空方法,这是您将注册后台获取任务的地方。

    现在向AppDelegate添加以下函数:

    func refresh() {
      // to simulate a refresh, just update the last refresh date
      // to current date/time
      let formattedDate = Self.dateFormatter.string(from: Date())
      UserDefaults.standard.set(
        formattedDate,
        forKey: UserDefaultsKeys.lastRefreshDateKey)
      print("refresh occurred")
    }
    

    这个函数模拟了一次刷新。

    在您创建的应用程序中,您可能会从网络获取数据。对于本教程,您将把一个格式化的时间戳保存到UserDefaults中,以显示刷新执行的时间。

    仍然在AppDelegate.swift中,向AppDelegate添加以下函数:

    func scheduleAppRefresh() {
      let request = BGAppRefreshTaskRequest(
        identifier: AppConstants.backgroundTaskIdentifier)
      request.earliestBeginDate = Date(timeIntervalSinceNow: 1 * 60)
      do {
        try BGTaskScheduler.shared.submit(request)
        print("background refresh scheduled")
      } catch {
        print("Couldn't schedule app refresh \(error.localizedDescription)")
      }
    }
    

    这里你创建了一个BGAppRefreshTaskRequest,然后从当前时间开始分配一个earliestBeginDate。然后使用BGTaskScheduler.submit(_:)提交请求。

    现在,将application(_:didFinishLaunchingWithOptions:)替换为:

    BGTaskScheduler.shared.register(
      forTaskWithIdentifier: AppConstants.backgroundTaskIdentifier,
      using: nil) { task in
        self.refresh() // 1
        task.setTaskCompleted(success: true) // 2
        self.scheduleAppRefresh() // 3
    }
    
    scheduleAppRefresh()
    return true
    

    当iOS完成启动应用程序时,这段代码向任务调度器注册任务并调度第一次刷新。任务本身,当执行时,将:

    现在需要将AppDelegate连接到AppMain。打开AppMain.swift。在body之前加上这一行:

    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    

    这就是iOS调用AppDelegate所需要的一切。

    在物理设备上构建并运行应用程序。检查Xcode控制台的消息,以确认后台刷新是预定调度的。

    3. Testing Background Fetch

    测试background fetch的一种方法是坐等系统决定执行它。但你可能要坐很长时间等待这一切发生。

    iOS无法保证何时执行刷新。该系统使用多种因素来决定何时执行,如应用程序使用模式、电池充电等。幸运的是,Xcode提供了一种使用调试器命令触发后台获取的方法。

    打开RefreshView.swift并在print("moved to background")处设置断点。

    然后将应用发送到后台,Xcode应该在新的断点处中断。在lldb提示符下,输入以下命令(或者,因为它非常复杂,复制和粘贴!)

    e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.mycompany.myapp.task.refresh"]
    

    这将指示调试器立即执行后台刷新。

    恢复应用程序的执行。控制台应该显示刷新已发生,然后调度计划的后台刷新。每次刷新都为未来安排另一次刷新。您还可能看到来自后台任务调度器的调试消息,指示其活动。

    接下来,重新打开应用程序。 Refresh tab将显示刷新发生的时间和日期。

    如果你把这款应用留在你的设备上,在接下来的几天里查看,你会不时看到时间戳的更新。iOS根据最佳刷新时间的计算调用刷新。

    对于需要很多分钟才能完成的长时间运行的后台任务,了解更多关于 background processing tasks的信息。后台处理任务Background processing类似于后台获取(background fetch),但用于更严格的任务,如数据处理和维护。

    还有两个与后台模式(background mode)相关的很棒的WWDC演讲:

    最后,您可以在Configuring Background Execution Modes 中了解所有的后台执行模式。

    后记

    本篇主要讲述了Background Modes几种Mode使用示例,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

          本文标题:Background Modes详细解析(一) —— 几种Mod

          本文链接:https://www.haomeiwen.com/subject/sbkczrtx.html