相冊浏覽器/選擇器/照相機Demo:LGPhotoBrowser
大多數項目中都會用到相冊浏覽和選擇功能,如果需要使用到自定義相冊浏覽器,那麼,性能優化將是一個很重要的課題。畢竟操作對象是圖片這樣相對較大寫數據單位。今天就針自定義相冊浏覽選擇器四個優化點進行剖析:
縮略圖頁面加載速度優化
縮略圖頁面滑動流暢度優化
大圖浏覽滑動流暢度優化
內存優化
先看看自定義相冊的兩個主要界面:
1.縮略圖頁面加載速度優化
如果本地相冊有200張以上的照片,那麼縮略圖頁面的加載速度就顯得尤為重要。
首先,要保證縮略圖界面的控制器在沒有加載照片的時候,從viewDidLoad到viewDidAppear的時間不能太長。之前用的MWPhotoBrowser,從viewDidLoad開始到viewDidAppear完成的耗時是700ms,這個時間就太長。
其次,是從本地讀取asset的過程不能太長。獲取一個group的所有asset方法如下:
- (void) getGroupPhotosWithGroup : (ZLPhotoPickerGroup *) pickerGroup finished : (groupCallBackBlock ) callBack{ NSMutableArray *assets = [NSMutableArray array]; ALAssetsGroupEnumerationResultsBlock result = ^(ALAsset *asset , NSUInteger index , BOOLBOOL *stop){ if (asset) { [assets addObject:asset]; }else{ callBack(assets); } }; [pickerGroup.group enumerateAssetsUsingBlock:result]; }
每取一張照片,就要執行一次代碼塊result。如果本地相冊中有1000張照片,那麼這個代碼塊就要執行1000次,所有這個代碼塊絕對不能臃腫,盡量簡潔!!!
2. 縮略圖頁面滑動流暢度優化
1)cell重用
縮略圖頁面的使用collectionView展示,每一個cell上實際有兩個子視圖,一個是裡面有對勾的小圓圈,另一個是透明的按鈕。所以在cell重用的時候就要注意了,這兩個子視圖也要加入到重用的行列中,千萬不要每加載一個cell就刪除所有子視圖然後再重新安裝。試驗證明,重用機制發揮好了,可以很大程度改善流暢度。
代碼如下:
/** * 每個cell右上角的選擇按鈕 */ - (void)setupTickButtonOnCell:(ZLPhotoPickerCollectionViewCell *)cell AtIndex:(NSIndexPath *)indexPath { UIButton *tickButton = nil; if (cell.contentView.subviews.count == 2 && [cell.contentView.subviews[1] isKindOfClass:[UIButton class]]) {//如果是重用cell,則不用再添加button tickButton = cell.contentView.subviews[1]; } else { tickButton = [[UIButton alloc] init]; tickButton.frame = CGRectMake(cell.frame.size.width - 40, 0, 40, 40); [tickButton setBackgroundColor:[UIColor clearColor]]; [cell.contentView addSubview:tickButton]; [tickButton addTarget:self action:@selector(tickBtnTouched:) forControlEvents:UIControlEventTouchUpInside]; } //runtime 關聯對象 objc_setAssociatedObject(tickButton, @"tickBtn", indexPath, OBJC_ASSOCIATION_ASSIGN); }
2)獲取縮略圖
在ALAsset類中有兩種獲取縮略圖的方法:thumbnail和aspectRatioThumbnail,第一個是普通縮略圖,第二個是按比例的縮略圖,實驗證明前者比後者快。實現方法如下:
慢
- (UIImage*)thumbImage{ return [UIImage imageWithCGImage:[self.assetaspectRatioThumbnail]]; }
快
- (UIImage*)thumbImage{ return [UIImage imageWithCGImage:[self.assetthumbnail]]; }
thumbnail方法要比aspectRatioThumbnail快很多。
但是,在ios9上,用thumbnail方法取得的縮略圖顯示出來不清晰。
所以,在代碼中可以加判斷,在iOS9上使用前者,其他使用後者。這樣可以在一定程度上提高流暢度。
3.大圖浏覽滑動流暢度優化
大圖浏覽界面使用collectionView展示,一張圖占據一個cell。一般情況下,collectionView需要加載cell時,就用數據源方法中取一個cell。在數據源方法中需要給cell獲取ALAsset對象的fullResolutionImage,獲取方法如下:
- (UIImage*)originImage{ UIImage *image = [UIImage imageWithCGImage:[[self.assetdefaultRepresentation]fullResolutionImage]]; return image; }
那麼問題來了,執行這個方法需要花費大概80ms,這就會產生卡頓感。
那怎麼辦?用異步加載?如果用異步的話,在originImage方法執行完之前,需要加載的cell已經暴露在視野中,是背景色,等fullResolutionImage執行完了,圖片會突然蹦出。這樣的體驗效果當然不行啦。
我的解決思路是:提前獲取image。概括的來說呢,就是在恰當的時機,提前在後台取到將要顯示的fullResolutionImage。
在viewDidLoad中獲取當前圖片的前一張image和後一個image,這時程序中就有三張准備好的image,如果向左邊滑動,直接拿取右邊的圖片給cell。
這一步是重點。滑動過程中,異步加載下一張image,這個下一張是需要判斷滑動方向的。從本次滑動動作結束到下次滑動開始,中間這個時間段異步加載一張圖片,時間是充裕的。那麼,在什麼時候開始異步加載呢?你可以在scrollView的這幾個代理方法中實現:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView; // called on finger up as we are moving - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; // called when // called on finger up if the user dragged. velocity is in points/millisecond. targetContentOffset may be changed to adjust where the scroll view comes to rest - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset NS_AVAILABLE_IOS(5_0);
我選擇最後一個,它有targetContentOffset,根據這個可以判斷手離開屏幕後,collectionView是否滑到下一張。因為用戶有時候會在滑動的時候猶豫一下,結果沒滑到下一張,這個時候在代碼裡就不要做多余的操作了。功能代碼如下:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { //如果滑動松開手後回滾動到下一個頁面 if (targetContentOffset->x != _beginDraggingContentOffsetX) { DraggingDirect direct = [self getDraggingDirect]; //獲得currentPage if (direct == LEFT) { self.currentPage = (NSInteger)(scrollView.contentOffset.x / (scrollView.frame.size.width) + 0.9); if (self.currentPage > self.photos.count - 1) { self.currentPage --; } } else if (direct == RIGHT) { self.currentPage = (NSInteger)(scrollView.contentOffset.x / (scrollView.frame.size.width)); } //獲得image dispatch_queue_t queue = dispatch_queue_create("BeginDecelerating", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ //獲取下一張image [self loadNextImageWithDirect:direct]; }); } }
- (DraggingDirect)getDraggingDirect { DraggingDirect direct; if (self.beginDraggingContentOffsetX == self.collectionView.contentOffset.x ) { direct = MIDDLE; } else if (self.beginDraggingContentOffsetX > self.collectionView.contentOffset.x) { direct = RIGHT; } else { direct = LEFT; } return direct; }
/** * 根據上一次的滑動方向,加載下一張圖 */ - (void)loadNextImageWithDirect:(DraggingDirect)direct { if (direct == LEFT && self.currentPage < self.photos.count - 1){ if (!((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage + 1]).photoImage) { LGPhotoAssets *asset = ((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage + 1]).asset; ((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage + 1]).photoImage = [asset originImage]; } } else if(direct == RIGHT && self.currentPage > 0){ if (!((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage - 1]).photoImage) { LGPhotoAssets *asset = ((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage - 1]).asset; ((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage - 1]).photoImage = [asset originImage]; } } else ; }
4. 內存優化
如果選中9張原圖,點擊發送後內存瞬間暴增到100M以上。原因是點擊發送之後,程序會連續循環執行9次
UIImage*image = [UIImage imageWithCGImage:[[self.assetdefaultRepresentation]fullResolutionImage]];
這句代碼是解壓縮原分辨率照片的。
這句代碼是很耗內存的,但是沒辦法,這是系統的API。
解決辦法1:讓這九次解壓過程分開執行,一張圖片發送完成之後再解壓下一張圖片。這樣可以改善內存問題。
解決辦法2:利用NSData,如果項目中只需要得到原圖的原始數據,那麼就沒必要再把CGImage解壓成UIImage,避開解壓代碼。附上獲取NSData代碼:
- (NSData *)imageData:(ALAsset*)asset{ ALAssetRepresentation *assetRep = [asset defaultRepresentation]; NSUInteger size = [assetRep size]; uint8_t *buff = malloc(size); NSError *err = nil; NSUInteger gotByteCount = [assetRep getBytes:buff fromOffset:0 length:size error:&err]; if (gotByteCount) { if (err) { free(buff); return nil; } } return [NSData dataWithBytesNoCopy:buff length:size freeWhenDone:YES]; }
以上就是本文的所有內容,難免會出纰漏,望各位匡正!
優化後的自定義相冊選擇浏覽器Demo:LGPhotoBrowser
關於相冊方面的基礎知識請參考: iOS的AssetsLibrary框架解讀與應用