(轉載)基於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不太明白的 這裏有詳細的解釋)

相關文章
相關標籤/搜索