iOS源码解析—SDWebImage(SDWebImageDownloader)

概述

本篇分析一下SDWebImage中负责下载图片数据的相关代码,SDWebImageDownloader和SDWebImageDownloaderOperation。

SDWebImageDownloader

SDWebImageDownloader是管理下载图片数据的类,初始化方法代码注释如下:

- (id)init {
    if ((self = [super init])) {
        _operationClass = [SDWebImageDownloaderOperation class];
        _shouldDecompressImages = YES; //是否解压图片
        _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
        _downloadQueue = [NSOperationQueue new]; //下载的队列
        _downloadQueue.maxConcurrentOperationCount = 6;
        _URLCallbacks = [NSMutableDictionary new]; //请求回调dic
#ifdef SD_WEBP
        _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy]; //请求webp图片数据,报文头部accept多包含image/webp字段
#else
        _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT); //
        _downloadTimeout = 15.0; //请求图片数据超时超时时间
        //初始化NSURLSessionConfiguration对象
        NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
        sessionConfig.timeoutIntervalForRequest = _downloadTimeout; //设置超时时间

        self.session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                     delegate:self
                                                delegateQueue:nil]; //创建session
    }
    return self;
}

该方法初始化了一些参数用于网络请求,SDWebImageDownloader是通过NSURLSession的方式请求数据。同时提供了一些方法用于外部设置。代码注释如下:

//设置请求报文头部
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
    if (value) {
        self.HTTPHeaders[field] = value;
    } else {
        [self.HTTPHeaders removeSafeObjectForKey:field];
    }
}
//获取设置的报文头部
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    return self.HTTPHeaders[field];
}
//设置下载队列的最大并发个数
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
    _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}
//获取下载队列的当前operation个数
- (NSUInteger)currentDownloadCount {
    return _downloadQueue.operationCount;
}
//获取下载队列的最大并发个数
- (NSInteger)maxConcurrentDownloads {
    return _downloadQueue.maxConcurrentOperationCount;
}

SDWebImageDownloader最主要的方法是downloadImageWithURL:options:progress:completed:方法,调用该方法开始下载数据。下面主要分析一下具体执行步骤:

  1. 首先调用addProgressCallback:completedBlock:forURL:createCallback:方法为当前网络请求关联回调block,包括请求进度的回调和请求结束的回调,主要代码如下:

          dispatch_barrier_sync(self.barrierQueue, ^{
                  BOOL first = NO;
                  if (!self.URLCallbacks[url]) {
                      self.URLCallbacks[url] = [NSMutableArray new]; //url对应block数组
                      first = YES;
                  }
                  NSMutableArray *callbacksForURL = self.URLCallbacks[url];
                  NSMutableDictionary *callbacks = [NSMutableDictionary new];
                 //设置当前请求进度progress回调block和请求完成回调block
                  if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
                  if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
                  [callbacksForURL addObject:callbacks]; //添加callbacks
                  self.URLCallbacks[url] = callbacksForURL;
                  if (first) {
                      createCallback(); //执行createCallback
                  }
              });
    
      该方法允许设置一组回调block,key是请求的url,数组中每个元素又是一个dictionary,包含请求进度progress回调block和请求完成回调block,最后执行createCallback。
    
  2. downloadImageWithURL方法接着在createCallback中构建请求报文,代码如下:

    //创建request对象
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
    //设置参数
    request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
    request.HTTPShouldUsePipelining = YES;
    

    首先创建request对象,根据option值来决定本次网络请求的缓存策略,option是SDWebImageDownloaderOptions类型的枚举值,通过|的方式多选。SDWebImage默认忽略NSURLCache缓存机制,即request的cachePolicy是NSURLRequestReloadIgnoringLocalCacheData,而是用自定义的SDImageCache来缓存数据,上一篇文章分析了SDImageCache,如果option设置了SDWebImageDownloaderUseNSURLCache,则启用NSURLCache缓存机制,并且设置cachePolicy为NSURLRequestUseProtocolCachePolicy,即通过服务端响应报文的缓存策略来决定本地NSURLCache是否缓存数据。另外还支持pipelining和处理cookies。

  3. downloadImageWithURL方法接着创建一个SDWebImageDownloaderOperation类型的NSOperation对象,将本次网络请求作为一个operation执行。代码注释如下:

    //创建一个operation对象
    operation = [[wself.operationClass alloc] initWithRequest:request
                                                     inSession:self.session
                                                              options:options                                                 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
     SDWebImageDownloader *sself = wself;
     if (!sself) return;
     __block NSArray *callbacksForURL;
     dispatch_sync(sself.barrierQueue, ^{
         callbacksForURL = [sself.URLCallbacks[url] copy]; //取出callback数组
     });
     for (NSDictionary *callbacks in callbacksForURL) { //遍历callback数组
         dispatch_async(dispatch_get_main_queue(), ^{
             SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey]; //取出progress的callback
             if (callback) callback(receivedSize, expectedSize); //执行block
         });
     }
    }
    completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
     SDWebImageDownloader *sself = wself;
     if (!sself) return;
     __block NSArray *callbacksForURL;
     dispatch_barrier_sync(sself.barrierQueue, ^{
         callbacksForURL = [sself.URLCallbacks[url] copy];
         if (finished) {
             [sself.URLCallbacks removeObjectForKey:url]; //删除回调
         }
     });
     for (NSDictionary *callbacks in callbacksForURL) { //遍历callback数组
         SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
         if (callback) callback(image, data, error, finished); //执行block
     }
    }
    cancelled:^{
     SDWebImageDownloader *sself = wself;
     if (!sself) return;
     dispatch_barrier_async(sself.barrierQueue, ^{
         [sself.URLCallbacks removeObjectForKey:url]; //删除url
     });
    }];
    operation.shouldDecompressImages = wself.shouldDecompressImages; //图片数据下载完成后是否要解压
    

    请求图片数据过程中触发progress回调,receivedSize是当前接收数据的大小,expectedSize是数据总大小,请求完成触发completed回调,删除url对应的相关callback,并且执行SDWebImageDownloaderCompletedBlock。当operation被cancel,触发cancelled回调,删除url对应的相关callback。

  4. 最后将operation加入downloadQueue队列中,开始执行当前网络请求任务。同时设置了operation的优先级和依赖关系。

由于SDWebImageDownloader将self设置为urlsession的delegate,当发起网络请求时,触发urlsession的回调方法。SDWebImageDownloader实现了urlsession的相关代理方法,但是逻辑交给当前dataTask对应的SDWebImageDownloaderOperation对象来处理,通过operationWithTask找到dataTask对应的operation。

- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
    SDWebImageDownloaderOperation *returnOperation = nil;
    for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations)     {
        //遍历downloadQueue中当前所有operation,匹配task
        if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
            returnOperation = operation;
            break;
        }
    }
    return returnOperation; //返回
}

SDWebImageDownloaderOperation

SDWebImageDownloaderOperation是继承NSOperation的类,对应一次网络请求任务,首先通过初始化方法初始化一些参数设置,参数值有SDWebImageDownloader传入。

start方法

当operation加入downloadQueue中,触发start方法,相关代码注释如下:

- (void)start {
    @synchronized (self) { //加锁,多线程数据同步
        if (self.isCancelled) { //如果operation被取消
            self.finished = YES; //则operation结束
            [self reset];
            return;
        }

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        //如果当前operation标记为在app进入后台时继续下载图片数据
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            //app进入后允许执行一段时间,超过期限执行ExpirationHandler
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;
                if (sself) {
                    [sself cancel];
                    [app endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif
        NSURLSession *session = self.unownedSession;
        if (!self.unownedSession) { //如果没有在初始化方法设置unownedSession,则创建一个urlsession对象
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
            session = self.ownedSession;
        }
        //通过session创建一个dataTask
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES; //当前operation标记为执行状态
        self.thread = [NSThread currentThread]; //获取当前的线程
    }
    [self.dataTask resume]; //开始执行dataTask

    if (self.dataTask) {
        if (self.progressBlock) { //当前进度为0
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
    }
    else {
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }
    ...
}

该方法主要开启一个NSURLSessionTask,并且调用resume方法执行task,开始进行网络请求。

控制operation生命周期

提供了setFinished:方法,setExecuting:方法控制operation的状态,实现是修改operation的状态值,然后手动抛通知给外部。同时提供了cancel和cancelInternal方法取消当前operation。代码注释如下:

- (void)cancelInternal {
    if (self.isFinished) return;
    [super cancel]; //取消operation
    if (self.cancelBlock) self.cancelBlock();
    //取消当前网络请求
    if (self.dataTask) {
        [self.dataTask cancel];
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
        });
        //operation状态为结束
        if (self.isExecuting) self.executing = NO;
        if (!self.isFinished) self.finished = YES;
    }
    [self reset]; //相关属性置为nil
}
NSURLSessionDataDelegate

网络请求执行的过程中,触发NSURLSession的相关代理方法,SDWebImageDownloader是delegate,实现了相关代理方法,会调用operation来执行。operation实现了以下4个方法:

  1. -(void)URLSession:dataTask:didReceiveResponse:completionHandler:方法

    当请求建立连接时,服务器发送响应给客户端,触发该方法,代码注释如下:
    
        - (void)URLSession:(NSURLSession *)session
                  dataTask:(NSURLSessionDataTask *)dataTask
        didReceiveResponse:(NSURLResponse *)response
         completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
            //根据服务端响应的statusCode执行不同逻辑
            if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) { //连接成功
                NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
                self.expectedSize = expected;
                if (self.progressBlock) {
                    self.progressBlock(0, expected);
                }
                //创建imageData接收数据
                self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
                self.response = response;
                dispatch_async(dispatch_get_main_queue(), ^{ //抛通知
                    [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
                });
            }
            else {
                NSUInteger code = [((NSHTTPURLResponse *)response) statusCode];
                if (code == 304) {
                    [self cancelInternal]; //304表示服务端图片没有更新,结束operation,用缓存中的图片数据
                } else {
                    [self.dataTask cancel];//请求发生错误,取消本次请求,
                }
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
                });
                
                if (self.completedBlock) {
                    self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
                }
                [self done]; //结束operation
            }
            if (completionHandler) {
                completionHandler(NSURLSessionResponseAllow);
            }
        }
    

    该方法发生在客户端收到服务端的响应,但是还没开始传输数据的时候,如果相应报文的statusCode小于400且不是304,则说明连接正常,则创建imageData结束数据。如果statusCode是304,说明网络请求开启缓存功能,且客户端的请求报文中带有上次缓存到本地的lastModified和ETag信息,服务端在对比本地资源和报文中的字段,发现资源没有修改后,返回304,且不返回响应的报文body数据。具体可以参考这篇文章。这种情况下不创建imageData接收数据,直接取消dataTask,结束operation,即使用缓存中的图片数据。如果statusCode不是304,则说明请求失败,取消dataTask,最后在done方法中结束operation。

  2. -(void)URLSession: dataTask: didReceiveData:方法

    当开始下载数据时,触发该方法,如果数据比较多,会不断触发,代码注释如下:

    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
        [self.imageData appendData:data]; //1.拼接数据
         if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
            //当前已经下载的数据大小
            const NSInteger totalSize = self.imageData.length;
            CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
            if (width + height == 0) {
                CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
                if (properties) {
                    NSInteger orientationValue = -1;
                    CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight); //图像高度
                    if (val) CFNumberGetValue(val, kCFNumberLongType, &height);
                    val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth); //图像宽度
                    if (val) CFNumberGetValue(val, kCFNumberLongType, &width);
                    val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation); //图像方向
                    if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
                    CFRelease(properties);
                    orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)]; //图片方向
                }
            }
            //图像宽度和高度不为0,还没接受完数据
            if (width + height > 0 && totalSize < self.expectedSize) {
                CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
    
    #ifdef TARGET_OS_IPHONE
                if (partialImageRef) {
                    const size_t partialHeight = CGImageGetHeight(partialImageRef);
                    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
                     //创建context
                    CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
                    CGColorSpaceRelease(colorSpace);
                    if (bmContext) {
                         //画位图
                        CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
                        CGImageRelease(partialImageRef);
                         //取出位图数据
                        partialImageRef = CGBitmapContextCreateImage(bmContext);
                        CGContextRelease(bmContext);
                    }
                    else {
                        CGImageRelease(partialImageRef);
                        partialImageRef = nil;
                    }
                }
    #endif
    
                if (partialImageRef) {
                     //创建image对象
                    UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
                    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                     //缩放图片
                    UIImage *scaledImage = [self scaledImageForKey:key image:image];
                     //需要解码
                    if (self.shouldDecompressImages) {
                        image = [UIImage decodedImageWithImage:scaledImage];
                    }
                    else {
                        image = scaledImage;
                    }
                    CGImageRelease(partialImageRef);
                    dispatch_main_sync_safe(^{
                        if (self.completedBlock) {
                            self.completedBlock(image, nil, nil, NO);
                        }
                    });
                }
            }
            CFRelease(imageSource);
        }
        if (self.progressBlock) {
            self.progressBlock(self.imageData.length, self.expectedSize);
        }
    }
    

    首先将拼接数据,如果option包含SDWebImageDownloaderProgressiveDownload,即支持边下载边展示图片,首先获取图片的宽、高、方向等信息,然后创建上下文对象bmContext,并调用CGContextDrawImage方法绘制图像,最后生成UIImage对象,通过completedBlock返回。

  3. -(void)URLSession: dataTask: willCacheResponse: completionHandler:方法

    当服务器缓存数据到本地时,触发该方法,代码注释如下:

    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
     willCacheResponse:(NSCachedURLResponse *)proposedResponse
     completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
        responseFromCached = NO; //不是从缓存中取数据
        NSCachedURLResponse *cachedResponse = proposedResponse;
        if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
            cachedResponse = nil; //不缓存数据
        }
        if (completionHandler) {
            completionHandler(cachedResponse);
        }
    }
    

    首先将responseFromCached置为NO,说明本次数据不是200 from cache,而是从服务器下载的,然后判断cachePolicy如果是NSURLRequestReloadIgnoringLocalCacheData,说明采用的缓存策略是忽略本地NSURLCache缓存,这个时候不将数据存入本地。上文分析到SDWebImage默认忽略NSURLCache缓存,即request的cachePolicy是NSURLRequestReloadIgnoringLocalCacheData,所以该方法不将cachedResponse存入本地。(勘误:经过实验,无论是200 from cache还是200,都会触发NSURLSession的该方法,而NSURLConnection不会触发)。

  4. -(void)URLSession: task: didCompleteWithError:方法

    当数据下载完成时,触发该方法,代码注释如下:

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
        @synchronized(self) {
            self.thread = nil;
            self.dataTask = nil;
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
                if (!error) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
                }
            });
        }
        
        if (error) {
            if (self.completedBlock) {
                self.completedBlock(nil, nil, error, YES);
            }
        } else {
            SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
            
            if (completionBlock) {
                if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached && [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]) { //如果缓存中存在数据,且没有更新缓存,则返回nil,即图片数据不更新
                    completionBlock(nil, nil, nil, YES);
                } else if (self.imageData) { //存在数据,处理数据
                    UIImage *image = [UIImage sd_imageWithData:self.imageData];
                    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                    image = [self scaledImageForKey:key image:image];
                    
                    // GIF图不解压
                    if (!image.images) {
                        if (self.shouldDecompressImages) {
                            image = [UIImage decodedImageWithImage:image];//解压图片
                        }
                    }
                    if (CGSizeEqualToSize(image.size, CGSizeZero)) {
                        completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
                    }
                    else {
                        completionBlock(image, self.imageData, nil, YES); //返回图片
                    }
                } else {
                    completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
                }
            }
        }
        self.completionBlock = nil;
        [self done]; //完成本次operation
    }
    

    该方法首先判断缓存策略,如果设置了option是SDWebImageRefreshCached(参考SDWebImageManager中的代码),则downloadOption是SDWebImageDownloaderIgnoreCachedResponse和SDWebImageDownloaderUseNSURLCache,说明使用NSURLCache的缓存机制来决定图片的更新,通过服务器的cache-control字段来控制,具体情况分析如下:

    一、第一次下载图片:服务器cache-control指定缓存时间,会将response存入NSURLCache,同时会进入else if(self.imageData)中,将数据转成图片,并调用completionBlock()回调,将图片显示出来,并存入SDWebImageCache中。

    二、第二次下载相同url的图片,如果本地缓存未过期,即NSURLCache中存在缓存数据,responseFromCached=YES,同时由于设置了SDWebImageDownloaderIgnoreCachedResponse选项,则completionBlock回调nil给外层。如果本地缓存过期则从服务端重新下载数据,responseFromCached=NO,进入else if(self.imageData)中,和(1)一样处理。

    这种机制可以通过实现相同url图片的更新,而不是SDWebImage默认的机制,一个url对应一张图片,如果下载到本地(SDWebImageCache中存储),则从缓存中取,不再下载。

    但是让我疑惑的是SD实现NSURLSession的-(void)URLSession: dataTask: willCacheResponse: completionHandler:方法,每次都会触发,无论数据是否是从服务器下载还是缓存中取,导致responseFromCached=NO,每次都会进入else if (self.imageData)这个逻辑分支。之前老的SD版本基于NSURLConnection,如果是200 from cache,不会触发类似的willCacheResponse方法。这里不太清楚作者的用意。

  5. -(void)URLSession: task:didReceiveChallenge: completionHandler:方法

    当发送HTTPS的url时,触发该方法用于校验服务端下发的证书。具体不进行分析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容