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)