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应该是没问题的。

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

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

相关推荐
幸福回头19 小时前
ms-swift 代码推理数据集
llm·swift
若水无华1 天前
fiddler 配置ios手机代理调试
ios·智能手机·fiddler
不二狗1 天前
每日算法 -【Swift 算法】Two Sum 问题:从暴力解法到最优解法的演进
开发语言·算法·swift
Aress"1 天前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy2 天前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克2 天前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨2 天前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆2 天前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂2 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T3 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa