// // BackgroundTasks.swift // map_mapkit_ios // // Created by 诺诺诺的言 on 2025/7/14. // import BackgroundTasks import Combine @available(iOS 13.0, *) class BackgroundTaskManager { static let shared = BackgroundTaskManager() private let backgroundTaskIdentifier = "com.your.app.screenlock.monitor" func registerBackgroundTask() { BGTaskScheduler.shared.register( forTaskWithIdentifier: backgroundTaskIdentifier, using: nil ) { task in self.handleBackgroundTask(task: task as! BGProcessingTask) } } func scheduleBackgroundTask() { let request = BGProcessingTaskRequest(identifier: backgroundTaskIdentifier) request.requiresNetworkConnectivity = false request.requiresExternalPower = false request.earliestBeginDate = Date(timeIntervalSinceNow: 60) // 1分钟后 do { try BGTaskScheduler.shared.submit(request) } catch { print("无法提交后台任务: \(error.localizedDescription)") } } private func handleBackgroundTask(task: BGProcessingTask) { // 设置任务过期处理 task.expirationHandler = { task.setTaskCompleted(success: false) } // 开始监测 ScreenLockMonitor.shared.startMonitoringInBackground() // 安排下一个任务 self.scheduleBackgroundTask() task.setTaskCompleted(success: true) } } import Foundation // 定义屏幕事件类型 struct ScreenEvent: Codable { let timestamp: Date let eventType: EventType enum EventType: String, Codable { case lock = "锁屏" case unlock = "解锁" } } @available(iOS 13.0, *) class ScreenLockMonitor { static let shared = ScreenLockMonitor() private var lastState: Bool? private let stateChangeSubject = PassthroughSubject() var stateChangePublisher: AnyPublisher { stateChangeSubject.eraseToAnyPublisher() } func startMonitoringInBackground() { NotificationCenter.default.addObserver( self, selector: #selector(protectedDataWillBecomeUnavailable), name: UIApplication.protectedDataWillBecomeUnavailableNotification, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(protectedDataDidBecomeAvailable), name: UIApplication.protectedDataDidBecomeAvailableNotification, object: nil ) // 启动时检查当前状态 checkInitialState() } private func checkInitialState() { let isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable if lastState != isProtectedDataAvailable { lastState = isProtectedDataAvailable let event = ScreenEvent( timestamp: Date(), eventType: isProtectedDataAvailable ? .unlock : .lock ) persistEvent(event: event) } } @objc private func protectedDataWillBecomeUnavailable() { recordEvent(eventType: .lock) } @objc private func protectedDataDidBecomeAvailable() { recordEvent(eventType: .unlock) } private func recordEvent(eventType: ScreenEvent.EventType) { let event = ScreenEvent( timestamp: Date(), eventType: eventType ) persistEvent(event: event) stateChangeSubject.send(event) } private func persistEvent(event: ScreenEvent) { // 使用CoreData持久化存储 //CoreDataManager.shared.saveScreenEvent(event) print("eventsfsfdsd---\(event.timestamp)---\(event.eventType)") // 同时写入文件备份 FileStorageManager.shared.saveEvent(event) } } import CoreData class CoreDataManager { static let shared = CoreDataManager() private let persistentContainer: NSPersistentContainer private init() { persistentContainer = NSPersistentContainer(name: "ScreenEventsModel") persistentContainer.loadPersistentStores { description, error in if let error = error { fatalError("无法加载CoreData存储: \(error)") } } } func getEventsBetween(startDate: Date, endDate: Date) -> [ScreenEvent] { let request: NSFetchRequest = ScreenEventEntity.fetchRequest() // 设置时间范围谓词 request.predicate = NSPredicate( format: "timestamp >= %@ AND timestamp <= %@", startDate as NSDate, endDate as NSDate ) // 按时间升序排列 request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)] do { let results = try persistentContainer.viewContext.fetch(request) return results.compactMap { guard let timestamp = $0.timestamp else { return nil } return ScreenEvent( timestamp: timestamp, eventType: ScreenEvent.EventType(rawValue: $0.eventType ?? "lock") ?? .lock ) } } catch { print("查询事件失败: \(error)") return [] } } func saveScreenEvent(_ event: ScreenEvent) { let context = persistentContainer.viewContext let entity = ScreenEventEntity(context: context) entity.timestamp = event.timestamp entity.eventType = event.eventType.rawValue do { try context.save() } catch { print("保存事件失败: \(error)") } } func getAllEvents() -> [ScreenEvent] { let request: NSFetchRequest = ScreenEventEntity.fetchRequest() request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)] do { let results = try persistentContainer.viewContext.fetch(request) return results.map { ScreenEvent( timestamp: $0.timestamp ?? Date(), eventType: ScreenEvent.EventType(rawValue: $0.eventType ?? "lock") ?? .lock ) } } catch { print("获取事件失败: \(error)") return [] } } } // CoreData实体 public class ScreenEventEntity: NSManagedObject { @NSManaged public var timestamp: Date? @NSManaged public var eventType: String? } extension ScreenEventEntity { @nonobjc public class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "ScreenEventEntity") } } class FileStorageManager { static let shared = FileStorageManager() private let fileURL = FileManager.default .urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent("screenEventsBackup.json") func saveEvent(_ event: ScreenEvent) { var existingEvents = loadEvents() existingEvents.append(event) do { let data = try JSONEncoder().encode(existingEvents) try data.write(to: fileURL) } catch { print("文件存储失败: \(error)") } } func loadEvents() -> [ScreenEvent] { do { let data = try Data(contentsOf: fileURL) return try JSONDecoder().decode([ScreenEvent].self, from: data) } catch { return [] } } /// 从JSON文件中获取时间范围内的事件 func getEventsBetween(startDate: Date, endDate: Date) -> [ScreenEvent] { let allEvents = loadEvents() return allEvents.filter { $0.timestamp >= startDate && $0.timestamp <= endDate }.sorted { $0.timestamp < $1.timestamp } } } class EventQueryService { static let shared = EventQueryService() /// 获取时间范围内的事件并转换为字典列表 func getEventPairsBetween(startDate: Date, endDate: Date) -> [[String: Any]] { // 获取原始事件(已按时间排序) let events = getOrderedEventsBetween(startDate: startDate, endDate: endDate) var result = [[String: Any]]() var currentLockEvent: ScreenEvent? for event in events { switch event.eventType { case .lock: // 如果遇到新的锁屏事件而前一个未配对,则保存前一个不完整记录 if let lockEvent = currentLockEvent { result.append([ "startTime": lockEvent.timestamp, "endTime": event.timestamp // 异常情况:两个锁屏事件之间没有解锁 ]) } currentLockEvent = event case .unlock: if let lockEvent = currentLockEvent { // 正常情况:锁屏-解锁配对 result.append([ "startTime": lockEvent.timestamp, "endTime": event.timestamp ]) currentLockEvent = nil } else { // 异常情况:第一个事件是解锁事件 result.append([ "startTime": startDate, // 用查询开始时间作为默认值 "endTime": event.timestamp ]) } } } // 处理最后一个未配对的锁屏事件 if let lockEvent = currentLockEvent { result.append([ "startTime": lockEvent.timestamp, "endTime": 0 // 用查询结束时间作为默认值 ]) } return result } /// 私有方法:获取排序后的事件列表 private func getOrderedEventsBetween(startDate: Date, endDate: Date) -> [ScreenEvent] { // 从CoreData获取 // let coreDataEvents = CoreDataManager.shared.getEventsBetween( // startDate: startDate, // endDate: endDate // ) // 从文件备份获取 let fileEvents = FileStorageManager.shared.getEventsBetween( startDate: startDate, endDate: endDate ) // 合并、去重并排序 var allEvents = fileEvents;//Array(Set.init(from: fileEvents)) // 去重 allEvents.sort { $0.timestamp < $1.timestamp } // 按时间升序 return allEvents } }