iOS支持webp格式图片

作者: mumumayday | 来源:发表于2017-08-01 19:54 被阅读767次

    1.配置环境

    1. 工程引入SDWebImage开源库;
    2. 引入WebP.framework (demo中可以获得)
    3. 让SDWebImage支持WebP,设置如下Build Settings -- Preprocessor Macros , add SD_WEBP=1

    2.用SDWebImage加载webp图片

    //视图加载webp图片
    UIView *webpView = [[UIView alloc] initWithFrame:CGRectMake(10, 80, 320, 100)];
    webpView.backgroundColor = [UIColor yellowColor];
    [self.view addSubview:webpView];
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://mmbiz.qpic.cn/mmbiz_jpg/AZQZ9KUtamupibEQMmFDLqqsU7RLEvH5h5sPcyZEvhv6tQ5y3WAYKYibeQfnOtQulul5QHFHpL9b0icCKliajWFe2A/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1"]];
    UIImageView *wpimage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    #ifdef SD_WEBP
    [wpimage setImage:[UIImage sd_imageWithWebPData:data]];
    #endif
    [webpView addSubview:wpimage];
    

    3.Webview里加载webp图片

    有两种方案:

    1,和js协作。
    2,自定义NSURLProtocal

    和JS协作

    webView 等网页上如果用的是 webP 图片,iOS 直接解析的话,会显示?图片.

    ?.png

    实现方法:

    1. 在网页加载完成的回调里,截取到 HTML 及内部的 JS ,获取全部需要转码的 webP 格式图片下载地址。(调用 JS 预先准备好的方法,或者客户端一个一个的遍历也行).
    2. 在客户端本地开启线程下载图片,下载完成后,将图片转码由 webP—> png—>Base64(直接用 png/jpg 的话,不起没用)
    3. 将 Base64及原图片下载地址,对应调用 JS 准备好的方法进行替换
    4. 将下载后的图片进行缓存,并进行管理

    注意点:

    1. 图片在最终显示成功前会显示成?,此处为了用户体验应该采用占位图片
    2. 图片显示成功前应该保持网页布局不调整,需要由 JS 预先设置好布局
    3. 图片在本地的缓存需要管理

    例如,HTML的代码如下:

    <head>
            <meta charset="utf-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
            <title>webp</title>
            <script type="text/javascript">
                 var YongChe = {}
                 YongChe.getAllWebPImg = function () {
                    var images = document.getElementsByTagName('img')
                    var srcList = []
                    var patt1 = new RegExp("\.webp$")
                    for (var i = 0; i < images.length; i++) {
                         if (patt1.test(images[i].src)) {
                             srcList.push(images[i].src)
                         }
                     }
                    return JSON.stringify(srcList)
                  }
    
                  YongChe.replaceWebPImg = function (src, localPath) {
                      elementList = document.querySelectorAll('img[src="' + src + '"]')
                      for (element in elementList) {
                           elementList[element].src = localPath
                      }
                   }
    </script>
    </head>
    <body>
            <img src="http://testing.yongche.org/images/150X1501.webp” width = 50 ,height = 20>
            ![](http://testing.yongche.org/images/150X1501.webp)
            <script type="text/javascript">
                console.log(YongChe.getAllWebPImg());
                YongChe.replaceWebPImg('http://testing.yongche.org/images/logo.png','http://testing.yongche.org/images/150X1501.webp')
            </script>
    </body>
    

    客户端代码如下:

    - (void)webViewDidFinishLoad:(UIWebView *)webView{
        //---------------------------------测试-----------------------------------
        //获取`HTML`代码
        NSString *lJs = @"document.documentElement.innerHTML";
        NSString *str = [webView stringByEvaluatingJavaScriptFromString:lJs];
        //执行约定好的方法,获取需要下载的 webp 图片
        NSString *imgs = [webView stringByEvaluatingJavaScriptFromString:@"YongChe.getAllWebPImg();"];
        NSArray *array = [NSJSONSerialization JSONObjectWithData:[imgs dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
        
        _webpImageUrlDic = [NSMutableDictionary dictionaryWithCapacity:3];
        for (NSString *imgUrl in array) {
            //检查本地图片缓存
            NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:[NSURL URLWithString:imgUrl]];
            NSString *localPath = [[SDImageCache sharedImageCache] defaultCachePathForKey:key];
            NSString *newSrc = imgUrl;
            BOOL localExsit = [[NSFileManager defaultManager] fileExistsAtPath:localPath];
            if (localExsit) {
                newSrc = [NSString stringWithFormat:@"file://%@", localPath];
            }
            //存储�webp图片和原图片,如果newSrc和webp相同则说明本地没有缓存图片
            [_webpImageUrlDic setObject:imgUrl forKey:newSrc];
        }
        
        //处理webp格式加载
        [_webpImageUrlDic enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            if([obj isEqualToString:key]){//说明这图没有缓存,还需要下载
                [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:obj] options:0 progress:nil completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                    if (image&&finished) {
                        NSString *js;
                        NSRange range = [[obj lowercaseString] rangeOfString:@".gif"];//检查是否是gif
                        BOOL isGif = (range.location != NSNotFound);
                        if (!isGif) {
                            [[SDImageCache sharedImageCache] storeImage:image forKey:obj];
                            NSString *base64 = [UIImageJPEGRepresentation(image,1) base64EncodedStringWithOptions:0];
                            js = [NSString stringWithFormat:@"YongChe.replaceWebPImg('%@','data:image/png;base64,%@')",obj,base64];
                        }else{//gif的图片如果直接存储,会变成jpg从而失去动画,因此要特殊处理
                            [[SDImageCache sharedImageCache] storeImage:image recalculateFromImage:false imageData:data forKey:obj toDisk:true];
                            NSString *base64 = [data base64EncodedStringWithOptions:0];
                            js = [NSString stringWithFormat:@"YongChe.replaceWebPImg('%@','data:image/gif;base64,%@')",obj,base64];
                        }
                        dispatch_async(dispatch_get_main_queue(), ^{
                            //更新UI操作
                            [self.webv stringByEvaluatingJavaScriptFromString:js];
                        });
                    }
                }];
            } else {
                //缓存中存在,那么直接加载吧
                NSString *js;
                NSRange range = [[obj lowercaseString] rangeOfString:@".gif"];//检查是否是gif
                NSData* data = [NSData dataWithContentsOfFile:[key stringByReplacingOccurrencesOfString:@"file://" withString:@""]];
                NSString *base64 = [data base64EncodedStringWithOptions:0];
                BOOL isGif = (range.location != NSNotFound);
                if (!isGif) {
                    js = [NSString stringWithFormat:@"YongChe.replaceWebPImg('%@','data:image/png;base64,%@')",obj,base64];
                }else{
                    js = [NSString stringWithFormat:@"YongChe.replaceWebPImg('%@','data:image/gif;base64,%@')",obj,base64];
                }
                dispatch_async(dispatch_get_main_queue(), ^{
                    //更新UI操作
                    [self.webv stringByEvaluatingJavaScriptFromString:js];
                });
            }
        }];
    }
    

    效果如下:


    和js协作.png

    自定义NSURLProtocal

    自定义CYCustomURLProtocal 继承自NSURLProtocal

    #import <Foundation/Foundation.h>
    
    @interface CYCustomURLProtocal : NSURLProtocol
    
    @end
    

    NSURLSession

    #import "CYCustomURLProtocal.h"
    #import "UIImage+WebP.h"
    
    static NSString *URLProtocolHandledKey = @"URLHasHandle";
    
    @interface CYCustomURLProtocal()<NSURLSessionDelegate,NSURLSessionDataDelegate>
    
    @property (nonatomic,strong) NSURLSession *session;
    
    @end
    
    @implementation CYCustomURLProtocal
    
    #pragma mark 初始化请求
    
    + (BOOL)canInitWithRequest:(NSURLRequest *)request
    {
        //只处理http和https请求
        NSString *scheme = [[request URL] scheme];
        NSString *extension = [[request URL] pathExtension];
        if (([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame) && ([extension caseInsensitiveCompare:@"webp"] == NSOrderedSame)) {
            //看看是否已经处理过了,防止无限循环
            if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
                return NO;
            }
            return YES;
        }
        return NO;
    }
    
    + (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
        return request;
    }
    
    #pragma mark 通信协议内容实现
    
    - (void)startLoading
    {
        NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
        //标示改request已经处理过了,防止无限循环
        [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
        
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue currentQueue]];
        
        //判断是否缓存
        NSString *name = [NSString stringWithFormat:@"%@.jpg", self.request.URL.absoluteString];
        NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:name];
        if([[NSFileManager defaultManager] fileExistsAtPath:path]) {
            mutableReqeust.URL = [NSURL fileURLWithPath:path];
        }
        [[self.session dataTaskWithRequest:mutableReqeust] resume];
    }
    
    - (void)stopLoading
    {
        [self.session invalidateAndCancel];
    }
    
    #pragma mark dataDelegate
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
    {
        [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
        
        if (completionHandler) {
            completionHandler(NSURLSessionResponseAllow);
        }
        
    }
    
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
        NSData *transData = data;
        NSString *extension = [dataTask.currentRequest.URL pathExtension];
        if ([extension caseInsensitiveCompare:@"webp"] == NSOrderedSame) {
            UIImage *imgData = [UIImage sd_imageWithWebPData:data];
            transData = UIImageJPEGRepresentation(imgData, 1.0f);
            //写入缓存
            NSString *name = [NSString stringWithFormat:@"%@.jpg", dataTask.currentRequest.URL.absoluteString];
            NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:name];
            if (transData != nil) {
                BOOL yy = [transData writeToFile:path atomically:YES];
                if (yy) {
                    NSLog(@"写入成功");
                }
            }
        }
        [self.client URLProtocol:self didLoadData:transData];
    }
    
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error{
        
        if (error) {
            
            [self.client URLProtocol:self didFailWithError:error];
        }else{
            
            [self.client URLProtocolDidFinishLoading:self];
        }
        
    }
    
    @end
    

    记得再delegate里注册该协议:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        //注册protocol
        [NSURLProtocol registerClass:[CYCustomURLProtocal class]];
        return YES;
    }
    

    有两个注意点:

    1. NSURLProtocol 会影响 App 内所有请求,稍有遗漏的话后果比较严重,要慎重,所以最好是限定只有webp格式的图片才走这个自定义协议。
    2. 不论 NSURLConnection 还是 NSURLSession,尽量把 URLProtocol didFailWithError , URLProtocol DidFinishLoading , URLProtocol didLoadData , URLProtocol didReceiveResponse 几个方法写全,避免网络请求产生不可预知的 BUG

    4.最后

    仅以此记录一下调研结果。
    demo附上:https://github.com/sharon5mayday5/iOS_WebP.git

    相关文章

      网友评论

        本文标题:iOS支持webp格式图片

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