AVCaptureSession阻塞主線程問題
前陣子程序中出現了一個奇怪的 bug,在 iOS 系統上,頁面彈出的時候會卡很久,相機始終黑屏,大概6-7秒鍾,跟蹤具體每個步驟花費時間的時候發現在viewWillDisappear:
中開銷最大,這其中只調用了一個相機關閉的代碼:
if ([[self.avCameraManager session] isRunning]) {
[[self.avCameraManager session] stopRunning];
}
仔細看了文檔之后,發現問題出在stopRunning
這里,
蘋果文檔描述如下:
Clients invoke -stopRunning to stop the flow of data from inputs to outputs connected to
the AVCaptureSession instance. This call blocks until the session object has completely
stopped.
重點是這個函數在 session 完全停止下來之前會始終阻塞線程, 同樣的,在startRunning
中:
Clients invoke -startRunning to start the flow of data from inputs to outputs connected to
the AVCaptureSession instance. This call blocks until the session object has completely
started up or failed. A failure to start running is reported through the AVCaptureSessionRuntimeErrorNotification
mechanism.
因此這里必須放在后台線程中處理,否則,就會導致界面不響應,iOS8之后應該在這里做了優化,即使放在主線程做也沒有很卡頓的現象,但 iOS7中,尤其是測試設備為4s,界面卡死問題很嚴重。
開啟和關閉相機部分代碼改為:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
DDLogDebug(@"Function: %s,line : %d 開啟相機",__FUNCTION__,__LINE__);
[[self.avCameraManager session] startRunning];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
DDLogDebug(@"Function: %s,line : %d 關閉相機",__FUNCTION__,__LINE__);
if ([[self.avCameraManager session] isRunning]) {
[[self.avCameraManager session] stopRunning];
}
});
這里除了使用系統提供的隊列以外還可以自己創建 FIFO 類型后台線程進行管理,包括切換前后攝像頭、改變閃光燈模式、切換拍照和錄像等,都可以放入子線程操作。
相機前后台切換問題
另一個問題與前后台切花相關,項目中在程序進入后台時有這么一段代碼:
[self performSelector:@selector(startRunningSession) withObject:nil afterDelay:0.5];
當時我很奇怪,這里為什么要加延時呢?進入到前台時不是應該直接開啟相機嗎?於是我直接把這里改成了:
[self startRunningSession];
沒過多久,問題出現了,多次切換前后台之后發現,相機始終黑屏,這時,進入后台再回來,問題解決了,仔細加了 log 之后發現,出現黑屏的時候,確實也正確調用了啟動 session,但是並沒有收到系統相機啟動成功的消息,反而收到了兩次相機關閉的通知。
那么之前代碼中加了0.5秒延時的原因也就清楚了,是為了等待系統關閉相機之后,再調用開啟相機,以免相機啟動失敗,但這種方式真的合理嗎?其實並沒有解決本質問題,如果0.5秒鍾的時間並沒有完全關閉系統相機呢?這里仍然會出現黑屏問題。
於是繼續查看系統文檔,發現系統的 sampleCode AVCam-iOS中並沒有專門用於前后台處理的邏輯,原來這里並不需要我們自己手動管理相機,系統會自動根據程序狀態來判斷是否需要關閉或者開啟相機,如果按 home 進入UIApplicationStateBackground
狀態,那么系統自動關閉相機並在進入UIApplicationStateActive
狀態時開啟相機;如果是進入UIApplicationStateInactive
狀態,例如,雙擊 home 調出任務管理器,這時,相機並沒有被關閉,仍然能夠看到 preview 畫面。
我們在程序切換前后台時,僅需要捕獲相繼開啟或者關閉的通知來刷新界面即可,否則可能會由於快速開啟切換前后台導致系統相機執行命令錯亂,無法正確啟動相機。
總結
- AVCaptureSession 中絕大部分操作需要在后台線程完成,最好使用一個 FIFO 的隊列來進行操作
- 前后台切換時,無需手動管理 CaptureSession