拍照APP发展过程 2025-08-26 周二

简介

  • 用苹果iPhone13手机拍照,传到公司后台,供网站和APP浏览。这个照片清晰度比较高,对于电商推广助力很大,是公司产品的亮点之一。
  • 照片的规格是4032*3024,图片质量高,体积也大,给拍照和上传也带来了一些麻烦。
  • 2022年4月开始第一个版本,那个时候只有Object-C熟练,现在就一路沿用下来。如果时间充裕的话,重来一次,可以考虑用Flutter写,定制拍照部分可以用Swift插件实现。

第1版:后台转发

  • 为了赶进度,采用后台转发的方式上传照片,就像一个普通的后台接口那样;
  • 一般情况,图片接口采用表单数据的方式,只是我们没有采用;
  • 将UIImage转NSData,然后Base64转字符串,就像是一个普通的字符串参数的后端接口;
  • 对于客户端来说,这是大大降低了开发难度,速度也快了很多。

第2版:阿里OSS上传

  • 运行了一段时间,后端服务器不堪重负,需要改成OSS上传的方式
  • 照片数据最终是存储在阿里云上的,后端转一手,本来也没什么,但是现在后端资源不够,所以需要去掉这个中间商。
  • 一个订单可以有6张照片,没张照片又有原图和水印图两份,每张图片在PNG格式下有20M左右,我们采用的是webp格式,每张图片的大小1M左右。
  • 阿里云提供了AliyunOSSiOS Pod库,是Object-C写的,引入进来之后,上传速度比较快,后端服务器压力也随之减小。

第3版:时间优化

  • 对于Object-C来说,PNG和JPEG格式非常方便,有现成的API可以用。但是webp就很少了,幸亏有YYImage可以用,但是耗时长,内存占用大。
/**
 *  WebP图片压缩
 *
 *  @param image      需要压缩的图片
 *  @param compressionQuality 压缩系数;0到1之间
 *
 *  备注:WebP的压缩比较耗时,需要放入工作者线程中
 *
 */
+ (nullable NSData *)webpDataWithImage:(UIImage *)image compressionQuality:(CGFloat)compressionQuality {
    // 参数检查
    if (image == nil) {
        return nil;
    }
    CGFloat quality = compressionQuality;
    if (quality < 0) {
        quality = 0;
    }
    if (quality > 1) {
        quality = 1;
    }
    
    /// webp压缩耗时耗内存,可能会导致崩溃,这里放入一个try结构
    @try {
        NSData *webpData = [YYImageEncoder encodeImage:image type:YYImageTypeWebP quality:quality];
        NSLog(@"webpData; quality:%0.2f; length:%ld", quality, webpData.length);
        return webpData;
    } @catch (NSException *exception) {
        NSLog(@"webp压缩出错:%@", exception.description);
        return nil;
    }
}
  • 测试环境下,按照拍6张照片的情况,试了一下,webp压缩大约要3秒,阿里云OSS上传只要2秒,合计大约5秒一次,感觉速度还不错。
  • 实际工厂反馈说速度有点慢,估计是仓库实际使用环境的网络比较慢,总时间比测试环境要长。
  • 采取的方式把压缩和上传的任务放后台,操作员拍完照只要提交就好了,压缩和上传任务在后台完成。
  • 提供一个测试记录页面随时查看上传情况:分为“成功,失败,上传中三种状态。

第4版:内存优化

  • 后来引入定制拍照,数量没有6个限制,导致时间更长,内存占用更大
  • 仓库实际的一个例子,要拍10张照片,引起程序闪退,分析原因是因为内存占用过大。
  • 测试环境下,开启XCode的内存监控,发现确实内存有超过2G的情况,确实有可能导致崩溃。
  • 由于内存占用最大的就是webp的压缩过程,所以采取的措施是把拍摄之后得到的UIImage转为webp之后,存储在本地的Document目录。上传的时候,再从Document目录读取对应的文件。这样的话,UIImage和NSData这些大数量都不需要保存在模型中了。
/**
 *  保存图片数据到本地;采用webp压缩
 *  webp压缩过程耗时,调用时需要放到后台进程
 *
 *  @param image    图片数据
 *
 *  @return 文件路径
 */
+ (nullable NSString *)saveImageToLocal:(UIImage *)image withFileName:(NSString *)fileName {
    NSDate *startDateItem = [NSDate date];
    
    /// 获取Documents目录路径
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    
    /// 创建完整的文件路径
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
    filePath = [filePath stringByAppendingPathExtension:@"webp"];
    
    /// webp格式,0.8是压缩质量(0.0-1.0)
    NSData *imageData = [self webpDataWithImage:image compressionQuality:0.8];
    
    /// 写入文件
    BOOL success = [imageData writeToFile:filePath atomically:YES];
    if (success) {
        NSDate *endDateItem = [NSDate date];
        NSTimeInterval timeInterval = [endDateItem timeIntervalSinceDate:startDateItem];
        NSLog(@"图片保存成功,路径:%@,耗时: %0.2f 秒", filePath, timeInterval);
        return filePath;
    } else {
        NSLog(@"图片保存失败");
        return nil;
    }
}

/**
 *  从本地读取图片数据
 *
 *  @param filePath   文件路径
 *
 *  @return 文件数据
 */
+ (nullable NSData *)readDataFromLocal:(NSString *)filePath {
    /// 检查文件是否存在
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:filePath]) {
        NSLog(@"文件不存在: %@", filePath);
        return nil;
    }
    
    /// 读取文件数据
    NSData *fileData = [NSData dataWithContentsOfFile:filePath];
    if (fileData) {
        NSLog(@"文件读取成功,大小: %lu 字节", (unsigned long)fileData.length);
    } else {
        NSLog(@"文件读取失败");
    }
    
    return fileData;
}

/**
 *  从本地读取图片对象
 *
 *  @param filePath   文件路径
 *
 *  @return 图片对象
 */
+ (nullable UIImage *)readImageFromLocal:(NSString *)filePath {
    NSData *imageData = [self readDataFromLocal:filePath];
    if (imageData) {
        UIImage *image = [UIImage imageWithData:imageData];
        return image;
    } else {
        return nil;
    }
}
  • 普通拍照最多6张,定制拍照虽然总数很多,但是可以一张一张拍摄,这样就把内存占用限制在可控范围之内。
  • 调试的时候发现,YYWebImage或者SDWebImage这些第三方的图片展示库,随着照片数量的增加,内存占用也很多,需要加以限制。我们用的是YYWebImage,加了如下设置之后,内存占用明显减少:
// 配置YYWebImage的缓存参数
+ (void)configYYWebImageCache {
    // 获取YYWebImage的默认缓存实例
    YYImageCache *imageCache = [YYWebImageManager sharedManager].cache;
    
    // 1. 设置磁盘缓存最大容量(例如:500MB)
    // 1MB = 1024 * 1024 字节
    imageCache.diskCache.costLimit = 500 * 1024 * 1024;
    
    // 3. 可选:设置内存缓存最大容量(例如:50MB)
    // 内存缓存满时会按LRU策略清理
    imageCache.memoryCache.costLimit = 50 * 1024 * 1024;
    
    // 4. 可选:设置内存缓存的自动清理时机
    imageCache.memoryCache.shouldRemoveAllObjectsOnMemoryWarning = YES; // 内存警告时清空
    imageCache.memoryCache.shouldRemoveAllObjectsWhenEnteringBackground = YES; // 进入后台时清空
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容