(转载)基于LBS地图的开发,知足地图上有头像的需求

最近作的项目主要是LBS这块 主打成员定位功能 咱们的UI设计是这样的git

pic_001.png

乍一看上去是挺好挺美观的 不一样的人会显示不一样的头像 但是当人扎堆的时候 问题就来了github

pic_002.png

当人多的时候(例如上图所示) 地图滑动起来就能感受到明显顿卡 那种不流畅感能折磨死人 因此 天然咱们要解决这个问题(等等 先不要吐槽为何不用地图聚合 由于这已是地图放到最大了 聚合不适合此次的问题讨论)缓存

分析性能优化

首先看下我是怎么实现这个annotationView的 因为这个annotationsView是异形的(也就是没法经过设置圆角直接获得) 并且里面的图片还因用户而异 因此解决方案就是使用layer.mask来进行遮罩 代码以下服务器

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
44
45
46
@implementation MMAnnotationView
- (instancetype)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
     self = [ super  initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
     if  ( self )
     {
         self.frame = CGRectMake(0, 0, TRACK_ANNOTATION_SIZE.width, TRACK_ANNOTATION_SIZE.height);
         self.centerOffset = CGPointMake(0, -(TRACK_ANNOTATION_SIZE.height-3)/2);
         self.canShowCallout = NO;
         self.avatarView = [[UIImageView alloc] initWithFrame:self.bounds];
         [self addSubview:self.avatarView];
         self.avatarView.contentMode = UIViewContentModeScaleAspectFill;
         CAShapeLayer *shapelayer = [CAShapeLayer layer];
         shapelayer.frame = self.bounds;
         shapelayer.path = self.framePath.CGPath;
         self.avatarView.layer.mask = shapelayer;
         self.layer.shadowPath = self.framePath.CGPath;
         self.layer.shadowRadius = 1.0f;
         self.layer.shadowColor = [UIColor colorWithHex:0x666666FF].CGColor;
         self.layer.shadowOpacity = 1.0f;
         self.layer.shadowOffset = CGSizeMake(0, 0);
         self.layer.masksToBounds = NO;
     }
     return  self;
}
//mask路径
- (UIBezierPath *)framePath
{
     if  ( !_framePath )
     {
         CGFloat arrowWidth = 14;
         CGMutablePathRef path = CGPathCreateMutable();
         CGRect rectangle = CGRectInset(CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetWidth(self.bounds)), 3,3);
         CGPoint p[3] = {
         {CGRectGetMidX(self.bounds)-arrowWidth/2, CGRectGetWidth(self.bounds)-6},
         {CGRectGetMidX(self.bounds)+arrowWidth/2, CGRectGetWidth(self.bounds)-6},
         {CGRectGetMidX(self.bounds), CGRectGetHeight(self.bounds)-4}
         };
         CGPathAddRoundedRect(path, NULL, rectangle, 5, 5);
         CGPathAddLines(path, NULL, p, 3);
         CGPathCloseSubpath(path);
         _framePath = [UIBezierPath bezierPathWithCGPath:path];
         CGPathRelease(path);
     }
     return  _framePath;
}

我用代码生成了形状路径 并以今生成了layer的mask和shadowPath工具

使用时 只要直接用SDWebImage设置头像就好了性能

1
[annotationView.avatarView sd_setImageWithURL:[NSURL URLWithString:avatarURL] placeholderImage:placeHolderImage];

接下来用工具分析一下问题出来哪 分析性能固然是选择Instrments(用法在这里就不作介绍了) 打开Core Animation 而后运行程序 滑动地图 能够看到性能分析以下优化

pic_003.png

原来平均帧数只有不到30帧 这离咱们的目标60帧差得实在太远动画

再使用Debug Option来深刻分析一下url

pic_004.png

因为MKMapView的缘由 这里咱们主要关心这几个选项

  • Color Blended Layers

  • Color Misaligned Images

  • Color Offscreen-Rendered Yellow

分别打开这几个选项 结果以下

pic_005.png

能够看到

  • Color Blended Layers没有问题 不过这也是正常的 因为使用了mask 没有透明的地方

  • Color Misaligned Images除了默认头像外全中 这是由于服务器上的图片大小跟显示的大小不一致 致使缩放 而默认头像则是一致的 因此没问题

  • Color Offscreen-Rendered Yellow全中 因为使用了mask 致使大量的离屏渲染 这也是性能降低的主要缘由

解决

问题的缘由找到了 那么接下来该如何解决呢?

  • 首先mask是确定不能用了

  • 其次下载下来的图片咱们要预处理成实际大小

那么 直接把下载下来的图片合成为咱们要显示的最终结果不就ok了吗? 试试看

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
- (void)loadAnnotationImageWithURL:(NSString*)url imageView:(UIImageView*)imageView
{
     //将合成后的图片缓存起来
     NSString *annoImageURL = url;
     NSString *annoImageCacheURL = [annoImageURL stringByAppendingString:@ "cache" ];
     UIImage *cacheImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:annoImageCacheURL];
     if  ( cacheImage )
     {
         //LLLog(@"hit cache");
         imageView.image = cacheImage;
     }
     else
     {
         //LLLog(@"no cache");
         [imageView sd_setImageWithURL:[NSURL URLWithString:annoImageURL]
         placeholderImage:placeHolderImage
         completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
         if  (!error)
         {
             UIImage *annoImage = [image annotationImage];
             imageView.image = annoImage;
             [[SDImageCache sharedImageCache] storeImage:annoImage forKey:annoImageCacheURL];
             }
         }];
     }
}
@implementation UIImage (LJC)
- (UIImage*) annotationImage
{
     static UIView *snapshotView = nil;
     static UIImageView *imageView = nil;
     if  ( !snapshotView )
     {
         snapshotView = [UIView  new ];
         snapshotView.frame = CGRectMake(0, 0, TRACK_ANNOTATION_SIZE.width, TRACK_ANNOTATION_SIZE.height);
         imageView = [UIImageView  new ];
         [snapshotView addSubview:imageView];
         imageView.clipsToBounds = YES;
         imageView.frame = snapshotView.bounds;
         imageView.contentMode = UIViewContentModeScaleAspectFill;
         CGFloat arrowWidth = 14;
         CGMutablePathRef path = CGPathCreateMutable();
         CGRect rectangle = CGRectInset(CGRectMake(0, 0, CGRectGetWidth(imageView.bounds), CGRectGetWidth(imageView.bounds)), 3,3);
         CGPoint p[3] = {
             {CGRectGetMidX(imageView.bounds)-arrowWidth/2, CGRectGetWidth(imageView.bounds)-6},
             {CGRectGetMidX(imageView.bounds)+arrowWidth/2, CGRectGetWidth(imageView.bounds)-6},
             {CGRectGetMidX(imageView.bounds), CGRectGetHeight(imageView.bounds)-4}
         };
         CGPathAddRoundedRect(path, NULL, rectangle, 5, 5);
         CGPathAddLines(path, NULL, p, 3);
         CGPathCloseSubpath(path);
         CAShapeLayer *shapelayer = [CAShapeLayer layer];
         shapelayer.frame = imageView.bounds;
         shapelayer.path = path;
         imageView.layer.mask = shapelayer;
         snapshotView.layer.shadowPath = path;
         snapshotView.layer.shadowRadius = 1.0f;
         snapshotView.layer.shadowColor = [UIColor colorWithHex:0x666666FF].CGColor;
         snapshotView.layer.shadowOpacity = 1.0f;
         snapshotView.layer.shadowOffset = CGSizeMake(0, 0);
         CGPathRelease(path);
     }
     imageView.image = self;
     UIGraphicsBeginImageContextWithOptions(TRACK_ANNOTATION_SIZE, NO, 0);
     [snapshotView.layer renderInContext:UIGraphicsGetCurrentContext()];
     UIImage *copied = UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();
     return  copied;
}
@end

而后使用的时候 只要简单的以下调用就OK了

1
[self loadAnnotationImageWithURL:avatarURL imageView:annotationView.avatarView];

看看修改以后的Instruments表现如何

pic_006.png

  • Color Blended Layers全中 这也是无可避免的 由于显示的就是一张带透明度的图 可是因为地图的特殊性(头像的位置变化间隔较长 因此不会常常引起合成 也没有动画) 因此这里也不是问题

  • Color Misaligned Images没问题了 由于头像已被缩放成了相同大小

  • Color Offscreen-Rendered Yellow没问题了 由于只是简单的显示了一张图片 而并无须要离屏渲染的东西了

再来看下帧数状况

pic_007.png

Oh-Yeah~ 不光帧数达到了咱们的目标60帧(因为还有业务逻辑线程在后台跑 因此没有那么的稳定) 就连平均运行耗时都降低了很多 就算地图上再多显示几十我的 也不成问题了

小结

不光是MKMapView 其实包括UITableView在内的不少地方均可以用文中所说的方法去优化 其核心点就是 合成+缓存固然 因为合成仍是会耗费一部分资源的 因此比较适合头像这种小的资源

关于图形性能优化 能够看下这篇好文(有对文中提到的Debug Option不太明白的 这里有详细的解释)

相关文章
相关标签/搜索