美文网首页人猿星球iOS开发进阶iOS开发
关于“发送原图”功能问题的记录

关于“发送原图”功能问题的记录

作者: si1ence | 来源:发表于2017-11-03 12:35 被阅读670次

    本文主要记录一个bug从发现、定位到延期解决的过程。文末添加了已踩过的坑

    微信中发送原图样式

    近期在做“发送原图”功能的时候,遇到一个bug:在Android、Windows、Mac 客户端发送原图,iOS客户端接收,保存原图后,原图物理尺寸不变,存储空间变小,对应的location等Exif信息丢失。与此同时,iOS客户端之间互发原图没有问题。针对这个问题,做了以下测试调研,现记录下来:

    一. 首先介绍一下发送一张原图的流程:

    1. 比如 Android 端发送一张原图,先上传到 IM 的服务器,上传成功后再发送消息体;(上传成功后,服务器会分配三个url分别对应缩略图、大图、原图)
    2. 接收方接收到消息体,下载缩略图;
    3. 点击缩略图,下载大图;
    4. 再点击“查看原图”按钮,下载原图;
    5. 下载成功后,长按图片,保存原图。

    二. 问题定位是在最后一步,保存图片的部分:

    • 下载的图片大小与服务器存储大小是完全一致的,保存之后大小就发生了变化(目前是“jpg”格式变小,“png”格式图片变大);
      • 下载的方式分别尝试了 AFNetwoking 下载、SDWebImage 的普通下载和高级下载方式(因为产品需求要求显示下载原图进度)
      • 结论下载的图片大小与服务器存储大小是完全一致的是因为在 SDWebImage 高级下载方法的 completionBlock 中有已下载的 bit 值
    • 尝试了能找到的各种保存图片的方式,均不行
    • 验证测试:将安卓端产生的图片(包括拍照“jpg”和屏幕截图“png”)从浏览器下载到电脑,大小不变,将该图片文件拖到项目中,执行保存图片的方法,大小也发生了变化。
    • 补充测试:安卓端拍摄一张图片(大小为5M),发送给 iOS 客户端(下载大小为5M),保存(大小为3M),再将该保存的图片发给安卓客户端(保存后为3M),安卓客户端再发送给iOS 客户端(保存后大小为3M)。结论:该压缩只会进行一次
    • 保存图片后,图片的Exif信息丢失,但是Exif信息的大小远小于文件损失的大小。图片物理尺寸没有发生变化
    • 2017/11/10补充:已尝试的保存图片的方法
    // 方法一:<Photos/Photos.h>框架,9.0以上系统支持,保存图片
    [[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
         placeholder = [PHAssetChangeRequest creationRequestForAssetFromImage:image].placeholderForCreatedAsset;
    } error:&error];
    
    // 方法二:将 UIImage 转换为 NSData,再进行保存
    // 该方法保存后的图片会变大很多,有个比较low的方法是进行压缩到正常大小
    // 至于Exif信息,有的人认为泄漏了隐私,有的人坚持要带上,Exif的获取及写入在这里不再赘述
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        [[PHAssetCreationRequest creationRequestForAsset] addResourceWithType:PHAssetResourceTypePhoto data:UIImageJPEGRepresentation(image, 1) options:nil];
    } completionHandler:^( BOOL success, NSError *error ) {
        if ( ! success ) {
            NSLog( @"Error occurred while saving image to photo library: %@", error );
        }
    }];
    
    // 方法三:UIImageWriteToSavedPhotosAlbum 直接保存
    UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
    
    // 方法四:
    - (void)saveImage:(UIImage *)image toAlbum:(NSString *)album completionHandler:(STAlbumSaveHandler)completionHandler;
    
    // 方法五:9.0之前的版本适用方法:将 UIImage 转换为 NSData,再进行保存
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    [library writeImageDataToSavedPhotosAlbum:UIImageJPEGRepresentation(image, 1) metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) {
        NSLog(@"Success at %@", [assetURL path] );
    }] ;
    

    三. 竞品的该功能现状:

    Android、Windows、Mac发送原图,iOS客户端接收

    • 腾讯系(QQ、WeChat)各端发送接收原图都没有问题
    • 钉钉 的图片变小,Exif 信息丢失
    • BearyChat 发送原图的图片变小,Exif 信息丢失
    • 美团旗下的“大象”企业办公通讯软件,发送原图的图片变小,Exif信息丢失(2017/11/10更新,测试完后竟然有点幸灾乐祸哈哈哈)
    • Slack 只支持发送图片,没有发送原图功能

    四. 基于现有情况的分析

    • iOS 系统在保存图片的时候,会对图片进行编解码操作,可能在位图上进行优化
    • 一张图片的存储大小的计算方式:水平像素垂直像素1色黑白或3基色*颜色深度bit数 = MB数,可能是不同系统的基数不同导致
    • 如果要解决这个问题,要先研究一下图片编解码这些很底层的东西,目前来看,性价比很低

    如果您之前踩过类似的坑并找到有效的解决方法,方便的话,劳烦请私信告知(被同事们吐槽好久:没解决问题还写鸡毛博客,我不会告诉他们我是为了第一张图才写的这篇博客,大一的时候看了一眼便再也难以忘怀。。)

    五. 补充点干货

    1. 关于iOS11新增的“.heic”格式图片
    • 什么是“.heic”格式图片?
      • 之前叫“live”图片,打开下图红框中的按钮即可打开该模式,拍照后会截取拍照前后大概两秒的一个片段,与“Gif”图不同的是,该格式还包含了声音(目前只有微博支持“live”格式正常显示)
        image.png
    • 什么样的手机才能拍出“.heic”格式图片?

      • 只有在 iOS11系统下且CPU为A10及其以上(最低也得是iPhone 7),其他情况下拍出来的都是普通“live”图,即在需要转换格式的时候会自动转换为“.jpg/.jpeg”格式
    • 如何判断一张图片是否是“.heic”格式?

      • SDWebImage-NSData+ImageContentType.m 已更新,刚开始遇到这个问题的时候提了个issue,还让我提供对应url。。
    + (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
        if (!data) {
            return SDImageFormatUndefined;
        }
        
        // File signatures table: http://www.garykessler.net/library/file_sigs.html
        uint8_t c;
        [data getBytes:&c length:1];
        switch (c) {
            case 0xFF:
                return SDImageFormatJPEG;
            case 0x89:
                return SDImageFormatPNG;
            case 0x47:
                return SDImageFormatGIF;
            case 0x49:
            case 0x4D:
                return SDImageFormatTIFF;
            case 0x52: {
                if (data.length >= 12) {
                    //RIFF....WEBP
                    NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
                    if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                        return SDImageFormatWebP;
                    }
                }
                break;
            }
            case 0x00: {
                if (data.length >= 12) {
                    //....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
                    NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
                    if ([testString isEqualToString:@"ftypheic"]
                        || [testString isEqualToString:@"ftypheix"]
                        || [testString isEqualToString:@"ftyphevc"]
                        || [testString isEqualToString:@"ftyphevx"]) {
                        return SDImageFormatHEIC;
                    }
                }
                break;
            }
        }
        return SDImageFormatUndefined;
    }
    
    + (nonnull CFStringRef)sd_UTTypeFromSDImageFormat:(SDImageFormat)format {
        CFStringRef UTType;
        switch (format) {
            case SDImageFormatJPEG:
                UTType = kUTTypeJPEG;
                break;
            case SDImageFormatPNG:
                UTType = kUTTypePNG;
                break;
            case SDImageFormatGIF:
                UTType = kUTTypeGIF;
                break;
            case SDImageFormatTIFF:
                UTType = kUTTypeTIFF;
                break;
            case SDImageFormatWebP:
                UTType = kSDUTTypeWebP;
                break;
            case SDImageFormatHEIC:
                UTType = kSDUTTypeHEIC;
                break;
            default:
                // default is kUTTypePNG
                UTType = kUTTypePNG;
                break;
        }
        return UTType;
    }
    
    • 对于“.heic”格式图片我们应该怎么处理?
      • 首先肯定不是服务器去支持这个类型,因为 Windows、Android 是不支持该类型的图片正常显示的,尤其是 Windows 明确表示以后也不会支持。
      • 微信目前的处理方式是转换成了 jpg 格式,因此直接使用 UIImageJPEGRepresentation(originalImage, 0.82); 转换为jpg即可
      • 但是经多次测试后发现,必须设置压缩比为0.82,转换后的大小才尽可能的接近原图大小

    最后,说点正经的,这是一篇每个iOS开发人员都应该了解的文章 iOS11/iPhone X 适配简单,但你的Apple思维适配做好了么?

    再说点不正经的,如果你很愉悦的看完了本文并且学到了知识,那么这篇文章你应该也会喜欢NavigationController已经洗干净了,就等你来

    相关文章

      网友评论

      • ssdfsj:要不试试先把原图下载临时文件夹,然后再用creationRequestForAssetFromImageAtFileURL保存。
        si1ence:临时文件会变大很多,相关将临时文件在压缩到合适比例,但是Exif信息还要额外处理,这个有点low
      • ZhongXi:斯嘉丽,赞!!!
        si1ence:@ZhongXi 哈哈哈,同道中人!
      • sinno:你们保存图片的方法是哪个?如果是UIImageWriteToSavedPhotosAlbum的话确实有这种问题的,可以用PHPhotoLibrary来将NSData保存到相册中。
        sinno:@si1ence 可否把保存的那段代码贴一下呢~:relieved:
        si1ence:@sinno 嗯,这些方法都尝试了
        sinno:PHPhotoLibrary支持9.0以上系统。对更早的系统版本可以使用ALAssetsLibrary来保存图片。
      • 啊哈呵:各种保存图片到相册的方法,都尝试了那些方法,提供出来大家看看讨论分析,大家可以从坑之外找办法呢,省的又去踩坑
        啊哈呵:@si1ence :赞👍
        si1ence:已更新
      • 糊涂0:配图风骚的很啊!
      • 总有bug为难本宫:一天天写个博客都要开车
      • d94098065219:明白了:+1:
        si1ence:@李y 今中午盒饭加俩鸡腿!

      本文标题:关于“发送原图”功能问题的记录

      本文链接:https://www.haomeiwen.com/subject/bcoxuxtx.html