这几天在做iOS上的网络图片的异步加载和缓存。网上查了下,决定用SDWebImage实现。按照网络的教程,走了一遍,没什么问题。但是程序运行起来发现加载起来的图片都变得很模糊,本人有一点平面设计的经验。推测是因为图片太大的造成的细节都是,从而使图片变的模糊。进到程序的缓存文件目录,可以看到下载的图片果真和直接从网页上下载的一样大,即图片没有经过压缩就被缓存起来了。但是我的要求应该是图片应该在保存时被压缩的啊,就像Android开源框架Afinal中的FinalBitmap做的一样。
那么作为一个iOS新手真的不想去改SDWebImage得源码,于是刚开始就一直在google上找,找了不少时间,没找到。无奈之下,只能自己尝试着改了。还好要改动的东西比较简单。只需要在保存图片时将图片压缩一下在保存就好了。工程文件可以在本文最后下载。
先上运行结果:
测试用的两张图都是1920x1080的原图,从左边可以看出位于下方的那张图片已经变得很模糊了。从右边可看出,下面那张原图的大小是2.1M,而上面的那张图片只有34k,说明,缓存文件已经被处理过了。
其实SDWebImage已经做了这方面的考虑,就是那个SDWebImageManager中的SDWebImageManagerDelegate中,
/**
* Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
* NOTE: This method is called from a global queue in order to not to block the main thread.
*
* @param imageManager The current `SDWebImageManager`
* @param image The image to transform
* @param imageURL The url of the image to transform
*
* @return The transformed image object.
*/
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
只可惜前面我每仔细看框架,没发现有这个东西,自己在那边建Category,然后各种试,还好最终还是发现了这个方法。那么现在问题就比较简单了。经过代码追踪(其实就是按照关键字,进行代码的全文查找,哈哈),发现在
- (id<SDWebImageOperation>)downloadWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedWithFinishedBlock)completedBlock;
方法中调用了transformDownloadedImage方法,从transformDownloadedImage的注释可以看到这个回调方法会将下载得到UIImage对象作为参数,然后将处理过后的UIImage对象返回。而downloadWithURL...方法则会先判断URL指向的图片是否已经缓存过了,如果没有缓存过话,就将下载得到的UIImage对象交给
transformDownloadedImage处理一下,然后调用保存数据方法storeImage:imageData:key:toDisk方法进行数据保存。
那么可以拷贝一下SDWebImageManager中的downloadWithURL...方法,然后添加两个参数height和width
/**
* 重写下载图片方法,可以指定要缓存的图片大小(长、宽),同时作用于内存缓存和磁盘缓存
*/
- (id<SDWebImageOperation>)downloadWithURL:(NSURL *)url options:(SDWebImageOptions)options
width:(NSInteger)w height:(NSInteger)h
progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedWithFinishedBlock)completedBlock
{
// Invoking this method without a completedBlock is pointless
NSParameterAssert(completedBlock);
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
if ([url isKindOfClass:NSString.class])
{
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class])
{
url = nil;
}
__block SDWebImageCombinedOperation *operation = SDWebImageCombinedOperation.new;
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
@synchronized(self.failedURLs)
{
isFailedUrl = [self.failedURLs containsObject:url];
}
if (!url || (!(options & SDWebImageRetryFailed) && isFailedUrl))
{
dispatch_main_sync_safe(^
{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES);
});
return operation;
}
@synchronized(self.runningOperations)
{
[self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url];
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType)
{
if (operation.isCancelled)
{
@synchronized(self.runningOperations)
{
[self.runningOperations removeObject:operation];
}
return;
}
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]))
{
if (image && options & SDWebImageRefreshCached)
{
dispatch_main_sync_safe(^
{
// If image was found in the cache bug SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
completedBlock(image, nil, cacheType, YES);
});
}
// download if no image or requested to refresh anyway, and download allowed by delegate
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (image && options & SDWebImageRefreshCached)
{
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
id<SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished)
{
if (weakOperation.isCancelled)
{
dispatch_main_sync_safe(^
{
completedBlock(nil, nil, SDImageCacheTypeNone, finished);
});
}
else if (error)
{
dispatch_main_sync_safe(^
{
completedBlock(nil, error, SDImageCacheTypeNone, finished);
});
if (error.code != NSURLErrorNotConnectedToInternet)
{
@synchronized(self.failedURLs)
{
[self.failedURLs addObject:url];
}
}
}
else
{
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && image && !downloadedImage)
{
// Image refresh hit the NSURLCache cache, do not call the completion block
}
// NOTE: We don't call transformDownloadedImage delegate method on animated images as most transformation code would mangle it
else if (downloadedImage && !downloadedImage.images && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:
width:height:)]) //定义新的回调方法
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
{ //将设定的h和w传递给回调方法...tranformDownloadedImage...,这个回调方法需要在具体的Delegate中实现
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url
width:w height:h];
dispatch_main_sync_safe(^
{
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished);
});
if (transformedImage && finished)
{
NSData *dataToStore = [transformedImage isEqual:downloadedImage] ? data : nil;
[self.imageCache storeImage:transformedImage imageData:dataToStore forKey:key toDisk:cacheOnDisk];
}
});
}
else
{
dispatch_main_sync_safe(^
{
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished);
});
if (downloadedImage && finished)
{
[self.imageCache storeImage:downloadedImage imageData:data forKey:key toDisk:cacheOnDisk];
}
}
}
if (finished)
{
@synchronized(self.runningOperations)
{
[self.runningOperations removeObject:operation];
}
}
}];
operation.cancelBlock = ^{[subOperation cancel];};
}
else if (image)
{
dispatch_main_sync_safe(^
{
completedBlock(image, nil, cacheType, YES);
});
@synchronized(self.runningOperations)
{
[self.runningOperations removeObject:operation];
}
}
else
{
// Image not in cache and download disallowed by delegate
dispatch_main_sync_safe(^
{
completedBlock(nil, nil, SDImageCacheTypeNone, YES);
});
@synchronized(self.runningOperations)
{
[self.runningOperations removeObject:operation];
}
}
}];
return operation;
}
同时拷贝SDWebImageManagerDelegate中的回调方法...transformDownloadedImage...方法,修改并增加参数height和width,在SDWebImageManager.h中的SDWebImageManagerDelegate中有:
//自定义方法,指定要转换的图片的长、宽
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL width:(NSInteger)w height:(NSInteger)h;
最后调用,使ViewController.h实现SDWebImageManagerDelegate,下面是
ViewController.m:
//
// ViewController.m
// MySDWEBIMAGE_0928
//
// Created by wly on 13-9-28.
// Copyright (c) 2013年 wly. All rights reserved.
//
#import "ViewController.h"
#import "SDImageCache.h"
#import "UIImageView+WebCache_HW.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
manager.delegate = self;
//添加图片一,指定缓存大小是原图的1/10,即192x108
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(40, 100, 100, 100)];
imageView.contentMode = UIViewContentModeScaleAspectFill;
[manager downloadWithURL:[NSURL URLWithString:@"http://c.hiphotos.baidu.com/album/w%3D1920%3Bcrop%3D0%2C0%2C1920%2C1080/sign=4e447d5d7acb0a4685228f305953cd47/c995d143ad4bd113314728955bafa40f4afb058a.jpg"]
options:0 width:192 height:108
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
// do something with image
imageView.image = image;
}
}];
//添加图片二,指定缓存大小和原图大小一样,为1920x1080
UIImageView *imageView2 = [[UIImageView alloc]initWithFrame:CGRectMake(40, 300, 100, 100)];
imageView2.contentMode = UIViewContentModeScaleAspectFill;
[manager downloadWithURL:[NSURL URLWithString:@"http://c.hiphotos.baidu.com/album/w%3D1920%3Bcrop%3D0%2C0%2C1920%2C1080/sign=52823b0355e736d158138801a96074a1/10dfa9ec8a136327885cb7ec908fa0ec09fac7b8.jpg"]
options:0 width:1920 height:1080
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
// do something with image
imageView2.image = image;
}
}];
[self.view addSubview:imageView];
[self.view addSubview:imageView2];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(UIImage*)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL width:(NSInteger)w height:(NSInteger)h {
//缩放图片
// Create a graphics image context
UIGraphicsBeginImageContext(CGSizeMake(w, h));
// Tell the old image to draw in this new context, with the desired
// new size
[image drawInRect:CGRectMake(0,0,w, h)];
// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
// End the context
UIGraphicsEndImageContext();
return newImage;
}
@end
再是ViewController.h
//
// ViewController.h
// MySDWEBIMAGE_0928
//
// Created by wly on 13-9-28.
// Copyright (c) 2013年 wly. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "SDWebImageManager.h"
@interface ViewController : UIViewController <SDWebImageManagerDelegate>
@end
最后,在调试过程中遇到一个问题,就是传递CGFloat参数时,得到了一个很小的负数,即出现了奇怪的问题。有兴趣的朋友可看一下stackoverflow上的一篇文章:http://stackoverflow.com/questions/3018978/cgfloat-argument-type-incorrectly-traces-out-with-zero-value-why
转载请保留出处:http://blog.csdn.net/u011638883/article/details/12160963
工程文件:http://download.csdn.net/detail/u011638883/6338379