BackgroundTasks.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. //
  2. // BackgroundTasks.swift
  3. // map_mapkit_ios
  4. //
  5. // Created by 诺诺诺的言 on 2025/7/14.
  6. //
  7. import BackgroundTasks
  8. import Combine
  9. @available(iOS 13.0, *)
  10. class BackgroundTaskManager {
  11. static let shared = BackgroundTaskManager()
  12. private let backgroundTaskIdentifier = "com.your.app.screenlock.monitor"
  13. func registerBackgroundTask() {
  14. BGTaskScheduler.shared.register(
  15. forTaskWithIdentifier: backgroundTaskIdentifier,
  16. using: nil
  17. ) { task in
  18. self.handleBackgroundTask(task: task as! BGProcessingTask)
  19. }
  20. }
  21. func scheduleBackgroundTask() {
  22. let request = BGProcessingTaskRequest(identifier: backgroundTaskIdentifier)
  23. request.requiresNetworkConnectivity = false
  24. request.requiresExternalPower = false
  25. request.earliestBeginDate = Date(timeIntervalSinceNow: 60) // 1分钟后
  26. do {
  27. try BGTaskScheduler.shared.submit(request)
  28. } catch {
  29. print("无法提交后台任务: \(error.localizedDescription)")
  30. }
  31. }
  32. private func handleBackgroundTask(task: BGProcessingTask) {
  33. // 设置任务过期处理
  34. task.expirationHandler = {
  35. task.setTaskCompleted(success: false)
  36. }
  37. // 开始监测
  38. ScreenLockMonitor.shared.startMonitoringInBackground()
  39. // 安排下一个任务
  40. self.scheduleBackgroundTask()
  41. task.setTaskCompleted(success: true)
  42. }
  43. }
  44. import Foundation
  45. // 定义屏幕事件类型
  46. struct ScreenEvent: Codable {
  47. let timestamp: Date
  48. let eventType: EventType
  49. enum EventType: String, Codable {
  50. case lock = "锁屏"
  51. case unlock = "解锁"
  52. }
  53. }
  54. @available(iOS 13.0, *)
  55. class ScreenLockMonitor {
  56. static let shared = ScreenLockMonitor()
  57. private var lastState: Bool?
  58. private let stateChangeSubject = PassthroughSubject<ScreenEvent, Never>()
  59. var stateChangePublisher: AnyPublisher<ScreenEvent, Never> {
  60. stateChangeSubject.eraseToAnyPublisher()
  61. }
  62. func startMonitoringInBackground() {
  63. NotificationCenter.default.addObserver(
  64. self,
  65. selector: #selector(protectedDataWillBecomeUnavailable),
  66. name: UIApplication.protectedDataWillBecomeUnavailableNotification,
  67. object: nil
  68. )
  69. NotificationCenter.default.addObserver(
  70. self,
  71. selector: #selector(protectedDataDidBecomeAvailable),
  72. name: UIApplication.protectedDataDidBecomeAvailableNotification,
  73. object: nil
  74. )
  75. // 启动时检查当前状态
  76. checkInitialState()
  77. }
  78. private func checkInitialState() {
  79. let isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
  80. if lastState != isProtectedDataAvailable {
  81. lastState = isProtectedDataAvailable
  82. let event = ScreenEvent(
  83. timestamp: Date(),
  84. eventType: isProtectedDataAvailable ? .unlock : .lock
  85. )
  86. persistEvent(event: event)
  87. }
  88. }
  89. @objc private func protectedDataWillBecomeUnavailable() {
  90. recordEvent(eventType: .lock)
  91. }
  92. @objc private func protectedDataDidBecomeAvailable() {
  93. recordEvent(eventType: .unlock)
  94. }
  95. private func recordEvent(eventType: ScreenEvent.EventType) {
  96. let event = ScreenEvent(
  97. timestamp: Date(),
  98. eventType: eventType
  99. )
  100. persistEvent(event: event)
  101. stateChangeSubject.send(event)
  102. }
  103. private func persistEvent(event: ScreenEvent) {
  104. // 使用CoreData持久化存储
  105. //CoreDataManager.shared.saveScreenEvent(event)
  106. print("eventsfsfdsd---\(event.timestamp)---\(event.eventType)")
  107. // 同时写入文件备份
  108. FileStorageManager.shared.saveEvent(event)
  109. }
  110. }
  111. import CoreData
  112. class CoreDataManager {
  113. static let shared = CoreDataManager()
  114. private let persistentContainer: NSPersistentContainer
  115. private init() {
  116. persistentContainer = NSPersistentContainer(name: "ScreenEventsModel")
  117. persistentContainer.loadPersistentStores { description, error in
  118. if let error = error {
  119. fatalError("无法加载CoreData存储: \(error)")
  120. }
  121. }
  122. }
  123. func getEventsBetween(startDate: Date, endDate: Date) -> [ScreenEvent] {
  124. let request: NSFetchRequest<ScreenEventEntity> = ScreenEventEntity.fetchRequest()
  125. // 设置时间范围谓词
  126. request.predicate = NSPredicate(
  127. format: "timestamp >= %@ AND timestamp <= %@",
  128. startDate as NSDate,
  129. endDate as NSDate
  130. )
  131. // 按时间升序排列
  132. request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)]
  133. do {
  134. let results = try persistentContainer.viewContext.fetch(request)
  135. return results.compactMap {
  136. guard let timestamp = $0.timestamp else { return nil }
  137. return ScreenEvent(
  138. timestamp: timestamp,
  139. eventType: ScreenEvent.EventType(rawValue: $0.eventType ?? "lock") ?? .lock
  140. )
  141. }
  142. } catch {
  143. print("查询事件失败: \(error)")
  144. return []
  145. }
  146. }
  147. func saveScreenEvent(_ event: ScreenEvent) {
  148. let context = persistentContainer.viewContext
  149. let entity = ScreenEventEntity(context: context)
  150. entity.timestamp = event.timestamp
  151. entity.eventType = event.eventType.rawValue
  152. do {
  153. try context.save()
  154. } catch {
  155. print("保存事件失败: \(error)")
  156. }
  157. }
  158. func getAllEvents() -> [ScreenEvent] {
  159. let request: NSFetchRequest<ScreenEventEntity> = ScreenEventEntity.fetchRequest()
  160. request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
  161. do {
  162. let results = try persistentContainer.viewContext.fetch(request)
  163. return results.map {
  164. ScreenEvent(
  165. timestamp: $0.timestamp ?? Date(),
  166. eventType: ScreenEvent.EventType(rawValue: $0.eventType ?? "lock") ?? .lock
  167. )
  168. }
  169. } catch {
  170. print("获取事件失败: \(error)")
  171. return []
  172. }
  173. }
  174. }
  175. // CoreData实体
  176. public class ScreenEventEntity: NSManagedObject {
  177. @NSManaged public var timestamp: Date?
  178. @NSManaged public var eventType: String?
  179. }
  180. extension ScreenEventEntity {
  181. @nonobjc public class func fetchRequest() -> NSFetchRequest<ScreenEventEntity> {
  182. return NSFetchRequest<ScreenEventEntity>(entityName: "ScreenEventEntity")
  183. }
  184. }
  185. class FileStorageManager {
  186. static let shared = FileStorageManager()
  187. private let fileURL = FileManager.default
  188. .urls(for: .documentDirectory, in: .userDomainMask)[0]
  189. .appendingPathComponent("screenEventsBackup.json")
  190. func saveEvent(_ event: ScreenEvent) {
  191. var existingEvents = loadEvents()
  192. existingEvents.append(event)
  193. do {
  194. let data = try JSONEncoder().encode(existingEvents)
  195. try data.write(to: fileURL)
  196. } catch {
  197. print("文件存储失败: \(error)")
  198. }
  199. }
  200. func loadEvents() -> [ScreenEvent] {
  201. do {
  202. let data = try Data(contentsOf: fileURL)
  203. return try JSONDecoder().decode([ScreenEvent].self, from: data)
  204. } catch {
  205. return []
  206. }
  207. }
  208. /// 从JSON文件中获取时间范围内的事件
  209. func getEventsBetween(startDate: Date, endDate: Date) -> [ScreenEvent] {
  210. let allEvents = loadEvents()
  211. return allEvents.filter {
  212. $0.timestamp >= startDate && $0.timestamp <= endDate
  213. }.sorted {
  214. $0.timestamp < $1.timestamp
  215. }
  216. }
  217. }
  218. class EventQueryService {
  219. static let shared = EventQueryService()
  220. /// 获取时间范围内的事件并转换为字典列表
  221. func getEventPairsBetween(startDate: Date, endDate: Date) -> [[String: Any]] {
  222. // 获取原始事件(已按时间排序)
  223. let events = getOrderedEventsBetween(startDate: startDate, endDate: endDate)
  224. var result = [[String: Any]]()
  225. var currentLockEvent: ScreenEvent?
  226. for event in events {
  227. switch event.eventType {
  228. case .lock:
  229. // 如果遇到新的锁屏事件而前一个未配对,则保存前一个不完整记录
  230. if let lockEvent = currentLockEvent {
  231. result.append([
  232. "startTime": lockEvent.timestamp,
  233. "endTime": event.timestamp // 异常情况:两个锁屏事件之间没有解锁
  234. ])
  235. }
  236. currentLockEvent = event
  237. case .unlock:
  238. if let lockEvent = currentLockEvent {
  239. // 正常情况:锁屏-解锁配对
  240. result.append([
  241. "startTime": lockEvent.timestamp,
  242. "endTime": event.timestamp
  243. ])
  244. currentLockEvent = nil
  245. } else {
  246. // 异常情况:第一个事件是解锁事件
  247. result.append([
  248. "startTime": startDate, // 用查询开始时间作为默认值
  249. "endTime": event.timestamp
  250. ])
  251. }
  252. }
  253. }
  254. // 处理最后一个未配对的锁屏事件
  255. if let lockEvent = currentLockEvent {
  256. result.append([
  257. "startTime": lockEvent.timestamp,
  258. "endTime": 0 // 用查询结束时间作为默认值
  259. ])
  260. }
  261. return result
  262. }
  263. /// 私有方法:获取排序后的事件列表
  264. private func getOrderedEventsBetween(startDate: Date, endDate: Date) -> [ScreenEvent] {
  265. // 从CoreData获取
  266. // let coreDataEvents = CoreDataManager.shared.getEventsBetween(
  267. // startDate: startDate,
  268. // endDate: endDate
  269. // )
  270. // 从文件备份获取
  271. let fileEvents = FileStorageManager.shared.getEventsBetween(
  272. startDate: startDate,
  273. endDate: endDate
  274. )
  275. // 合并、去重并排序
  276. var allEvents = fileEvents;//Array<Any>(Set.init(from: fileEvents)) // 去重
  277. allEvents.sort { $0.timestamp < $1.timestamp } // 按时间升序
  278. return allEvents
  279. }
  280. }