iOS swift 后台运行应用尝试失败

最近需要制作一个能够后台长期运行的移动应用。该应用需要调用摄像头周期性捕获数据,然后对数据处理过后,实时反馈结果。支持android和ios平台。

主要有下面几点:

1、摄像头实时捕获

2、能够适配多款不同机型的处理算法

3、能在后台以服务形式常驻运行,不影响用户使用其他应用

4、根据数据处理结果,给用户提醒,通常用户这时在使用其他应用

在安卓平台上,已经通过多款不同型号的手机,验证了方案与算法,包括用户易用性方面也进行了一些界面设计与调整。

那么接下来,理论上,ios采用一样的方案,就能够实现。

实际摸下来的结果,1和2很容易实现,3和4在ios上,apple有很大的限制,要遵守隐私和用户体验协议。

对于第四点,android采用系统级弹窗,可以在其他应用上方弹出悬浮窗,给出提醒。

但是在iOS平台上,由于Apple的限制,一个应用无法直接在其他应用上方弹出悬浮窗(也被称为悬浮窗或者覆盖层)。这种操作属于对用户体验的侵犯,Apple在设计iOS时明确规定应用之间不能有重叠的UI元素。

这里可以采用一个替代的方式,就是用声音提醒,录制几个小的音频文件就可以。

对于第三点,后台运行应用,我做了很多尝试,都不成功。下面记录下过程。

1、网上搜索的后台方案,很多误导

我一开始主要采用百度搜索的方式,结果发现产生了很大的误导。

无论是百度还是某些AI,都给出了可行的方案,但实践下来都行不通。

筛去不完整、有错误的,主要是两种方式,一在ViewController里修改,二在AppDelegate里修改

首先都要设置Background Modes,根据不同应用场景,选择不同模式,要用到视频和音频,所以设置的是Audio, AirPlay, and Picture in Picture。设置后,info会自动刷新,不用再修改info。

1)在ViewController直接注册通知消息,来启动后台接口。有一点效果的方法,是采用beginBackgroundTask和endBackgroundTask,可以启动有限实践的后台任务。达不到需求。

2)不少文章给出在AppDelegate里修改,实测下来发现applicationDidEnterBackground接口进不去,没有打印。后来发现原因是方案太旧了,早就过时了。

performFetchWithCompletionHandler 是旧的应用程序,ios13不再调用,现在都已经ios18了

在iOS 10 后 applicationDidEnterBackground和applicationWillEnterForeground已经被SceneDelegate给接管了。

所以关于前台后台等的处理操作,不应该写在AppDelegate里。

理论上把AppDelegate的接口,转到SceneDelegate,应该也能打通后台流程。后来找了下官方的示例,给出的也是AppDelegate相关的。

3)如果要实现长时间的后台任务。

有个说法是启动一个无线时长的无声音频,可以让程序持续保活,但是被打断就会失效。这个方法我没有去试验,因为要用到声音提醒,同时其他应用也会使用音频。

4)比较新的后台框架 BGAppRefreshTask和BGProcessingTask,通过后台schegual的方式,起到类似周期性刷新数据的效果,可以理解为这个模仿的限制性少一点,当然对应的Background Modes是Background Fetch。

这个框架主要参考了官方示例。见下文。

2、官方文档也很抽象

网上文章比较乱,那么找官方的支持总应该可以吧。实践下来也是很多坑。

从apple官方文档里,找到一个比较新的后台任务框架:

BGAppRefreshTask和BGProcessingTask

参考官方给的样例ColorFeed。

移除SceneDelegate

首先官方给的工程里是没有SceneDelegate的,而我使用xcode创建的工程默认有scene文件。比对了info文件的差异。我就想是不是可以移除,删除SceneDelegate文件,删除info里的权限Application Scene Manifest键值对,删除AppDelegate里UISceneSession Lifecycle下的接口。

参考下面文章:

(Objective-C) SceneDelegate使用及移除注意事项和分屏_ios 删除scenedelegate-CSDN博客

有一点注意,删除后运行可能报错无入口,需要在AppDelegate里,增加var window: UIWindow?,虽然代码里看不出哪里在使用。

裁剪方案

官方这个案例的后台任务框架部分很简单,但是还自定义了一些自定义的比较复杂的数据结构,比如Mocks、Operations、PersistentContainer,这些我的应用不需要,也用不着数据库,所以最好是剪裁一下。

为此我又找了两篇网上的文章作参照:

BGTaskScheduler 后台数据刷新 - 简书

写一个后台监听程序,iOS【此路不通,该死的苹果】

参考第一篇文章,去掉无用的对象,写了一套简化实现,如下:

Swift 复制代码
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    let view = UIView()
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        setupAudioSession()

        // MARK: Registering Launch Handlers for Tasks
        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.background.faceDistance.refresh", using: nil) { task in            // Downcast the parameter to an app refresh task as this identifier is used for a refresh request.
            self.handleAppRefresh(task: task as! BGAppRefreshTask)
        }
        return true
    }
    
    func handleAppRefresh(task: BGAppRefreshTask) {
        scheduleAppRefresh()
        
        print("任务触发")
//        setupAudioSession()
        CameraManager.shared.stopCamera()
        CameraManager.shared.setupCamera(forgroundFlag: false, view: view)
        
        DispatchQueue.global(qos: .background).async {
            
        }
        
        task.expirationHandler = {
            // 任务时间结束调用
            CameraManager.shared.stopCamera()
            task.setTaskCompleted(success: false)
        }

        CameraManager.shared.stopCamera()
        task.setTaskCompleted(success: true)
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
        scheduleAppRefresh()
    }
    
    func scheduleAppRefresh() {
        let taskRequest = BGAppRefreshTaskRequest(identifier:"com.background.faceDistance.refresh")
        taskRequest.earliestBeginDate = Date(timeIntervalSinceNow: 30.0)

        do{
            try BGTaskScheduler.shared.submit(taskRequest)
            print("任务提交成功")
        }catch{
            print("任务提交失败")
        }
    }

}

核心思想是初始注册后台任务标识forTaskWithIdentifier和接口,在应用进入后台时applicationDidEnterBackground,启动任务编排,提交一个后台任务到BGTaskScheduler里。待系统调度执行后台任务时,handleAppRefresh里先提交一个新的BGTask,然后执行后台任务。

taskRequest.earliestBeginDate设置了30s,实践ios能在什么实践触发不好说。

因为有时候需要等很久才会触发,用官方推荐的方式触发,设置断点,提交下面命令:

e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.test.BackgroundDemo"]

这样测试是能够成功的。说明代码逻辑和实现都没问题。

但是真机测试,一个晚上没有激活任何后台处理。

所以还是回到需求本身,实时摄像头捕捉要用到AVFoundation,而这套接口,目前看来在后台无法运行。

如果说类似ColorFeed那样,通过数据库或者网络来刷新应用,用BGAppRefreshTask应该是没问题的。

但是我的应用需要摄像头、实时视频,类似监控或者直播。所以从后台运行这个方向找方案不行。

受到小视频窗口启发,另一个方向,就是画中画。在下一篇博文,我再总结画中画的摸索经验。

相关推荐
小鹿撞出了脑震荡6 小时前
Effective Objective-C 2.0 读书笔记——关联对象
开发语言·ios·objective-c
小鹿撞出了脑震荡6 小时前
Effective Objective-C 2.0 读书笔记—— objc_msgSend
ios·objective-c·xcode
fareast_mzh6 小时前
Customize ringtone on your iPhone
ios·iphone
Mr.L705171 天前
Maui学习笔记- SQLite简单使用案例02添加详情页
笔记·学习·ios·sqlite·c#
taopi20241 天前
ios swift画中画技术尝试
ios·xcode·swift
Swift社区1 天前
LeetCode - #194 Swift 实现文件内容转置
vue.js·leetcode·swift
OKXLIN1 天前
IOS 自定义代理协议Delegate
macos·ios·cocoa
百度Geek说2 天前
百度APP iOS端磁盘优化实践(上)
macos·ios·cocoa
陈皮话梅糖@3 天前
iOS 集成ffmpeg
ios·ffmpeg