重點整理
- 打開相機,完成 full screen 預覽
第一步
最先開始,我們要先對整個螢幕設置全畫面顯示,並隱藏status bar 和 navgation(有的手機是用虛擬導航按鍵,那就直接隱藏)
navgation 用在實體的導航按鍵當然是不起作用的,不過多設這個flag對實體沒影響,主要是通吃所有手機全螢幕顯示
在 onResume 下多加這行:
1 | window.decorView.systemUiVisibility = |
接著我們要先做一件重要的事情,資源有開就有關,所以要在生命週期的部分去控制這個行為,記得之前已經有在 onResume 的地方打開了相機吧! 這時侯要在 onPause 的時候關閉,無論有無授權都關閉!
1 | override fun onPause() { |
close 都還沒有任何程式碼實作,之後再做就可以了
第二步
開始準備預覽畫面,首先要先在Activity中宣告出兩個變數,一個是 textureView 用 findViewById 存取,一個是 surfaceListener,textureView的各種狀態call back,使用匿名類別直接實作。
1 | private lateinit var textureView: TextureView |
然後在onSurfaceTextureAvailable呼叫 preview() 的 private 方法,稍後會實作。
- 在onCreate 加上:
1 | textureView = findViewById(R.id.surface) |
- onResume 原本直接openCamera的呼叫,改寫為:
1 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { |
之後統一打開相機接著打開預覽的步驟,都在prview直接動作。
- preview()
1 | private fun preview() { |
- 另外要特別注意的是,如果這台手機支援的最大preview size之長或寬小於 screen 的時候,即時用 1080x1920 對 Texture BufferSize設定,比例跟720x1280一樣的情況之下,還是一樣會造成圖像拉伸問題,所以在挑選合適的大小的時候,盡量preview長寬小於Screen,並符合 Screen ratio
程式碼解說:
onSurfaceTextureAvailable , onResume 中都直接呼叫了 preview(),onSurfaceTextureAvailable 照字面上顯示就是 TextureView 可使用的時候,開啟camera preview的時候,也是必須要在 Texture 可用之後,才比較不會有問題。
而第一行 textureView.isAvailable 的判斷,簡單做兩件事情:
- 可用的時候,直接preview
- 不可用的時候,先用 listener 去監聽,得到 callback之後在 preivew
所以在onSurfaceTextureAvailable call back時,preview()呼叫後就會進入 textureView.isAvailable == true 的判斷,然後開啟相機並預覽。
這邊我直接使用了 flatMap,串連兩個 Observable ,在 flatMap 區塊中,如果接到 false 代表開啟失敗,那麼就直接回傳了 Observable.just(false),後面也都不用持續在做。
operator of flatMap 在 Rx 的用意,是一種Observale的轉換方式,其轉換結果也要符合是 Observale
這個方式用在一步串一步的流程中,滿好使用與理解的!
第三步
Activity
如果Activity的程式碼實作都沒問題之後,就切回 CameraCore 寫有關預覽的部分程式碼。這次我們直接把重點擺在startPreview。
這裏先說明,我獨立CameraCore出來的意義:
- 封裝 Camera2 使用
- 跟 Activity 程式碼分開
- 跟 View 的邏輯分開
這樣做的好處很多,最大的益處就是邏輯切割和重複使用,比較不會因為需要改view而整個Activity大改程式碼與架構。
CameraCore
- thread lock
請先在class 內宣告一個執行緒鎖,這裏運用了 Semaphore。
1 | private val lock = Semaphore(1) |
Semaphore 建構子的 permits 填入1,代表一次執行一次就上鎖,其他 thread 等釋放。
- startPreview(surface: List
)
先把原本的回傳值改為 create ,就跟 openCamera 一樣:
1 | fun startPreview(surface: List<Surface>): Observable<Boolean> { |
開始寫主要的核心程式碼的時候,要先稍微提一下 Semaphore 用在這 class 中的意義:
Semaphore的使用是 acquire + release,具體運作流程不多說。主要在CameraCore中的作用是上鎖 preview 和 close 兩方法之間不要互搶資源。
是一種保證一定會依照以下流程的運作的手法:
- preview -> close
- close -> preivew
當你在背景執行緒開啟預覽、關閉相機的時候尤其更為明顯;甚至如果更安全,連 openCamera 都可以加上去,保證流程順暢。
所以一開始在 create 區塊中第一行要寫的就是上鎖:
1 | return Observable.create { |
tryAcquire 跟 acquire 不一樣的地方在,tryAcquire 是有試著獲取的意味,我試著獲取一段時間之內,如果沒有拿到鑰匙就會回傳 false,這時候我們就要做相對應的處理。
第四步
接著最後的流程就是建立兩個主要物件:CaptureRequest、CameraCaptureSession,它們的建立方式要使用 CameraDevice 來建立,就用我們開啟相機時得到的相機實例來建立吧!
1 | return Observable.create { |
1.一開始先判斷 device 是不是 null ? or 有沒有 surface?
2.createCaptureRequest,這裡要傳入告知TEMPLATE 型態
TEMPLATE 有分 PREVIEW , RECORD etc,很多,我也沒看完就不多做解釋。
建立出Builder後,把他指定給先前我們寫好的全域變數
3.對這個Buidler addTarget,至少要有一個,而且一定要是整個陣列中的其中之一,這是官方文件上寫的,實際測試的時候,如果沒有增加任何一個 target ,確實會出錯
4.最後 createCaptureSession,把要 output 的資源指定上去,並且監聽 callback
onConfigureFailed,onNext(失敗),並且釋放鎖
onConfigured,將 session 保存起來,然後釋放鎖、onNext(成功)
進行 previewNow(),要使用 session開始preview,一定要在onConfigured成功後執行。
兩個call back 記得最後都要 it.onComplete
previewNow()
當完成上面的程式碼之後,previewNow 還沒做所以一定是有錯誤,接著把它完全:
1 | //啟動 session 預覽 |
1.先設定 Request 的指令,這裡我設定了 MODE_AUTO,也就是 3A 自動模式
2.setRepeatingRequest,帶入三個參數,Request 實例、callback介面、Handler loop
Request 實例來自Builder.build()。這是一種 build 的架構設計模式,可自行google參考
callback介面,這裡不傳入
Handler loop,給 null 用 main loop執行
執行 APP,看看畫面有沒有出現吧!