flutter 混合开发 原生iOS/Android处理多图压缩

flutter处理图片压缩效率不高,如果同时压缩多张图片,那就更慢了,有时候甚至要等10几秒甚至几十秒,使用了flutter_luban这个框架压缩,仍然不能达到比较理想的效果,想到原生的压缩效率很高,于是便考虑使用iOS/Android原生来处理压缩,然后通过消息传递的方式返回到flutter端显示和使用
压缩框架就使用luban压缩 压缩效果好,清晰度高还尺寸小,据说微信图片算法就是用的这个
iOS https://github.com/GuoZhiQiang/Luban_iOS
Android https://github.com/Curzibn/Luban

压缩比如下表.Compare:

机型 照片获取途径 原图大小.before 压缩后大小.after
6s 拍照(竖屏) 5.19 Mb 86.8 Kb
6s 拍照(横屏) 5.26 Mb 29.2 Kb
7plus 拍照(竖屏) 8.05 Mb 229.1 Kb
7plus 拍照(横屏) 6.34 Mb 39.6 Kb
6s 截屏 1.05 Mb 53.56 Kb
7plus 截屏 234.7 Kb 37.5 Kb

方案确定了下面就开始flutter与原生的通讯代码
1、确定好通讯方式这里使用MethodChannel,定义好通讯channelName 和 methodName
2、定义好通讯方式编写flutter代码,发送消息传递List图片列表

import 'package:flutter/services.dart';

class ChannelUtils {
  static const MethodChannel platform =
      const MethodChannel('kzmall.flutter.io');
  static const String METHOD_COMPRESS_FILE = 'method_compress_file';

  // 工厂模式
  factory ChannelUtils() => _getShared();

  static ChannelUtils get shared => _getShared();
  static ChannelUtils _shared;

  ChannelUtils._internal();

  static ChannelUtils _getShared() {
    if (_shared == null) {
      _shared = ChannelUtils._internal();
    }
    return _shared;
  }

  Future<List<dynamic>> compressFile(List<dynamic> files) async{
    try {
      print('flutter发送图片路径');
      final res = await platform.invokeMethod<List<dynamic>>(METHOD_COMPRESS_FILE, files);
       print('flutter接收到消息');
      return Future.value(res);
    } catch (e) {
      print(e);
      return null;
    }
  }
}

3、iOS端定义一个页面继承FlutterViewController,引入Luban-iOS压缩分类,编写通讯代码,处理多图片压缩的逻辑,由于luban压缩只支持单张图片压缩,所有这里通过dispatch_group开启多线程实现多图片压缩功能,将压缩后的图片存储在沙盒中,并返回图片的沙盒路径Path

//
//  KZFlutterViewController.m
//  Runner
//
//  Created by chenhao on 2021/3/31.
//

#import "KZFlutterViewController.h"
#import <Foundation/Foundation.h>
#import "UIImage+Luban_iOS_Extension_h.h"
#import <UIKit/UIKit.h>

///通道名
#define MSG_CHANNEL_NAME    @"kzmall.flutter.io"
///方法名
#define MSG_METHOD_NAME     @"method_compress_file"

#define MSG_METHOD_CLEAR_NAME     @"method_clear_compress_file"


#define FILE_PATH [NSTemporaryDirectory() stringByAppendingPathComponent:@"compress_imagefile_ios"]


@interface KZFlutterViewController ()

@property (nonatomic,strong) FlutterMethodChannel *methodChannel;

@end

@implementation KZFlutterViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    /// 创建消息对象
    self.methodChannel = [FlutterMethodChannel methodChannelWithName:MSG_CHANNEL_NAME binaryMessenger:self.binaryMessenger];
    
    __weak __typeof__(self) weakSelf = self;
    /// 监听消息
    [self.methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
        if([call.method isEqualToString:MSG_METHOD_NAME]){
            NSArray *list = call.arguments;
            if([list isKindOfClass:[NSArray class]]){
                [weakSelf handleMsgData:list result:result];
            }
        }else if([call.method isEqualToString:MSG_METHOD_CLEAR_NAME]){
            //清空缓存
            [weakSelf clearTmpDirectory:FILE_PATH];
        }
        else{
            result(FlutterMethodNotImplemented);
        }
    }];
}

-(void)handleMsgData:(NSArray *)list result:(FlutterResult)result{
    NSMutableArray *newList = [NSMutableArray array];
    //创建group
    dispatch_group_t group = dispatch_group_create();
    
    for (NSString *imgPath in list) {
        //创建GCD组
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //创建信号量 并初始化信号量值为0
            dispatch_semaphore_t  sema = dispatch_semaphore_create(0);
            //由于信号量初始化值为0,所有当第一次执行到这里的时候信号量值仍然为0,程序就开始在这里等待信号量变化,前面异步的网络请求结束后,执行了dispatch_semaphore_signal(sema)方法,信号量+1,这样,dispatch_semaphore_wait监听到信号量由0变为1后,跳出等待,并且结束当前group队列
            NSString *name = [imgPath componentsSeparatedByString:@"/"].lastObject;
            if([[NSFileManager defaultManager] fileExistsAtPath:imgPath]){
                UIImage *img = [UIImage imageWithContentsOfFile:imgPath];
                NSData *newImgData = [UIImage lubanCompressImage:img];
                // UIImage *newImg = [[UIImage alloc] initWithData:newImgData];
                //拿到新的路径
                NSString *newPath = [self imageFilePathWithName:name];
                // 注意这里最好直接存储lubanCompressImage返回的Data数据而不是转出UIImage在把image转出Data在存储 否则通过一顿操作最后取出来的图片尺寸并没有怎么压缩
                // [UIImagePNGRepresentation(newImg) writeToFile:newPath atomically:YES];
                [newImgData writeToFile:newPath atomically:YES];
                [newList addObject:newPath];
                dispatch_semaphore_signal(sema);
            }
            dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        });
    }
    
    //把group_notify放在主队列中,这样可以在这个队列中刷新UI
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //排序
        result(newList);
    });
}

-(NSString *)imageFilePathWithName:(NSString *)name{
    
    NSString *path = FILE_PATH;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    // fileExistsAtPath 判断一个文件或目录是否有效,isDirectory判断是否一个目录
    
    BOOL isDir = NO;
    BOOL existed = [fileManager fileExistsAtPath:path isDirectory:&isDir];
    if (!(isDir && existed)) {
        // 在Document目录下创建一个archiver目录
        [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
    }
    path = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"%@",name]];
    return path;
}

-(void)clearTmpDirectory:(NSString *)directoryPath{
    NSFileManager *mgr = [NSFileManager defaultManager];
    if([mgr fileExistsAtPath:directoryPath]){
        NSError *error;
        [mgr removeItemAtPath:directoryPath error:&error];
        if(error){
            NSLog(@"%@",error);
        }
    }
}


-(void)sendEvent:(NSString *)eventName
       arguments:(NSDictionary *)arguments
          result:(FlutterResult _Nullable)callback{
    if(!eventName) return;
    NSMutableDictionary *msg = [NSMutableDictionary new];
    msg[@"name"] = eventName;
    msg[@"arguments"] = arguments;
    [self.methodChannel invokeMethod:MSG_METHOD_NAME
                           arguments:msg
                              result:callback];
}

3、android端MainActivity继承FlutterActivity,导入Luban implementation 'top.zibin:Luban:1.1.8' 库,并引入import top.zibin.luban.Luban import top.zibin.luban.OnCompressListener,编写通讯和压缩的业务代码,android的luban框架更完善 支持同时压缩多张图片和自动缓存图片返回路径功能

class MainActivity : FlutterActivity() {

    private val CHANNEL_NAME = "kzmall.flutter.io"

    private val METHOD_COMPRESS_FILE = "method_compress_file"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
//        super.configureFlutterEngine(flutterEngine)
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME).setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result ->
            if (methodCall.method == METHOD_COMPRESS_FILE) {
                if (methodCall.arguments != null && methodCall.arguments is List<*>) {
                    var handlerNum = 0
                    var fileList: List<String> = methodCall.arguments as List<String>
                    var compressFiles: ArrayList<String> = arrayListOf()
                    Luban.with(this)
                            .load<String>(fileList)
                            .ignoreBy(100)
                            .filter { path -> !(TextUtils.isEmpty(path) || path.toLowerCase().endsWith(".gif")) }
                            .setCompressListener(object : OnCompressListener {
                                override fun onStart() {
                                }

                                override fun onSuccess(file: File?) {
                                    handlerNum++
                                    if (file != null) {
                                        compressFiles.add(file.absolutePath)
                                    }
                                    if (handlerNum == fileList.size) {
                                        result.success(compressFiles)
                                    }
                                }

                                override fun onError(e: Throwable) {
                                    result.error("-1", "图片压缩失败", null)
                                }
                            }).launch()
                } else {
                    result.error("-2", "参数类型错误", null)
                }


            } else {
                result.notImplemented()
            }
        }
    }
}

4、flutter端在使用的地方调用即可返回压缩后的图片

List<dynamic> imges = [图片列表]
final res = await ChannelUtils.shared.compressFile(imges)
posted @ 2022-11-11 10:44  qqcc1388  阅读(334)  评论(0编辑  收藏  举报