|
|
@@ -16,6 +16,14 @@ public class ClassifyPhotoPlugin: NSObject, FlutterPlugin {
|
|
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
|
|
print("iOS: Received method call: \(call.method)")
|
|
|
switch call.method {
|
|
|
+ case "getScreenshots":
|
|
|
+ self.getScreenshots(flutterResult: result)
|
|
|
+ case "getBlurryPhotos":
|
|
|
+ self.getBlurryPhotos(flutterResult: result)
|
|
|
+ case "getPeoplePhotos":
|
|
|
+ self.getPeoplePhotos(flutterResult: result)
|
|
|
+ case "getSimilarPhotos":
|
|
|
+ self.getSimilarPhotos(flutterResult: result)
|
|
|
case "getPhoto":
|
|
|
self.getPhoto(flutterResult: result)
|
|
|
case "getStorageInfo":
|
|
|
@@ -84,34 +92,19 @@ public class ClassifyPhotoPlugin: NSObject, FlutterPlugin {
|
|
|
|
|
|
let group = DispatchGroup()
|
|
|
let queue = DispatchQueue(label: "com.app.photosize", attributes: .concurrent)
|
|
|
- let semaphore = DispatchSemaphore(value: 10) // 限制并发
|
|
|
+ let semaphore = DispatchSemaphore(value: 5) // 限制并发
|
|
|
|
|
|
allPhotos.enumerateObjects { (asset, index, stop) in
|
|
|
group.enter()
|
|
|
semaphore.wait()
|
|
|
|
|
|
- let resources = PHAssetResource.assetResources(for: asset)
|
|
|
- if let resource = resources.first {
|
|
|
- queue.async {
|
|
|
- let options = PHAssetResourceRequestOptions()
|
|
|
- options.isNetworkAccessAllowed = true
|
|
|
-
|
|
|
- PHAssetResourceManager.default().requestData(
|
|
|
- for: resource,
|
|
|
- options: options,
|
|
|
- dataReceivedHandler: { data in
|
|
|
- photoSize += Int64(data.count)
|
|
|
- },
|
|
|
- completionHandler: { error in
|
|
|
- if let error = error {
|
|
|
- print("Error getting photo size: \(error)")
|
|
|
- }
|
|
|
- semaphore.signal()
|
|
|
- group.leave()
|
|
|
- }
|
|
|
- )
|
|
|
+ queue.async {
|
|
|
+ let resources = PHAssetResource.assetResources(for: asset)
|
|
|
+ if let resource = resources.first {
|
|
|
+ if let unsignedInt64 = resource.value(forKey: "fileSize") as? CLong {
|
|
|
+ photoSize += Int64(unsignedInt64)
|
|
|
+ }
|
|
|
}
|
|
|
- } else {
|
|
|
semaphore.signal()
|
|
|
group.leave()
|
|
|
}
|
|
|
@@ -124,83 +117,526 @@ public class ClassifyPhotoPlugin: NSObject, FlutterPlugin {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private func getPhoto(flutterResult: @escaping FlutterResult) {
|
|
|
+ // 获取截图
|
|
|
+ private func getScreenshots(flutterResult: @escaping FlutterResult) {
|
|
|
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
|
+ guard let self = self else { return }
|
|
|
+
|
|
|
+ let fetchOptions = PHFetchOptions()
|
|
|
+ let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
|
|
|
+
|
|
|
+ // 只处理截图
|
|
|
+ self.photoClassifier.fetchScreenshots(from: allPhotos) { screenshots in
|
|
|
+ // 清理内存
|
|
|
+ self.cleanupMemory()
|
|
|
+
|
|
|
+ self.photoClassifier.calculateAssetsSize(screenshots) { sizeInfo in
|
|
|
+ self.processPhotoGroup(
|
|
|
+ assets: screenshots,
|
|
|
+ groupName: "screenshots",
|
|
|
+ sizeInfo: sizeInfo
|
|
|
+ ) { groupData in
|
|
|
+ // 再次清理内存
|
|
|
+ self.cleanupMemory()
|
|
|
+
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ if !groupData.isEmpty {
|
|
|
+ flutterResult([["group": groupData, "type": "screenshots"]])
|
|
|
+ } else {
|
|
|
+ flutterResult([])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ // 获取模糊照片
|
|
|
+ private func getBlurryPhotos(flutterResult: @escaping FlutterResult) {
|
|
|
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
|
+ guard let self = self else { return }
|
|
|
+
|
|
|
+ let fetchOptions = PHFetchOptions()
|
|
|
+ let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
|
|
|
+
|
|
|
+ // 只处理模糊照片
|
|
|
+ self.photoClassifier.detectBlurryPhotos(from: allPhotos) { blurryPhotos in
|
|
|
+ // 清理内存
|
|
|
+ self.cleanupMemory()
|
|
|
+
|
|
|
+ self.photoClassifier.calculateAssetsSize(blurryPhotos) { sizeInfo in
|
|
|
+ self.processPhotoGroup(
|
|
|
+ assets: blurryPhotos,
|
|
|
+ groupName: "blurry",
|
|
|
+ sizeInfo: sizeInfo
|
|
|
+ ) { groupData in
|
|
|
+ // 再次清理内存
|
|
|
+ self.cleanupMemory()
|
|
|
+
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ if !groupData.isEmpty {
|
|
|
+ flutterResult([["group": groupData, "type": "blurry"]])
|
|
|
+ } else {
|
|
|
+ flutterResult([])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func getPeoplePhotos(flutterResult: @escaping FlutterResult) {
|
|
|
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
|
+ guard let self = self else { return }
|
|
|
+
|
|
|
+ let fetchOptions = PHFetchOptions()
|
|
|
+ // 限制处理的照片数量,提高性能
|
|
|
+ fetchOptions.fetchLimit = 1000
|
|
|
+ let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
|
|
|
+
|
|
|
+ // 显示进度
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ print("开始处理人物照片,总数: \(allPhotos.count)")
|
|
|
+ }
|
|
|
+
|
|
|
+ // 只处理人物照片
|
|
|
+ self.photoClassifier.classifyByPeople(assets: allPhotos) { peopleGroups in
|
|
|
+ // 清理内存
|
|
|
+ self.cleanupMemory()
|
|
|
+
|
|
|
+ let peopleAssets = Array(peopleGroups.values.flatMap { $0 })
|
|
|
+
|
|
|
+ // 显示找到的人脸照片数量
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ print("找到包含人脸的照片: \(peopleAssets.count)")
|
|
|
+ }
|
|
|
+
|
|
|
+ self.photoClassifier.calculateAssetsSize(peopleAssets) { sizeInfo in
|
|
|
+
|
|
|
+ let resultData = Atomic<[[String: Any]]>([])
|
|
|
+
|
|
|
+ // 分批处理人物组,避免一次性处理太多数据
|
|
|
+ let processingQueue = DispatchQueue(label: "com.yourapp.peopleProcessing")
|
|
|
+ let group = DispatchGroup()
|
|
|
+
|
|
|
+ // 处理每个人物组
|
|
|
+ for (personName, personPhotos) in peopleGroups {
|
|
|
+ if personPhotos.isEmpty { continue }
|
|
|
+
|
|
|
+ // 限制每组处理的照片数量
|
|
|
+ let limitedPhotos = Array(personPhotos.prefix(500))
|
|
|
+
|
|
|
+ group.enter()
|
|
|
+ processingQueue.async {
|
|
|
+ autoreleasepool {
|
|
|
+ self.processPhotoGroup(
|
|
|
+ assets: limitedPhotos,
|
|
|
+ groupName: personName,
|
|
|
+ sizeInfo: sizeInfo
|
|
|
+ ) { groupData in
|
|
|
+ if !groupData.isEmpty {
|
|
|
+ resultData.mutate { $0.append([
|
|
|
+ "group": groupData,
|
|
|
+ "type": "people",
|
|
|
+ "name": personName
|
|
|
+ ]) }
|
|
|
+ }
|
|
|
+ group.leave()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ group.notify(queue: .main) {
|
|
|
+ // 最终清理内存
|
|
|
+ self.cleanupMemory()
|
|
|
+ flutterResult(resultData.value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取人物照片
|
|
|
+// private func getPeoplePhotos(flutterResult: @escaping FlutterResult) {
|
|
|
+// DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
|
+// guard let self = self else { return }
|
|
|
+//
|
|
|
+// let fetchOptions = PHFetchOptions()
|
|
|
+// let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
|
|
|
+//
|
|
|
+// // 只处理人物照片
|
|
|
+// self.photoClassifier.classifyByPeople(assets: allPhotos) { peopleGroups in
|
|
|
+// // 清理内存
|
|
|
+// self.cleanupMemory()
|
|
|
+//
|
|
|
+// let peopleAssets = Array(peopleGroups.values.flatMap { $0 })
|
|
|
+// self.photoClassifier.calculateAssetsSize(peopleAssets) { sizeInfo in
|
|
|
+//
|
|
|
+// let resultData = Atomic<[[String: Any]]>([])
|
|
|
+// let processingQueue = DispatchQueue(label: "com.yourapp.peopleProcessing")
|
|
|
+// let group = DispatchGroup()
|
|
|
+//
|
|
|
+// // 处理每个人物组
|
|
|
+// for (personName, personPhotos) in peopleGroups {
|
|
|
+// if personPhotos.isEmpty { continue }
|
|
|
+//
|
|
|
+// group.enter()
|
|
|
+// processingQueue.async {
|
|
|
+// autoreleasepool {
|
|
|
+// self.processPhotoGroup(
|
|
|
+// assets: personPhotos,
|
|
|
+// groupName: personName,
|
|
|
+// sizeInfo: sizeInfo
|
|
|
+// ) { groupData in
|
|
|
+// if !groupData.isEmpty {
|
|
|
+// resultData.mutate { $0.append([
|
|
|
+// "group": groupData,
|
|
|
+// "type": "people",
|
|
|
+// "name": personName
|
|
|
+// ]) }
|
|
|
+// }
|
|
|
+// group.leave()
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// group.notify(queue: .main) {
|
|
|
+// // 最终清理内存
|
|
|
+// self.cleanupMemory()
|
|
|
+// flutterResult(resultData.value)
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
+ // 添加内存清理方法
|
|
|
+ private func cleanupMemory() {
|
|
|
+ // 强制清理内存
|
|
|
+ autoreleasepool {
|
|
|
+ // 触发内存警告,促使系统回收内存
|
|
|
+ UIApplication.shared.performSelector(onMainThread: #selector(UIApplication.beginIgnoringInteractionEvents), with: nil, waitUntilDone: true)
|
|
|
+ UIApplication.shared.performSelector(onMainThread: #selector(UIApplication.endIgnoringInteractionEvents), with: nil, waitUntilDone: true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取相似照片
|
|
|
+ private func getSimilarPhotos(flutterResult: @escaping FlutterResult) {
|
|
|
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
|
+ guard let self = self else { return }
|
|
|
+
|
|
|
+ let fetchOptions = PHFetchOptions()
|
|
|
+ let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
|
|
|
+
|
|
|
+ // 只处理相似照片
|
|
|
+ self.photoClassifier.detectSimilarPhotos(
|
|
|
+ assets: allPhotos,
|
|
|
+ progressHandler: { (stage, progress) in
|
|
|
+ print("Similar Photos Progress: \(stage) - \(progress)")
|
|
|
+ },
|
|
|
+ completion: { similarGroups in
|
|
|
+ // 清理内存
|
|
|
+ self.cleanupMemory()
|
|
|
+
|
|
|
+ // 限制处理的组数,避免内存过载
|
|
|
+ let maxGroupsToProcess = min(50, similarGroups.count)
|
|
|
+ let limitedGroups = Array(similarGroups.prefix(maxGroupsToProcess))
|
|
|
+
|
|
|
+ let similarAssets = Array(limitedGroups.flatMap { $0 })
|
|
|
+ self.photoClassifier.calculateAssetsSize(similarAssets) { sizeInfo in
|
|
|
+
|
|
|
+ let resultData = Atomic<[[String: Any]]>([])
|
|
|
+
|
|
|
+ // 分批处理照片组,每批处理少量组
|
|
|
+ let batchSize = 5
|
|
|
+ let totalBatches = Int(ceil(Double(limitedGroups.count) / Double(batchSize)))
|
|
|
+
|
|
|
+ self.processSimilarPhotoGroupsInBatches(
|
|
|
+ groups: limitedGroups,
|
|
|
+ batchIndex: 0,
|
|
|
+ totalBatches: totalBatches,
|
|
|
+ batchSize: batchSize,
|
|
|
+ sizeInfo: sizeInfo,
|
|
|
+ resultData: resultData
|
|
|
+ ) {
|
|
|
+ // 所有批次处理完成后的回调
|
|
|
+ // 最终清理内存
|
|
|
+ self.cleanupMemory()
|
|
|
+ flutterResult(resultData.value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加分批处理照片组的方法
|
|
|
+ private func processSimilarPhotoGroupsInBatches(
|
|
|
+ groups: [[PHAsset]],
|
|
|
+ batchIndex: Int,
|
|
|
+ totalBatches: Int,
|
|
|
+ batchSize: Int,
|
|
|
+ sizeInfo: ClassifyPhoto.PhotoSizeInfo,
|
|
|
+ resultData: Atomic<[[String: Any]]>,
|
|
|
+ completion: @escaping () -> Void
|
|
|
+ ) {
|
|
|
+ // 检查是否处理完所有批次
|
|
|
+ if batchIndex >= totalBatches {
|
|
|
+ completion()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算当前批次的范围
|
|
|
+ let startIndex = batchIndex * batchSize
|
|
|
+ let endIndex = min(startIndex + batchSize, groups.count)
|
|
|
+ let currentBatchGroups = groups[startIndex..<endIndex]
|
|
|
+
|
|
|
+ let processingQueue = DispatchQueue(label: "com.yourapp.similarProcessing.batch\(batchIndex)")
|
|
|
+ let group = DispatchGroup()
|
|
|
+
|
|
|
+ // 处理当前批次的照片组
|
|
|
+ for (index, photoGroup) in currentBatchGroups.enumerated() {
|
|
|
+ if photoGroup.isEmpty { continue }
|
|
|
+
|
|
|
+ group.enter()
|
|
|
+ processingQueue.async {
|
|
|
+ autoreleasepool {
|
|
|
+ self.processPhotoGroup(
|
|
|
+ assets: photoGroup,
|
|
|
+ groupName: "similar_\(startIndex + index)",
|
|
|
+ sizeInfo: sizeInfo
|
|
|
+ ) { groupData in
|
|
|
+ if !groupData.isEmpty {
|
|
|
+ resultData.mutate { $0.append([
|
|
|
+ "group": groupData,
|
|
|
+ "type": "similar"
|
|
|
+ ]) }
|
|
|
+ }
|
|
|
+ group.leave()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 当前批次处理完成后
|
|
|
+ group.notify(queue: .global()) {
|
|
|
+ // 清理内存
|
|
|
+ self.cleanupMemory()
|
|
|
+
|
|
|
+ // 延迟一小段时间再处理下一批,给系统一些恢复时间
|
|
|
+ DispatchQueue.global().asyncAfter(deadline: .now() + 0.3) {
|
|
|
+ // 递归处理下一批
|
|
|
+ self.processSimilarPhotoGroupsInBatches(
|
|
|
+ groups: groups,
|
|
|
+ batchIndex: batchIndex + 1,
|
|
|
+ totalBatches: totalBatches,
|
|
|
+ batchSize: batchSize,
|
|
|
+ sizeInfo: sizeInfo,
|
|
|
+ resultData: resultData,
|
|
|
+ completion: completion
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func getPhoto(flutterResult: @escaping FlutterResult) {
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
|
- guard let self = self else { return }
|
|
|
+ guard let self = self else { return }
|
|
|
|
|
|
- let fetchOptions = PHFetchOptions()
|
|
|
- let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
|
|
|
+ let fetchOptions = PHFetchOptions()
|
|
|
+ let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
|
|
|
|
|
|
- photoClassifier.classifyPhotos(
|
|
|
- assets: allPhotos,
|
|
|
- progressHandler: { (stage, progress) in
|
|
|
- print("Progress: \(stage) - \(progress)")
|
|
|
- },
|
|
|
- completion: { result in
|
|
|
- var resultData: [[String: Any]] = []
|
|
|
- let mainGroup = DispatchGroup()
|
|
|
-
|
|
|
- // 处理截图
|
|
|
- mainGroup.enter()
|
|
|
- self.processPhotoGroup(assets: result.screenshots, groupName: "screenshots", sizeInfo: result.screenshotsSize) { groupData in
|
|
|
- if !groupData.isEmpty {
|
|
|
- resultData.append(["group": groupData, "type": "screenshots"])
|
|
|
- }
|
|
|
- mainGroup.leave()
|
|
|
- }
|
|
|
-
|
|
|
- // 处理相似照片组
|
|
|
- for photoGroup in result.similarPhotos {
|
|
|
- mainGroup.enter()
|
|
|
- self.processPhotoGroup(assets: photoGroup, groupName: "similar", sizeInfo: result.similarPhotosSize) { groupData in
|
|
|
- if !groupData.isEmpty {
|
|
|
- resultData.append(["group": groupData, "type": "similar"])
|
|
|
- }
|
|
|
- mainGroup.leave()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 处理地点分组
|
|
|
- for (location, assets) in result.locations {
|
|
|
- mainGroup.enter()
|
|
|
- self.processPhotoGroup(assets: assets, groupName: location, sizeInfo: result.locationsSize) { groupData in
|
|
|
- if !groupData.isEmpty {
|
|
|
- resultData.append(["group": groupData, "type": "location", "name": location])
|
|
|
- }
|
|
|
- mainGroup.leave()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 处理人物分组
|
|
|
- for (person, assets) in result.people {
|
|
|
- mainGroup.enter()
|
|
|
- self.processPhotoGroup(assets: assets, groupName: person, sizeInfo: result.peopleSize) { groupData in
|
|
|
- if !groupData.isEmpty {
|
|
|
- resultData.append(["group": groupData, "type": "people"])
|
|
|
- }
|
|
|
- mainGroup.leave()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 处理模糊照片
|
|
|
- mainGroup.enter()
|
|
|
- self.processPhotoGroup(assets: result.blurryPhotos, groupName: "blurry", sizeInfo: result.blurryPhotosSize) { groupData in
|
|
|
- if !groupData.isEmpty {
|
|
|
- resultData.append(["group": groupData, "type": "blurry"])
|
|
|
- }
|
|
|
- mainGroup.leave()
|
|
|
- }
|
|
|
-
|
|
|
- mainGroup.notify(queue: .main) {
|
|
|
- print("Final result count: \(resultData.count)")
|
|
|
- flutterResult(resultData)
|
|
|
- }
|
|
|
- }
|
|
|
- )
|
|
|
- }
|
|
|
+
|
|
|
+ self.photoClassifier.classifyPhotos(
|
|
|
+ assets: allPhotos,
|
|
|
+ progressHandler: { (stage, progress) in
|
|
|
+ print("Progress: \(stage) - \(progress)")
|
|
|
+ },
|
|
|
+ completion: { result in
|
|
|
+ // 使用串行队列处理结果,避免同时处理多个组
|
|
|
+ let processingQueue = DispatchQueue(label: "com.yourapp.photoProcessing")
|
|
|
+ let resultData = Atomic<[[String: Any]]>([])
|
|
|
+
|
|
|
+ // 创建一个函数来依次处理每个组
|
|
|
+ func processGroups(index: Int, groups: [(assets: [PHAsset], name: String, type: String, sizeInfo: ClassifyPhoto.PhotoSizeInfo)]) {
|
|
|
+ // 检查是否处理完所有组
|
|
|
+ if index >= groups.count {
|
|
|
+ // 所有组处理完毕,返回结果
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ print("Final result count: \(resultData.value.count)")
|
|
|
+ flutterResult(resultData.value)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let currentGroup = groups[index]
|
|
|
+
|
|
|
+ // 处理当前组
|
|
|
+ self.processPhotoGroup(
|
|
|
+ assets: currentGroup.assets,
|
|
|
+ groupName: currentGroup.name,
|
|
|
+ sizeInfo: currentGroup.sizeInfo
|
|
|
+ ) { groupData in
|
|
|
+ // 使用自动释放池减少内存占用
|
|
|
+ autoreleasepool {
|
|
|
+ if !groupData.isEmpty {
|
|
|
+ resultData.mutate { $0.append([
|
|
|
+ "group": groupData,
|
|
|
+ "type": currentGroup.type,
|
|
|
+ currentGroup.type == "location" ? "name" : "" : currentGroup.name
|
|
|
+ ].filter { !($0.value is String && $0.value as! String == "") }) }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动触发内存清理
|
|
|
+ self.cleanupMemory()
|
|
|
+
|
|
|
+ // 延迟一小段时间,让系统有机会回收内存
|
|
|
+ DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
|
|
|
+ // 处理下一个组
|
|
|
+ processGroups(index: index + 1, groups: groups)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 准备所有需要处理的组
|
|
|
+ var allGroups: [(assets: [PHAsset], name: String, type: String, sizeInfo: ClassifyPhoto.PhotoSizeInfo)] = []
|
|
|
+
|
|
|
+ // 添加截图组
|
|
|
+ if !result.screenshots.isEmpty {
|
|
|
+ allGroups.append((result.screenshots, "screenshots", "screenshots", result.screenshotsSize))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加相似照片组
|
|
|
+ for photoGroup in result.similarPhotos {
|
|
|
+ if !photoGroup.isEmpty {
|
|
|
+ allGroups.append((photoGroup, "similar", "similar", result.similarPhotosSize))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加模糊照片组
|
|
|
+ if !result.blurryPhotos.isEmpty {
|
|
|
+ allGroups.append((result.blurryPhotos, "blurry", "blurry", result.blurryPhotosSize))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加人物照片组
|
|
|
+ for (personName, personPhotos) in result.people {
|
|
|
+ if !personPhotos.isEmpty {
|
|
|
+ allGroups.append((personPhotos, personName, "people", result.peopleSize))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始处理第一个组
|
|
|
+ if allGroups.isEmpty {
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ print("No groups to process")
|
|
|
+ flutterResult([])
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ processGroups(index: 0, groups: allGroups)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+// self.photoClassifier.classifyPhotos(
|
|
|
+// assets: allPhotos,
|
|
|
+// progressHandler: { (stage, progress) in
|
|
|
+// print("Progress: \(stage) - \(progress)")
|
|
|
+// },
|
|
|
+// completion: { result in
|
|
|
+// var resultData: [[String: Any]] = []
|
|
|
+// let mainGroup = DispatchGroup()
|
|
|
+//
|
|
|
+// // 处理截图
|
|
|
+// mainGroup.enter()
|
|
|
+// self.processPhotoGroup(assets: result.screenshots, groupName: "screenshots", sizeInfo: result.screenshotsSize) { groupData in
|
|
|
+// if !groupData.isEmpty {
|
|
|
+// resultData.append(["group": groupData, "type": "screenshots"])
|
|
|
+// }
|
|
|
+// mainGroup.leave()
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 处理相似照片组
|
|
|
+// for photoGroup in result.similarPhotos {
|
|
|
+// mainGroup.enter()
|
|
|
+// self.processPhotoGroup(assets: photoGroup, groupName: "similar", sizeInfo: result.similarPhotosSize) { groupData in
|
|
|
+// if !groupData.isEmpty {
|
|
|
+// resultData.append(["group": groupData, "type": "similar"])
|
|
|
+// }
|
|
|
+// mainGroup.leave()
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 处理地点分组
|
|
|
+//// for (location, assets) in result.locations {
|
|
|
+//// mainGroup.enter()
|
|
|
+//// self.processPhotoGroup(assets: assets, groupName: location, sizeInfo: result.locationsSize) { groupData in
|
|
|
+//// if !groupData.isEmpty {
|
|
|
+//// resultData.append(["group": groupData, "type": "location", "name": location])
|
|
|
+//// }
|
|
|
+//// mainGroup.leave()
|
|
|
+//// }
|
|
|
+//// }
|
|
|
+//
|
|
|
+// // 处理人物分组
|
|
|
+//// for (person, assets) in result.people {
|
|
|
+//// mainGroup.enter()
|
|
|
+//// self.processPhotoGroup(assets: assets, groupName: person, sizeInfo: result.peopleSize) { groupData in
|
|
|
+//// if !groupData.isEmpty {
|
|
|
+//// resultData.append(["group": groupData, "type": "people"])
|
|
|
+//// }
|
|
|
+//// mainGroup.leave()
|
|
|
+//// }
|
|
|
+//// }
|
|
|
+//
|
|
|
+// // 处理模糊照片
|
|
|
+// mainGroup.enter()
|
|
|
+// self.processPhotoGroup(assets: result.blurryPhotos, groupName: "blurry", sizeInfo: result.blurryPhotosSize) { groupData in
|
|
|
+// if !groupData.isEmpty {
|
|
|
+// resultData.append(["group": groupData, "type": "blurry"])
|
|
|
+// }
|
|
|
+// mainGroup.leave()
|
|
|
+// }
|
|
|
+//
|
|
|
+// mainGroup.notify(queue: .main) {
|
|
|
+// print("Final result count: \(resultData.count)")
|
|
|
+// flutterResult(resultData)
|
|
|
+// }
|
|
|
+// }
|
|
|
+// )
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+// // 添加内存清理辅助方法
|
|
|
+// private func cleanupMemory() {
|
|
|
+// // 清理图像缓存
|
|
|
+// URLCache.shared.removeAllCachedResponses()
|
|
|
+//
|
|
|
+// // 强制进行一次垃圾回收
|
|
|
+// autoreleasepool {
|
|
|
+// let _ = [String](repeating: "temp", count: 1)
|
|
|
+// }
|
|
|
+//
|
|
|
+// #if os(iOS)
|
|
|
+// // 发送低内存警告
|
|
|
+// UIApplication.shared.perform(Selector(("_performMemoryWarning")))
|
|
|
+// #endif
|
|
|
+// }
|
|
|
+
|
|
|
+ // 添加内存清理方法
|
|
|
+// private func cleanupMemory() {
|
|
|
+// // 强制清理内存
|
|
|
+// autoreleasepool {
|
|
|
+// // 触发内存警告,促使系统回收内存
|
|
|
+// UIApplication.shared.performSelector(onMainThread: #selector(UIApplication.beginIgnoringInteractionEvents), with: nil, waitUntilDone: true)
|
|
|
+// UIApplication.shared.performSelector(onMainThread: #selector(UIApplication.endIgnoringInteractionEvents), with: nil, waitUntilDone: true)
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
// 处理照片组的辅助方法
|
|
|
private func processPhotoGroup(
|
|
|
assets: [PHAsset],
|
|
|
@@ -215,20 +651,22 @@ public class ClassifyPhotoPlugin: NSObject, FlutterPlugin {
|
|
|
photoProcessGroup.enter()
|
|
|
|
|
|
let options = PHContentEditingInputRequestOptions()
|
|
|
- options.isNetworkAccessAllowed = true
|
|
|
+ options.isNetworkAccessAllowed = false
|
|
|
|
|
|
- asset.requestContentEditingInput(with: options) { (input, info) in
|
|
|
- defer { photoProcessGroup.leave() }
|
|
|
-
|
|
|
- if let input = input, let url = input.fullSizeImageURL {
|
|
|
- let photoInfo: [String: Any] = [
|
|
|
- "path": url.path,
|
|
|
- "id": asset.localIdentifier,
|
|
|
- "width": asset.pixelWidth,
|
|
|
- "height": asset.pixelHeight,
|
|
|
- "creationDate": asset.creationDate?.timeIntervalSince1970 ?? 0
|
|
|
- ]
|
|
|
- photosData.append(photoInfo)
|
|
|
+ DispatchQueue.global(qos: .background).async {
|
|
|
+ asset.requestContentEditingInput(with: options) { (input, info) in
|
|
|
+ defer { photoProcessGroup.leave() }
|
|
|
+
|
|
|
+ if let input = input, let url = input.fullSizeImageURL {
|
|
|
+ let photoInfo: [String: Any] = [
|
|
|
+// "path": url.path,
|
|
|
+ "id": asset.localIdentifier,
|
|
|
+// "width": asset.pixelWidth,
|
|
|
+// "height": asset.pixelHeight,
|
|
|
+// "creationDate": asset.creationDate?.timeIntervalSince1970 ?? 0
|
|
|
+ ]
|
|
|
+ photosData.append(photoInfo)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -312,25 +750,89 @@ public class ClassifyPhotoPlugin: NSObject, FlutterPlugin {
|
|
|
extension ClassifyPhotoPlugin {
|
|
|
|
|
|
private func calculatePhotosSize(assetIds: [String], completion: @escaping FlutterResult) {
|
|
|
- let assets = PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: nil)
|
|
|
- var phAssets: [PHAsset] = []
|
|
|
- var sizes: Int64 = 0 // 用于存储文件大小
|
|
|
-
|
|
|
- assets.enumerateObjects { (phAsset, _, _) in
|
|
|
- phAssets.append(phAsset)
|
|
|
+ // 使用与调用者相同的QoS级别,避免优先级反转
|
|
|
+ DispatchQueue.global(qos: .userInitiated).async {
|
|
|
+ let assets = PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: nil)
|
|
|
+
|
|
|
+ // 使用原子操作确保线程安全
|
|
|
+ let totalSize = Atomic<Int64>(0)
|
|
|
+ let processingGroup = DispatchGroup()
|
|
|
+
|
|
|
+ // 创建一个具有相同QoS的调度队列用于信号量操作
|
|
|
+ let processingQueue = DispatchQueue(label: "com.yourapp.photosize.processing",
|
|
|
+ qos: .userInitiated,
|
|
|
+ attributes: .concurrent)
|
|
|
+
|
|
|
+ // 控制并发数量
|
|
|
+ let semaphore = DispatchSemaphore(value: 4)
|
|
|
+
|
|
|
+ // 分批处理资源
|
|
|
+ let batchSize = 20
|
|
|
+ let totalCount = assets.count
|
|
|
|
|
|
- // 获取 PHAsset 的资源
|
|
|
- let resources = PHAssetResource.assetResources(for: phAsset)
|
|
|
- if let resource = resources.first {
|
|
|
- // 获取文件大小
|
|
|
- if let unsignedInt64 = resource.value(forKey: "fileSize") as? CLong {
|
|
|
- sizes += Int64(unsignedInt64)
|
|
|
+ for batchStart in stride(from: 0, to: totalCount, by: batchSize) {
|
|
|
+ let end = min(batchStart + batchSize, totalCount)
|
|
|
+
|
|
|
+ processingGroup.enter()
|
|
|
+ // 使用具有明确QoS的队列
|
|
|
+ processingQueue.async {
|
|
|
+ var batchSize: Int64 = 0
|
|
|
+
|
|
|
+ for i in batchStart..<end {
|
|
|
+ autoreleasepool {
|
|
|
+ // 使用带超时的等待,避免无限期阻塞
|
|
|
+ let waitResult = semaphore.wait(timeout: .now() + 5)
|
|
|
+
|
|
|
+ defer {
|
|
|
+ // 确保信号量总是被释放
|
|
|
+ if waitResult != .timedOut {
|
|
|
+ semaphore.signal()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果等待超时,跳过当前资源
|
|
|
+ if waitResult == .timedOut {
|
|
|
+ print("警告: 等待资源超时,跳过资源")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let asset = assets.object(at: i)
|
|
|
+
|
|
|
+ // 使用资源管理器获取大小
|
|
|
+ PHAssetResource.assetResources(for: asset).forEach { resource in
|
|
|
+ var resourceSize: Int64 = 0
|
|
|
+
|
|
|
+ // 尝试获取文件大小
|
|
|
+ if let fileSize = resource.value(forKey: "fileSize") as? CLong {
|
|
|
+ resourceSize = Int64(fileSize)
|
|
|
+ }
|
|
|
+
|
|
|
+ batchSize += resourceSize
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新总大小
|
|
|
+ totalSize.mutate { $0 += batchSize }
|
|
|
+ processingGroup.leave()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用带超时的等待,避免无限期阻塞
|
|
|
+ let waitResult = processingGroup.wait(timeout: .now() + 30)
|
|
|
+
|
|
|
+ // 返回结果到主线程
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ if waitResult == .timedOut {
|
|
|
+ print("警告: 处理照片大小超时")
|
|
|
+ completion(FlutterError(code: "TIMEOUT",
|
|
|
+ message: "计算照片大小超时",
|
|
|
+ details: nil))
|
|
|
+ } else {
|
|
|
+ completion(totalSize.value)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- // 返回文件大小到 Flutter
|
|
|
- completion(sizes)
|
|
|
}
|
|
|
}
|
|
|
|