iOS圖片設定圓角效能問題

weixin_33912246發表於2016-01-06

一般我們在iOS開發的過程中設定圓角都是如下這樣設定的。

 avatarImageView.clipsToBounds = YES;
 [avatarImageView.layer setCornerRadius:50];

 這樣設定會觸發離屏渲染,比較消耗效能。比如當一個頁面上有十幾頭像這樣設定了圓角
 會明顯感覺到卡頓。

 注意:png圖片UIImageView處理圓角是不會產生離屏渲染的。(ios9.0之後不會離屏渲染,ios9.0之前還是會離屏渲染)。

所有如果要高效能的設定圓角就需要找另外的方法了。下面是我找到的一些方法並寫了一個例子。

101810-9c34cd972e319727.PNG
IMG_1816.PNG

設定圓角的方法

  • 直接使用setCornerRadius
    這種就是最常用的,也是最耗效能的。

  • setCornerRadius設定圓角之後,shouldRasterize=YES光柵化

    avatarImageView.clipsToBounds = YES;
    [avatarImageView.layer setCornerRadius:50];
    avatarImageView.layer.shouldRasterize = YES;
    avatarImageViewUrl.layer.rasterizationScale=[UIScreen mainScreen].scale;  //UIImageView不加這句會產生一點模糊
    
    shouldRasterize=YES設定光柵化,可以使離屏渲染的結果快取到記憶體中存為點陣圖,
    使用的時候直接使用快取,節省了一直離屏渲染損耗的效能。
    
    但是如果layer及sublayers常常改變的話,它就會一直不停的渲染及刪除快取重新
    建立快取,所以這種情況下建議不要使用光柵化,這樣也是比較損耗效能的。
    
  • 直接覆蓋一張中間為圓形透明的圖片(推薦使用)
    這種方法就是多加了一張透明的圖片,GPU計算多層的混合渲染blending也是會消耗
    一點效能的,但比第一種方法還是好上很多的。

  • UIImage drawInRect繪製圓角
    這種方式GPU損耗低記憶體佔用大,而且UIButton上不知道怎麼繪製,可以用
    UIimageView新增個點選手勢當做UIButton使用。

    UIGraphicsBeginImageContextWithOptions(avatarImageView.bounds.size, NO, [UIScreen mainScreen].scale);
    [[UIBezierPath bezierPathWithRoundedRect:avatarImageView.bounds
                                  cornerRadius:50] addClip];
    [image drawInRect:avatarImageView.bounds];
    avatarImageView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    這段方法可以寫在SDWebImage的completed回撥裡,在主執行緒非同步繪製。
    也可以封裝到UIImageView裡,寫了個DSRoundImageView。後臺執行緒非同步繪製,不會阻塞主執行緒。
    

問題:這種方法圖片很多的話CUP消耗會高,記憶體佔用也會暴增,而且後臺執行緒繪製會比在主執行緒繪製佔用更多的記憶體,不知道怎麼解決?求大神指教!

  • SDWebImage處理圖片時Core Graphics繪製圓角

      //UIImage繪製為圓角
      int w = imageSize.width;
      int h = imageSize.height;
      int radius = imageSize.width/2;
      
      UIImage *img = image;
      CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
      CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
      CGRect rect = CGRectMake(0, 0, w, h);
      
      CGContextBeginPath(context);
      addRoundedRectToPath(context, rect, radius, radius);
      CGContextClosePath(context);
      CGContextClip(context);
      CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);
      CGImageRef imageMasked = CGBitmapContextCreateImage(context);
      img = [UIImage imageWithCGImage:imageMasked];
      
      CGContextRelease(context);
      CGColorSpaceRelease(colorSpace);
      CGImageRelease(imageMasked);
    

    以上程式碼我寫成了UIImage的類別:UIImage+DSRoundImage.h
    並在SDWebImage庫裡處理image的時候使用類別方法繪製圓角並快取。


使用Instruments的Core Animation檢視效能

  • Color Offscreen-Rendered Yellow
    開啟後會把那些需要離屏渲染的圖層高亮成黃色,這就意味著黃色圖層可能存在效能問題。

  • Color Hits Green and Misses Red
    如果shouldRasterize被設定成YES,對應的渲染結果會被快取,如果圖層是綠色,就表示這些快取被複用;如果是紅色就表示快取會被重複建立,這就表示該處存在效能問題了。

用Instruments測試得

  • 第一種方法,UIImageView和UIButton都高亮為黃色。

  • 第二種方法,UIImageView和UIButton都高亮為綠色

  • 第三種方法,無任何高亮,說明沒離屏渲染。
    這種圓片覆蓋的方法一般只用在底色為純色的時候,如果圓角圖片的父View是張圖片的時候就沒辦法了,而且底色如果是多種顏色的話那要做多張不同顏色的圓片覆蓋。(可以用程式碼取底色的顏色值給圓片著色)

  • 第四種方法無任何高亮,說明沒離屏渲染(但是CPU消耗和記憶體佔用會很大)

  • 第五種方法無任何高亮,說明沒離屏渲染,而且記憶體佔用也不大。(暫時感覺是最優方法)


問題回覆:

  • 有回覆提到還有一種mask方法。
    這種方法比第一種方法其實更卡頓。一次mask發生了兩次離屏渲染和一次主屏渲染。 具體可以參考小心別讓圓角成了你列表的幀數殺手

  • @nerozhao說第四種比第一種更卡。

    我剛在demo里加了個例子測試了一下,第一種能明顯的感覺到卡頓,第四種還是挺順暢
    的,有興趣的可以自己試試看。第四種是解決了離屏渲染GPU的問題。
    

可以用Instruments的 GPU Driver進行測試:

  • Renderer Utilization
    如果這個值超過了~50%,就意味著你的動畫可能對幀率有所限制,很可能因為離屏渲染或者是重繪導致的過度混合。
  • Tiler Utilization
    如果這個值超過了~50%,就意味著你的動畫可能限制於幾何結構方面,也就是在螢幕上有太多的圖層佔用了。
101810-cab0c4046d8b916b.png
Instruments

圖上面一部分是第一種方法的資料,下面一部分是第四種方法的資料。
第一種方法的Renderer Utilization 和 Tiler Utilization 基本在90%左右。幀率在20左右。
第四種方法的Renderer Utilization 和 Tiler Utilization 基本在20%左右。幀率接近60。
幀率越接近60滑動越順暢。

  但是經過跟@nerozhao的討論發現第四種Core Graphics繪製圓角會有大量的記憶體佔用,
  而且每次繪製的時候CUP消耗會很大。

  由於@nerozhao使用了UITableView進行測試,因為UITableView滾動的時候是一直在
  複用的,UIImageView會重複繪製,所以會一直消耗CUP,然後你就能看的明顯的卡頓。

  @nerozhao在UITableView裡圖片的繪製在後臺執行緒進行繪製,解決了卡頓問題,但是
  由於是在後臺執行緒的非同步繪製所以在滾動的時候會看到圖片先是正方形然後再變成圓形。

  而我使用的是UIScrollerView進行的測試,只有第一次繪製的時候會佔用CUP資源,
  所以滑動的時候還是挺流暢的,但是記憶體消耗還是很大。如果是主執行緒繪製的話會阻塞一
  點時間的主執行緒,而後臺執行緒繪製的話記憶體消耗會更大,特別容易崩潰。

所以第四種方法當圖片特別多的時候很容易Received memory warning導致崩潰


解決問題參考文章

  • UIImage drawInRect繪製圓角記憶體暴增問題

文章:
記憶體惡鬼drawRect - 談畫圖功能的記憶體優化


最後

關於研究過程及各種設定圓角方法的例子測試對比 github原始碼
如果我的文章對你有幫助歡迎github Star
如果你有什麼問題或者想交流的可以聯絡我。QQ:398411773

相關文章