前言
在项目中遇到了加载高清大图的场景,处理不好占用大量的内存,造成性能问题,影响用户体验。
如何去解决加载高清大图这个难点呢?先看看iOS中图片的加载流程
iOS中图片的加载流程
加载图片数据(尚未解码)
解码图片数据
缓存图片数据
渲染图片数据
性能问题一般是发生在 解码图片
这个步骤上,SDWebImage
解码图片解码的代码是在 SDWebImageCoderHelper
中的 decodedImageWithImage
函数中,并 缓存起来
,以保证 tableViews/collectionViews
交互更加流畅,如果是 加载高分辨率图片
的话,会适得其反,有可能造成 内存占用过高
和 内存峰值过高
的问题
优化的思路
1 2
| [[SDImageCache sharedImageCache] setShouldDecompressImages:NO]; [[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];
|
当然,你也可以设置 SDWebImage
的其他参数,比如是 否缓存到内存以及内存缓存最高限制
等,来保证内存安全:
代码实现
- APPDelegate中设置相关参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| - (void)imageLoadingSettings { // 最大缓存周期 一周 [SDImageCache sharedImageCache].config.maxCacheAge = 3600 * 24 * 7; // 最大内存限制 20M [SDImageCache sharedImageCache].maxMemoryCost = 1024 * 1024 * 20; // 不缓存到内存 [SDImageCache sharedImageCache].config.shouldCacheImagesInMemory = NO; // 不解压图片 [SDImageCache sharedImageCache].config.shouldDecompressImages = NO; // 不解压图片 [SDWebImageDownloader sharedDownloader].shouldDecompressImages = NO; // iOS就不会把整个文件全部读取的内存了,而是将文件映射到进程的 地址空间 中,这么做并不会占用实际内存。这样就可以解决内存占用过高的问题。 [SDImageCache sharedImageCache].config.diskCacheReadingOptions = NSDataReadingMappedIfSafe;
}
|
- ViewController中实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| // Decompress 解码 static BOOL SDImageCacheOldShouldDecompressImages = YES; static BOOL SDImagedownloderOldShouldDecompressImages = YES; - (void)viewDidLoad { [super viewDidLoad]; // 设置图片缓存方式 防止图片过大崩溃的情况 SDImageCache *canche = [SDImageCache sharedImageCache]; SDImageCacheOldShouldDecompressImages = canche.config.shouldDecompressImages; canche.config.shouldDecompressImages = NO; SDWebImageDownloader *downloder = [SDWebImageDownloader sharedDownloader]; SDImagedownloderOldShouldDecompressImages = downloder.shouldDecompressImages; downloder.shouldDecompressImages = NO; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. [[SDWebImageManager sharedManager].imageCache clearMemory]; [[SDWebImageManager sharedManager].imageCache clearDiskOnCompletion:nil]; } - (void)dealloc { [[SDImageCache sharedImageCache] clearMemory]; SDImageCache *canche = [SDImageCache sharedImageCache]; canche.config.shouldDecompressImages = SDImageCacheOldShouldDecompressImages; SDWebImageDownloader *downloder = [SDWebImageDownloader sharedDownloader]; downloder.shouldDecompressImages = SDImagedownloderOldShouldDecompressImages; }
|
加载大图的解决方案
- 显示大图时,传统加载方法会造成内存暴涨,造成主线程阻塞,甚至造成程序crash。可以使用
CATiledLayer
显示,CATiledLayer
类似瓦片视图,他将需要绘制的内容分割成许多小块,然后异步绘制相应的小块,这样就节约了处理时间和内存。
它不需要你自己计算分块显示的区域,它自己直接提供,你只需要根据这个区域计算图片相应区域,然后画图就可以了。
它是在其他线程画图,不会因为阻塞主线程而导致卡顿。
它自己实现了只在屏幕区域显示图片,屏幕区域外不会显示,而且当移动图片时,它会自动绘制之前未绘制的区域,当你缩放时它也会自动重绘。
CATiledLayer
是为载入大图造成的性能问题提供的一个解决方案,具体如何划分小块和缩放时的加载策略,与CATiledLayer三个重要属性有关:
levelsOfDetail:levelsOfDetail 指的是该图层缓存的缩小 LOD 数目,默认值为1,每进一级会对前一级分辨率的一半进行缓存,图层的 levelsOfDetail
最大值,对应至少一个像素点。
levelsOfDetailBias:levelsOfDetailBias 指的是该图层缓存的放大LODB数目,它是layer的放大级别重绘设置,默认为0,即不会额外缓存放大层次,每进一级会对前一级两倍分辨率进行缓存。
tileSIze:(默认是256x256)tiledSize是layer划分视图区域最大尺寸,主要是影响layer的切片数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| @interface ViewController ()<CALayerDelegate> // 构建一个容器来展示大图,保证其contenSize与图片尺寸大小一致 @property (nonatomic, weak) IBOutlet UIScrollView *scrollView; @end
@implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; // 创建图层,并设置属性信息 CATiledLayer *tileLayer = [CATiledLayer layer]; CGFloat scale = UIScreen.mainScreen.scale; // 确保scale比例一致 tileLayer.frame = CGRectMake(0, 0, 3972/scale,15718/scale);// 图片像素 tileLayer.delegate = self; tileLayer.tileSize = CGSizeMake(256/scale, 256/scale); // 每个瓷砖块的大小 [self.scrollView.layer addSublayer:tileLayer]; self.scrollView.contentSize = tileLayer.frame.size; // 刷新当前屏幕Rect [tileLayer setNeedsDisplay]; }
// 当滑动到不同区域时会调用此方法 - (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx { // 确定坐标信息 CGRect bounds = CGContextGetClipBoundingBox(ctx); NSInteger x = floor(bounds.origin.x / layer.tileSize.width); NSInteger y = floor(bounds.origin.y / layer.tileSize.height); // 加载小图 NSString *imageName = [NSString stringWithFormat: @"zz_%02i_%02i", x, y]; NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"]; UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath]; // 在TiledLayer上绘制图片 UIGraphicsPushContext(ctx); [tileImage drawInRect:bounds]; UIGraphicsPopContext(); }
|