瀏覽代碼

fix: 修复一些问题

Destiny 9 月之前
父節點
當前提交
8948705f23

+ 7 - 9
lib/module/home/home_controller.dart

@@ -104,17 +104,15 @@ class HomeController extends BaseController {
         freeSpace.value = 50.0;
       });
 
-
-
     }
 
-    if (await Permission.photos.request().isGranted) {
-      PhotoManager.clearFileCache();
-      getStorageInfo();
-      handlePhotos();
-    } else {
-      ToastUtil.show("Please enable the album permission");
-    }
+    // if (await Permission.photos.request().isGranted) {
+    //   PhotoManager.clearFileCache();
+    //   getStorageInfo();
+    //   handlePhotos();
+    // } else {
+    //   ToastUtil.show("Please enable the album permission");
+    // }
 
     configRepository.refreshConfig();
     await userRepository.getUserInfo();

+ 54 - 62
lib/module/home/home_view.dart

@@ -337,7 +337,7 @@ class HomePage extends BaseView<HomeController> {
                             children: [
                               TextSpan(
                                 text:
-                                    "${ImagePickerUtil.similarPhotoCount.value}",
+                                "${ImagePickerUtil.similarPhotoCount.value}",
                                 style: TextStyle(
                                   color: Colors.white,
                                   fontSize: 12.sp,
@@ -361,7 +361,7 @@ class HomePage extends BaseView<HomeController> {
                   ),
                   Obx(() {
                     return CleanUpButton(
-                      label: !controller.isScanned.value
+                      label: !controller.isSimilarScanned.value
                           ? 'Scanning...'
                           : 'Clean up',
                       size: ImagePickerUtil.formatFileSize(
@@ -376,62 +376,52 @@ class HomePage extends BaseView<HomeController> {
               // SizedBox(height: 19.h),
               Spacer(),
               Obx(() {
-                return CleanUpButton(
-                  label:
-                  !controller.isSimilarScanned.value ? 'Scanning...' : 'Clean up',
-                  size: ImagePickerUtil.formatFileSize(
-                      ImagePickerUtil.similarPhotosSize.value),
-                  onTap: () {
-                    controller.similarCleanClick();
-                  },
-                );
-              }),
-            ],
-          ),
-          // SizedBox(height: 19.h),
-          Spacer(),
-          Obx(() {
-            return Row(
-              mainAxisAlignment: MainAxisAlignment.spaceBetween,
-              children: List.generate(4, (index) {
-                var image = Assets.images.iconHomeNoPhoto.image(
-                  width: 70.w * 0.45,
-                  height: 70.w * 0.45,
-                );
-                if (controller.similarPhotos.length > index) {
-                  image = AssetEntityImage(
-                      width: 70.w,
-                      height: 70.w,
-                      controller.similarPhotos[index],
-                      isOriginal: false,
-                      thumbnailSize: const ThumbnailSize.square(300),
-                      fit: BoxFit.cover,
-                      errorBuilder: (context, error, stackTrace) {
-                        return Assets.images.iconHomeNoPhoto.image(
-                          width: 70.w * 0.45,
-                          height: 70.w * 0.45,
-                        );
-                      });
-                }
-                return ImageContainer(
-                  size: 70.w,
-                  image: controller.similarPhotos.length > index ? image : Opacity(
-                    opacity: 0.22,
-                    child: const CircularProgressIndicator(color: Colors.white38,)
-                  ),
-                  // AssetEntityImage(
-                  //         width: 70.w,
-                  //         height: 70.w,
-                  //         controller.similarPhotos[index],
-                  //         isOriginal: false,
-                  //         thumbnailSize: const ThumbnailSize.square(300),
-                  //         fit: BoxFit.cover,
-                  //         errorBuilder: (context, error, stackTrace) {
-                  //           return Assets.images.iconHomeNoPhoto.image(
-                  //             width: 70.w * 0.45,
-                  //             height: 70.w * 0.45,
-                  //           );
-                  //         },
+                return Row(
+                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                  children: List.generate(4, (index) {
+                    var image = Assets.images.iconHomeNoPhoto.image(
+                      width: 70.w * 0.45,
+                      height: 70.w * 0.45,
+                    );
+                    if (controller.similarPhotos.length > index) {
+                      image = AssetEntityImage(
+                          width: 70.w,
+                          height: 70.w,
+                          controller.similarPhotos[index],
+                          isOriginal: false,
+                          thumbnailSize: const ThumbnailSize.square(300),
+                          fit: BoxFit.cover,
+                          errorBuilder: (context, error, stackTrace) {
+                            return Assets.images.iconHomeNoPhoto.image(
+                              width: 70.w * 0.45,
+                              height: 70.w * 0.45,
+                            );
+                          });
+                    }
+                    return controller.similarPhotos.isNotEmpty
+                        ? image
+                        : ImageContainer(
+                      size: 70.w,
+                      image: Opacity(
+                        opacity: 0.22,
+                        child: Lottie.asset(Assets.anim.animNoPhoto,
+                            repeat: true, width: 100.w, height: 100.w),
+                      ),
+                      // AssetEntityImage(
+                      //         width: 70.w,
+                      //         height: 70.w,
+                      //         controller.similarPhotos[index],
+                      //         isOriginal: false,
+                      //         thumbnailSize: const ThumbnailSize.square(300),
+                      //         fit: BoxFit.cover,
+                      //         errorBuilder: (context, error, stackTrace) {
+                      //           return Assets.images.iconHomeNoPhoto.image(
+                      //             width: 70.w * 0.45,
+                      //             height: 70.w * 0.45,
+                      //           );
+                      //         },
+                    );
+                  }),
                 );
               }),
               Spacer(),
@@ -515,7 +505,10 @@ class HomePage extends BaseView<HomeController> {
                         return ImageContainer(
                           image: controller.peoplePhotos.length > index ? image : Opacity(
                             opacity: 0.22,
-                            child: const CircularProgressIndicator(color: Colors.white38,),
+                            child: Lottie.asset(Assets.anim.animNoPhoto,
+                                repeat: true,
+                                width: 140.w,
+                                height: 140.w),
                           ),
                           size: 146.w,
                           // Image.file(
@@ -663,9 +656,8 @@ class HomePage extends BaseView<HomeController> {
               controller.screenshotPhoto.value == null
                   ? Opacity(
                       opacity: 0.22,
-                      child: const CircularProgressIndicator(
-                        color: Colors.white38,
-                      ),
+                      child: Lottie.asset(Assets.anim.animNoPhoto,
+                          repeat: true, width: 100.w, height: 100.w),
                     )
                   : AssetEntityImage(
                       width: 144.w,

+ 4 - 1
lib/module/image_picker/image_picker_util.dart

@@ -162,7 +162,7 @@ class ImagePickerUtil {
   // 更新照片数据
   static Future<void> updatePhotos(
       List<Map<String, dynamic>> photoGroups) async {
-    clearAllPhotos();
+    // clearAllPhotos();
 
     for (var group in photoGroups) {
       String type = group['type'] as String;
@@ -173,6 +173,7 @@ class ImagePickerUtil {
 
       switch (type) {
         case 'screenshots':
+          screenshotPhotos.clear();
           screenshotPhotos.value = await _convertToAssetEntities(photos);
           screenshotsSize.value = totalSize;
           break;
@@ -187,10 +188,12 @@ class ImagePickerUtil {
           locationsSize.value = totalSize;
           break;
         case 'people':
+          peoplePhotos.clear();
           peoplePhotos.value = await _convertToAssetEntities(photos);
           peopleSize.value = totalSize;
           break;
         case 'blurry':
+          blurryPhotos.clear();
           blurryPhotos.value = await _convertToAssetEntities(photos);
           blurrySize.value = totalSize;
           break;

+ 1 - 1
lib/utils/file_utils.dart

@@ -33,7 +33,7 @@ class FileUtils {
   static Future<AssetEntity?> saveAsset(FileType type, AssetEntity asset) async {
     try {
       final assetPath = await getAssetPath(type);
-      final title= Platform.isIOS ? asset.id.substring(0, 36) : asset.id;
+      final title = Platform.isIOS ? asset.id.substring(0, 36) : asset.id;
       final assetFile = File('$assetPath/$title.json');
 
       // // 将 AssetEntity 转换为 AssetInfo 后再序列化

+ 264 - 64
plugins/classify_photo/ios/Classes/ClassifyPhoto.swift

@@ -415,108 +415,301 @@ class ClassifyPhoto {
     }
     
     func classifyByPeople(assets: PHFetchResult<PHAsset>,
-                                completion: @escaping ([String: [PHAsset]]) -> Void) {
+                         completion: @escaping ([String: [PHAsset]]) -> Void) {
+        // 创建结果字典
         var peopleGroups: [String: [PHAsset]] = [:]
-        let group = DispatchGroup()
+        peopleGroups["包含人脸的照片"] = []
+        
+        // 使用主队列确保安全完成
+        let mainCompletion: ([String: [PHAsset]]) -> Void = { result in
+            DispatchQueue.main.async {
+                completion(result)
+            }
+        }
+        
+        // 限制处理的照片数量,防止内存过载
+        let totalCount = min(500, assets.count)
+        if totalCount == 0 {
+            mainCompletion(peopleGroups)
+            return
+        }
         
-        // 创建专用队列和信号量控制并发
-        let processingQueue = DispatchQueue(label: "com.app.peopleDetection", attributes: .concurrent)
-        let resultQueue = DispatchQueue(label: "com.app.peopleResult")
-        let semaphore = DispatchSemaphore(value: 4) // 限制并发数
+        // 创建专用队列
+        let processingQueue = DispatchQueue(label: "com.app.peopleDetection", qos: .userInitiated, attributes: .concurrent)
+        let resultQueue = DispatchQueue(label: "com.app.peopleResult", qos: .userInitiated)
+        
+        // 使用NSLock替代原子操作,更安全
+        let resultLock = NSLock()
         
         // 创建进度追踪
-        var processedCount = 0
-        let totalCount = assets.count
+        let processedCount = Atomic<Int>(0)
         
         // 分批处理,每批处理一部分数据
-        let batchSize = 50
-        let batches = Int(ceil(Float(assets.count) / Float(batchSize)))
+        let batchSize = 20
+        let batches = Int(ceil(Float(totalCount) / Float(batchSize)))
+        
+        // 创建一个组来等待所有操作完成
+        let group = DispatchGroup()
+        
+        // 创建一个Vision请求处理器
+        let faceDetectionRequest = VNDetectFaceRectanglesRequest()
+        
+        // 防止过早释放
+        var strongSelf: AnyObject? = self
         
         for batchIndex in 0..<batches {
             let startIndex = batchIndex * batchSize
-            let endIndex = min(startIndex + batchSize, assets.count)
+            let endIndex = min(startIndex + batchSize, totalCount)
             
-            // 使用自动释放池减少内存占用
-            autoreleasepool {
+            // 每批处理前进入组
+            group.enter()
+            
+            // 使用延迟减轻主线程压力
+            DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + Double(batchIndex) * 0.3) { [weak self] in
+                guard let self = self else {
+                    group.leave()
+                    return
+                }
+                
+                // 创建批次内的处理组
+                let batchGroup = DispatchGroup()
+                // 限制并发数
+                let batchSemaphore = DispatchSemaphore(value: 2)
+                
                 for i in startIndex..<endIndex {
-                    let asset = assets[i]
-                    group.enter()
-                    semaphore.wait()
-                    
-                    // 降低处理图片的分辨率
-                    let options = PHImageRequestOptions()
-                    options.deliveryMode = .fastFormat
-                    options.isSynchronous = false
-                    options.resizeMode = .fast
+                    batchGroup.enter()
+                    batchSemaphore.wait()
                     
                     processingQueue.async {
                         // 使用自动释放池减少内存占用
                         autoreleasepool {
-                            let result = PHImageManager.default().requestImage(
+                            let asset = assets[i]
+                            
+                            // 降低处理图片的分辨率
+                            let options = PHImageRequestOptions()
+                            options.deliveryMode = .fastFormat
+                            options.isSynchronous = false
+                            options.resizeMode = .fast
+                            options.isNetworkAccessAllowed = false
+                            
+                            PHImageManager.default().requestImage(
                                 for: asset,
-                                targetSize: CGSize(width: 128, height: 128), // 降低分辨率
+                                targetSize: CGSize(width: 120, height: 120),
                                 contentMode: .aspectFit,
                                 options: options
-                            ) { image, _ in
+                            ) { image, info in
                                 defer {
-                                    semaphore.signal()
+                                    batchSemaphore.signal()
+                                    batchGroup.leave()
                                 }
                                 
-                                guard let image = image else {
-                                    group.leave()
+                                // 检查是否是降级的图像
+                                if let degraded = info?[PHImageResultIsDegradedKey] as? Bool, degraded {
                                     return
                                 }
                                 
-                                // 使用 Vision 框架检测人脸
-                                guard let ciImage = CIImage(image: image) else {
-                                    group.leave()
+                                guard let image = image, let cgImage = image.cgImage else {
                                     return
                                 }
                                 
-                                let request = VNDetectFaceRectanglesRequest()
-                                let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
+                                // 使用简化的人脸检测
+                                let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
                                 
                                 do {
-                                    try handler.perform([request])
-                                    if let results = request.results, !results.isEmpty {
+                                    try handler.perform([faceDetectionRequest])
+                                    if let results = faceDetectionRequest.results, !results.isEmpty {
                                         // 检测到人脸,添加到数组
-                                        resultQueue.async {
-                                            if peopleGroups["包含人脸的照片"] == nil {
-                                                peopleGroups["包含人脸的照片"] = []
-                                            }
-                                            peopleGroups["包含人脸的照片"]?.append(asset)
-                                        }
+                                        resultLock.lock()
+                                        peopleGroups["包含人脸的照片"]?.append(asset)
+                                        resultLock.unlock()
                                     }
                                 } catch {
                                     print("人脸检测失败: \(error)")
                                 }
                                 
                                 // 更新进度
-                                resultQueue.async {
-                                    processedCount += 1
-                                    let progress = Float(processedCount) / Float(totalCount)
-                                    DispatchQueue.main.async {
-                                        print("人脸检测进度: \(Int(progress * 100))%")
-                                    }
-                                }
-                                
-                                group.leave()
+                                processedCount.mutate { $0 += 1 }
                             }
                         }
                     }
                 }
+                
+                // 等待批次内所有处理完成
+                batchGroup.wait()
+                
+                // 每批处理完后清理内存
+                self.cleanupMemory()
+                
+                // 批次完成
+                group.leave()
             }
-            
-            // 每批处理完后清理内存
-            cleanupMemory()
         }
         
+        // 设置超时保护
+        let timeoutWorkItem = DispatchWorkItem {
+            print("人脸检测超时,返回当前结果")
+            mainCompletion(peopleGroups)
+            strongSelf = nil
+        }
+        
+        // 30秒后超时
+        DispatchQueue.global().asyncAfter(deadline: .now() + 30, execute: timeoutWorkItem)
+        
         // 等待所有检测完成后更新结果
         group.notify(queue: .main) {
-            completion(peopleGroups)
+            // 取消超时
+            timeoutWorkItem.cancel()
+            
+            // 最终清理内存
+            self.cleanupMemory()
+            
+            // 返回结果
+            mainCompletion(peopleGroups)
+            
+            // 释放引用
+            strongSelf = nil
         }
     }
 
+    // 优化的人脸检测方法
+    private func optimizedFaceDetection(in image: UIImage, request: VNDetectFaceRectanglesRequest, completion: @escaping (Bool) -> Void) {
+        guard let cgImage = image.cgImage else {
+            completion(false)
+            return
+        }
+        
+        // 在后台线程执行检测
+        DispatchQueue.global(qos: .userInitiated).async {
+            autoreleasepool {
+                let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
+                
+                do {
+                    try handler.perform([request])
+                    let hasFace = request.results?.isEmpty == false
+                    completion(hasFace)
+                } catch {
+                    print("人脸检测失败: \(error)")
+                    completion(false)
+                }
+            }
+        }
+    }
+    
+//    func classifyByPeople(assets: PHFetchResult<PHAsset>,
+//                         completion: @escaping ([String: [PHAsset]]) -> Void) {
+//        var peopleGroups: [String: [PHAsset]] = [:]
+//        let group = DispatchGroup()
+//        
+//        // 创建专用队列和信号量控制并发
+//        let processingQueue = DispatchQueue(label: "com.app.peopleDetection", qos: .userInitiated, attributes: .concurrent)
+//        let resultQueue = DispatchQueue(label: "com.app.peopleResult", qos: .userInitiated)
+//        let semaphore = DispatchSemaphore(value: 4) // 限制并发数
+//        
+//        // 创建进度追踪
+//        var processedCount = 0
+//        let totalCount = assets.count
+//        
+//        // 分批处理,每批处理一部分数据
+//        let batchSize = 50
+//        let batches = Int(ceil(Float(assets.count) / Float(batchSize)))
+//        
+//        for batchIndex in 0..<batches {
+//            let startIndex = batchIndex * batchSize
+//            let endIndex = min(startIndex + batchSize, assets.count)
+//            
+//            // 使用自动释放池减少内存占用
+//            autoreleasepool {
+//                for i in startIndex..<endIndex {
+//                    let asset = assets[i]
+//                    group.enter()
+//                    semaphore.wait()
+//                    
+//                    // 降低处理图片的分辨率
+//                    let options = PHImageRequestOptions()
+//                    options.deliveryMode = .fastFormat
+//                    options.isSynchronous = false
+//                    options.resizeMode = .fast
+//                    
+//                    processingQueue.async {
+//                        // 使用自动释放池减少内存占用
+//                        autoreleasepool {
+//                            let _ = PHImageManager.default().requestImage(
+//                                for: asset,
+//                                targetSize: CGSize(width: 128, height: 128), // 降低分辨率
+//                                contentMode: .aspectFit,
+//                                options: options
+//                            ) { image, _ in
+//                                defer {
+//                                    semaphore.signal()
+//                                }
+//                                
+//                                guard let image = image else {
+//                                    group.leave()
+//                                    return
+//                                }
+//                                
+//                                // 使用 Vision 框架检测人脸
+//                                guard let ciImage = CIImage(image: image) else {
+//                                    group.leave()
+//                                    return
+//                                }
+//                                
+//                                let request = VNDetectFaceRectanglesRequest()
+//                                let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
+//                                
+//                                do {
+//                                    try handler.perform([request])
+//                                    if let results = request.results, !results.isEmpty {
+//                                        // 检测到人脸,添加到数组
+//                                        resultQueue.async {
+//                                            if peopleGroups["包含人脸的照片"] == nil {
+//                                                peopleGroups["包含人脸的照片"] = []
+//                                            }
+//                                            peopleGroups["包含人脸的照片"]?.append(asset)
+//                                        }
+//                                    }
+//                                } catch {
+//                                    print("人脸检测失败: \(error)")
+//                                }
+//                                
+//                                // 更新进度
+//                                resultQueue.async {
+//                                    processedCount += 1
+//                                    let progress = Float(processedCount) / Float(totalCount)
+//                                    if processedCount % 100 == 0 || processedCount == totalCount {
+//                                        DispatchQueue.main.async {
+//                                            print("人脸检测进度: \(Int(progress * 100))%")
+//                                        }
+//                                    }
+//                                }
+//                                
+//                                group.leave()
+//                            }
+//                        }
+//                    }
+//                }
+//            }
+//            
+//            // 每批处理完后清理内存
+//            cleanupMemory()
+//        }
+//        
+//        // 等待所有检测完成后更新结果
+//        group.notify(queue: .main) {
+//            completion(peopleGroups)
+//        }
+//    }
+
+//    // 添加内存清理方法(如果还没有)
+//    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)
+//        }
+//    }
+
     // 按人物分类
 //    func classifyByPeople(assets: PHFetchResult<PHAsset>,
 //                                completion: @escaping ([String: [PHAsset]]) -> Void) {
@@ -749,10 +942,10 @@ class ClassifyPhoto {
         // 使用更小的采样区域
         let width = cgImage.width
         let height = cgImage.height
-        let stride = 4 // 增加步长,减少处理像素数
+        let pixelStride = 4 // 改名为pixelStride,避免与函数名冲突
         
         // 提前检查图像尺寸是否合法
-        guard width > (2 * stride), height > (2 * stride) else {
+        guard width > (2 * pixelStride), height > (2 * pixelStride) else {
             return false
         }
         
@@ -779,16 +972,21 @@ class ClassifyPhoto {
         var sampledPixels = 0
         
         // 只采样图像的一部分区域
-        let sampleRows = min(10, height / stride)
-        let sampleCols = min(10, width / stride)
+        let sampleRows = 10
+        let sampleCols = 10
         
-        for y in stride(from: stride, to: height - stride, by: stride * sampleRows / 10) {
-            for x in stride(from: stride, to: width - stride, by: stride * sampleCols / 10) {
+        // 计算步长
+        let rowStep = max(1, height / sampleRows)
+        let colStep = max(1, width / sampleCols)
+        
+        // 使用Swift的stride函数
+        for y in Swift.stride(from: pixelStride, to: height - pixelStride, by: rowStep) {
+            for x in Swift.stride(from: pixelStride, to: width - pixelStride, by: colStep) {
                 let current = Int(buffer[y * width + x])
-                let left = Int(buffer[y * width + (x - stride)])
-                let right = Int(buffer[y * width + (x + stride)])
-                let top = Int(buffer[(y - stride) * width + x])
-                let bottom = Int(buffer[(y + stride) * width + x])
+                let left = Int(buffer[y * width + (x - pixelStride)])
+                let right = Int(buffer[y * width + (x + pixelStride)])
+                let top = Int(buffer[(y - pixelStride) * width + x])
+                let bottom = Int(buffer[(y + pixelStride) * width + x])
                 
                 // 简化的边缘检测
                 let dx = abs(left - right)
@@ -808,6 +1006,8 @@ class ClassifyPhoto {
         let threshold = 15.0
         return normalizedScore < threshold
     }
+
+    // ... existing code ...
     
 //    func detectBlurryPhotos(from assets: PHFetchResult<PHAsset>, completion: @escaping ([PHAsset]) -> Void) {
 //        var blurryPhotos: [PHAsset] = []

+ 227 - 20
plugins/classify_photo/ios/Classes/ClassifyPhotoPlugin.swift

@@ -749,19 +749,135 @@ public class ClassifyPhotoPlugin: NSObject, FlutterPlugin {
 // 计算选择的图片大小
 extension ClassifyPhotoPlugin {
     
+//    private func calculatePhotosSize(assetIds: [String], completion: @escaping FlutterResult) {
+//        // 使用与调用者相同的QoS级别
+//        let callerQoS: DispatchQoS = .userInitiated
+//        
+//        DispatchQueue.global(qos: callerQoS.qosClass).async {
+//            let assets = PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: nil)
+//            
+//            // 使用原子操作确保线程安全
+//            let totalSize = Atomic<Int64>(0)
+//            let processedCount = Atomic<Int>(0)
+//            
+//            // 创建一个与调用者相同QoS的组
+//            let processingGroup = DispatchGroup()
+//            
+//            // 创建一个与调用者相同QoS的队列
+//            let processingQueue = DispatchQueue(label: "com.yourapp.photosize.processing",
+//                                               qos: callerQoS,
+//                                               attributes: .concurrent)
+//            
+//            // 创建一个与调用者相同QoS的信号量队列
+//            let semaphoreQueue = DispatchQueue(label: "com.yourapp.photosize.semaphore",
+//                                              qos: callerQoS)
+//            
+//            // 控制并发数量
+//            let maxConcurrent = 4
+//            var activeWorkers = 0
+//            
+//            // 分批处理资源
+//            let batchSize = 20
+//            let totalCount = assets.count
+//            
+//            // 如果没有资产,直接返回
+//            if totalCount == 0 {
+//                DispatchQueue.main.async {
+//                    completion(0)
+//                }
+//                return
+//            }
+//            
+//            // 创建一个函数来处理下一个批次
+//            @Sendable func processNextBatch() {
+//                // 计算下一个批次的范围
+//                let currentProcessed = processedCount.value
+//                if currentProcessed >= totalCount {
+//                    // 所有批次已处理完毕
+//                    return
+//                }
+//                
+//                let batchStart = currentProcessed
+//                let batchEnd = min(batchStart + batchSize, totalCount)
+//                
+//                // 更新已处理计数
+//                processedCount.mutate { $0 = batchEnd }
+//                
+//                processingGroup.enter()
+//                processingQueue.async {
+//                    var batchSize: Int64 = 0
+//                    
+//                    for i in batchStart..<batchEnd {
+//                        autoreleasepool {
+//                            let asset = assets.object(at: i)
+//                            
+//                            // 使用资源管理器获取大小
+//                            PHAssetResource.assetResources(for: asset).forEach { resource in
+//                                if let fileSize = resource.value(forKey: "fileSize") as? CLong {
+//                                    batchSize += Int64(fileSize)
+//                                }
+//                            }
+//                        }
+//                    }
+//                    
+//                    // 更新总大小
+//                    totalSize.mutate { $0 += batchSize }
+//                    
+//                    // 处理下一个批次
+//                    semaphoreQueue.async {
+//                        activeWorkers -= 1
+//                        scheduleMoreWorkIfNeeded()
+//                    }
+//                    
+//                    processingGroup.leave()
+//                }
+//            }
+//            
+//            // 调度更多工作如果需要
+//            func scheduleMoreWorkIfNeeded() {
+//                while activeWorkers < maxConcurrent && processedCount.value < totalCount {
+//                    activeWorkers += 1
+//                    processNextBatch()
+//                }
+//            }
+//            
+//            // 开始处理
+//            semaphoreQueue.async {
+//                scheduleMoreWorkIfNeeded()
+//            }
+//            
+//            // 使用带超时的等待,避免无限期阻塞
+//            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)
+//                }
+//            }
+//        }
+//    }
+    
     private func calculatePhotosSize(assetIds: [String], completion: @escaping FlutterResult) {
-        // 使用与调用者相同的QoS级别,避免优先级反转
+        // 使用与调用者相同的QoS级别
         DispatchQueue.global(qos: .userInitiated).async {
             let assets = PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: nil)
             
             // 使用原子操作确保线程安全
             let totalSize = Atomic<Int64>(0)
+            
+            // 创建一个与调用者相同QoS的组
             let processingGroup = DispatchGroup()
             
-            // 创建一个具有相同QoS的调度队列用于信号量操作
+            // 创建一个与调用者相同QoS的队列,确保所有操作都有明确的QoS
             let processingQueue = DispatchQueue(label: "com.yourapp.photosize.processing",
-                                               qos: .userInitiated,
-                                               attributes: .concurrent)
+                                                qos: .userInitiated,
+                                                attributes: .concurrent)
             
             // 控制并发数量
             let semaphore = DispatchSemaphore(value: 4)
@@ -770,17 +886,26 @@ extension ClassifyPhotoPlugin {
             let batchSize = 20
             let totalCount = assets.count
             
+            // 如果没有资产,直接返回
+            if totalCount == 0 {
+                DispatchQueue.main.async {
+                    completion(0)
+                }
+                return
+            }
+            
             for batchStart in stride(from: 0, to: totalCount, by: batchSize) {
                 let end = min(batchStart + batchSize, totalCount)
                 
                 processingGroup.enter()
-                // 使用具有明确QoS的队列
+                // 确保使用明确QoS的队列
                 processingQueue.async {
-                    var batchSize: Int64 = 0
-                    
-                    for i in batchStart..<end {
-                        autoreleasepool {
+                    autoreleasepool {
+                        var batchSize: Int64 = 0
+                        
+                        for i in batchStart..<end {
                             // 使用带超时的等待,避免无限期阻塞
+                            // 重要:在同一个队列中等待和信号,避免优先级反转
                             let waitResult = semaphore.wait(timeout: .now() + 5)
                             
                             defer {
@@ -793,27 +918,23 @@ extension ClassifyPhotoPlugin {
                             // 如果等待超时,跳过当前资源
                             if waitResult == .timedOut {
                                 print("警告: 等待资源超时,跳过资源")
-                                return
+                                continue
                             }
                             
                             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 += Int64(fileSize)
                                 }
-                                
-                                batchSize += resourceSize
                             }
                         }
+                        
+                        // 更新总大小
+                        totalSize.mutate { $0 += batchSize }
                     }
                     
-                    // 更新总大小
-                    totalSize.mutate { $0 += batchSize }
                     processingGroup.leave()
                 }
             }
@@ -826,14 +947,100 @@ extension ClassifyPhotoPlugin {
                 if waitResult == .timedOut {
                     print("警告: 处理照片大小超时")
                     completion(FlutterError(code: "TIMEOUT",
-                                           message: "计算照片大小超时",
-                                           details: nil))
+                                            message: "计算照片大小超时",
+                                            details: nil))
                 } else {
                     completion(totalSize.value)
                 }
             }
         }
     }
+    
+//    private func calculatePhotosSize(assetIds: [String], completion: @escaping FlutterResult) {
+//        // 使用与调用者相同的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
+//            
+//            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)
+//                }
+//            }
+//        }
+//    }
 }
 
 

+ 1 - 1
pubspec.yaml

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 # In Windows, build-name is used as the major, minor, and patch parts
 # of the product and file versions while build-number is used as the build suffix.
-version: 1.3.0+28
+version: 1.3.0+30
 
 environment:
   sdk: ^3.6.0