iOS開發:對MKMapView的效能最佳化

來源:互聯網
上載者:User

   最近做的項目主要是LBS這塊 主打成員定位功能 我們的UI設計是這樣的

  乍一看上去是挺好挺美觀的 不同的人會顯示不同的頭像 可是當人紮堆的時候 問題就來了

  當人多的時候(例如上圖所示) 地圖滑動起來就能感覺到明顯頓卡 那種不流暢感能折磨死人 所以 自然我們要解決這個問題(等等 先不要吐槽為什麼不用地圖彙總 因為這已經是地圖放到最大了 彙總不適合這次的問題討論)

  分析

  首先看下我是怎麼實現這個annotationView的 由於這個annotationsView是異形的(也就是無法通過設定圓角直接得到) 而且裡面的圖片還因使用者而異 所以解決方案就是使用layer.mask來進行遮罩 代碼如下

  @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 然後運行程式 滑動地圖 可以看到效能分析如下

  原來平均幀數只有不到30幀 這離我們的目標60幀差得實在太遠

  再使用Debug Option來深入分析一下

  由於MKMapView的原因 這裡我們主要關心這幾個選項

  Color Blended Layers

  Color Misaligned Images

  Color Offscreen-Rendered Yellow

  分別開啟這幾個選項 結果如下

  可以看到

  Color Blended Layers沒有問題 不過這也是正常的 由於使用了mask 沒有透明的地方

  Color Misaligned Images除了預設頭像外全中 這是因為伺服器上的圖片大小跟顯示的大小不一致 導致縮放 而預設頭像則是一致的 所以沒問題

  Color Offscreen-Rendered Yellow全中 由於使用了mask 導致大量的離屏渲染 這也是效能下降的主要原因

  解決

  問題的原因找到了 那麼接下來該如何解決呢?

  首先mask是肯定不能用了

  其次下載下來的圖片我們要預先處理成實際大小

  那麼 直接把下載下來的圖片合成為我們要顯示的最終結果不就ok了嗎? 試試看

  - (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了

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

  看看修改之後的Instruments表現如何

  Color Blended Layers全中 這也是無可避免的 因為顯示的就是一張帶透明度的圖 但是由於地圖的特殊性(頭像的位置變化間隔較長 所以不會經常引發合成 也沒有動畫) 所以這裡也不是問題

  Color Misaligned Images沒問題了 因為頭像已被縮放成了相同大小

  Color Offscreen-Rendered Yellow沒問題了 因為只是簡單的顯示了一張圖片 而並沒有需要離屏渲染的東西了

  再來看下幀數情況

  Oh-Yeah~ 不光幀數達到了我們的目標60幀(由於還有商務邏輯線程在後台跑 所以沒有那麼的穩定) 就連平均運行耗時都下降了不少 就算地圖上再多顯示幾十個人 也不成問題了

  小結

  不光是MKMapView 其實包括UITableView在內的很多地方都可以用文中所說的方法去最佳化 其核心點就是 合成+緩衝 當然 由於合成還是會耗費一部分資源的 所以比較適合頭像這種小的資源

  關於圖形效能最佳化 可以看下這篇好文(有對文中提到的Debug Option不太明白的 這裡有詳細的解釋)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.