หลังจากที่ได้รู้เรื่องราวของ CameraX กันแล้ว มาดูกันว่าการใช้งาน CameraX ด้วยวิธีที่ง่ายที่สุดนั้น จะง่ายซักแค่ไหนกันเชียว
บทความทั้งหมดในซีรีย์นี้
- ตอนที่ 1 — รู้จัก CameraX กันแล้วหรือยัง?
- ตอนที่ 2 —ใช้งานแบบง่าย ๆ ด้วย PreviewView และ CameraController (Now Reading)
PreviewView และ CameraController สำหรับการใช้งานกล้องแบบพื้นฐาน
การเรียกใช้งานกล้องที่แอปส่วนใหญ่ต้องการมากที่สุด ก็คือการเปิดกล้องขึ้นมา แล้วสามารถกดถ่ายภาพหรือวีดีโอแบบง่ายๆได้ ไม่ต้องทำอะไรเยอะมาก เพราะว่าเป็นแค่ส่วนหนึ่งของแอปเท่านั้น ไม่ได้ต้องการลูกเล่นหวือหวาในกล้องเลย
นั่นคือที่มาของคลาสทั้ง 2 ตัวนี้ที่อยู่ใน CameraX เพื่อทำให้นักพัฒนาสามารถใช้งานกล้องแบบง่ายๆ โดยที่ไม่ต้องเขียนโค้ดให้เยอะ
การใช้งานกล้องแบบไหนถึงจะเหมาะกับวิธีนี้?
เพื่อให้ผู้ที่หลงเข้ามาอ่านสามารถรู้ได้ว่าควรจะใช้ PreviewView กับ CameraController หรือควรใช้คำสั่งอื่นที่อยู่ใน CameraX ด้วยตัวเอง เจ้าของบล็อกจึงรวมความสามารถที่มีใน CameraView เพื่อช่วยในการตัดสินใจได้
- แสดงภาพจาก View Finder ของกล้อง
- เลือกใช้งานได้ทั้งกล้องหน้าและกล้องหลัง
- มีปุ่มกดเพื่อถ่ายภาพ
- มีปุ่มบันเพื่อทึกวีดีโอ
- สามารถซูมได้
- เปิด/ปิดการใช้แฟลชกล้อง
- ไม่จำเป็นต้องสนใจ Multi Camera
ถ้าทั้งหมดนี้สามารถตอบโจทย์ความต้องการของผู้ที่หลงเข้ามาอ่านได้ ก็ไม่จำเป็นต้องเขียนเองทั้งหมดหรอก สามารถใช้ PreviewView และ CameraController ได้เลย
และที่เจ้าของบล็อกชอบที่สุดก็คือ PreviewView และ CameraController รองรับการแตะเพื่อปรับโฟกัสกล้องได้ด้วย ทำให้ไม่ต้องเสียเวลาไปนั่งเขียนเอง
Dependency ที่ต้องใช้
ในการใช้ PreviewView และ CameraController จะต้องเพิ่ม Dependency ของ CameraX เพียงแค่ 1 ตัว
implementation "androidx.camera:camera-camera2:<latest_version>"
implementation "androidx.camera:camera-view:<latest_version>"
โดยข้างในจะมี Dependency ของ CameraX Core และ CameraX Lifecycke อยู่แล้ว จึงไม่จำเป็นต้องเพิ่มเข้าไปเอง
และเหตุผลที่ต้องใส่ Dependency ของ Camera2 เพิ่มเข้าไปด้วย ก็เพราะว่าเดิมที CameraX นั้นต้องมีการ Initialize ก่อนจะเริ่มใช้งาน ซึ่งใน Camera2 ได้เตรียมคำสั่งไว้ให้แล้ว และเรียกคำสั่งนั้นผ่าน Content Provider ให้โดยอัตโนมัติ ช่วยให้นักพัฒนาไม่ต้องเพิ่มคำสั่งเอง
Rumtime Permission สำหรับ CameraX
นักพัฒนาจะต้องจัดการกับ Runtime Permission เอง เพราะว่า CameraX ไม่ได้จัดการให้ ดังนั้นก่อนที่จะเรียกใช้คำสั่งของ CameraX จะต้องขอ Permission ให้เรียบร้อยก่อน
- การถ่ายภาพนิ่ง — ต้องขอ Camera Permission
- การถ่ายวีดีโอ — ต้องขอ Camera Permission และ Audio Record Permission
- การบันทึกไฟล์ภาพหรือวีดีโอลงเครื่อง — ต้องขอ Storage Permission ที่จำเป็น แต่ถ้าบันทึกลงใน App-specific Directory ก็ไม่จำเป็นต้องขอ Permission ใดๆ
เมื่อเตรียมคำสั่งสำหรับ Permission เสร็จเรียบร้อยแล้ว ก็เริ่ม Implement คำสั่งของ CameraView ได้เลย
คำสั่งสำหรับ Layout XML
เนื่องจาก PreviewView เป็นแค่ View ตัวหนึ่ง ดังนั้นนักพัฒนาจึงสามารถใส่ไปลงใน Layout XML ที่ต้องการได้เลย
<androidx.camera.view.PreviewView ... />
โดย PreviewView จะไม่มี Custom Attribute เป็นของตัวเอง เพราะจะต้องควบคุมการทำงานของกล้องผ่าน CameraController
สร้าง CameraController ให้ PreviewView
นักพัฒนาจะต้องสร้างคลาส CameraController เพื่อกำหนดให้กับ PreviewView ทุกครั้ง ถึงจะควบคุมการทำงานของกล้องได้
val context: Context = /* ... */
val previewView: PreviewView = /* ... */
val cameraController = LifecycleCameraController(context)
/* ... */
previewView.controller = controller
ในการสร้าง CameraController จะต้องสร้างเป็น LifecycleCameraController ที่จะช่วยควบคุมการทำงานของกล้องให้สอดคล้องกับ Lifecycle ของ Activity หรือ Fragment โดยอัตโนมัติ (ใช่ครับ ผู้ที่หลงเข้ามาอ่านไม่ต้องเขียนเองเลย)
และในระหว่างนี้ก็สามารถกำหนดค่าต่าง ๆ ให้กับ CameraController ได้เลย
กำหนด Use Case ที่ต้องการใช้งาน
จากที่บอกไปในตอนแรกว่า CameraX ออกแบบการทำงานในลักษณะของ Use Case Interfacing เพื่อให้การทำงานแต่ละแบบเป็นอิสระจากกัน โดย CameraX จะมี Use Case พื้นฐานเพื่อใช้งานกับ CameraController ดังนี้
IMAGE_CAPTURE
สำหรับการถ่ายรูปIMAEG_ANALYSIS
สำหรับการนำภาพที่แสดงใน View Finder มาประมวลผลต่อVIDEO_CAPTURE
สำหรับการถ่ายวีดีโอ
โดยนักพัฒนาสามารถกำหนดได้ว่าจะให้ CameraController เรียกใช้งาน Use Case ใดบ้างด้วยคำสั่งแบบนี้
val cameraController: LifecycleCameraController = /* ... */
cameraController.setEnabledUseCases(
CameraController.IMAGE_CAPTURE or
CameraController.VIDEO_CAPTURE
)
จะเห็นว่าการกำหนด Use Case จะเป็นลักษณะของ Bitmask อยากได้ Use Case ไหนก็ให้เอามารวมกันด้วยคำสั่ง or
ได้เลย
แนะนำให้กำหนด Use Case ก่อนที่จะใช้คำสั่ง bindToLifecycle(...)
กำหนดว่าจะให้ใช้งานกล้องหน้าหรือกล้องหลัง
CameraX จะกำหนดให้ใช้งานกล้องหลังเป็นค่าเริ่มต้น แต่ถ้าต้องการกำหนดเป็นกล้องหน้า ก็ให้เช็คก่อนว่าอุปกรณ์แอนดรอยด์เครื่องนั้นมีกล้องหน้าหรือไม่ แล้วค่อยกำหนดค่าผ่าน CameraSelector ของ CameraController
val cameraController: LifecycleCameraController = /* ... */
/* ... */
if(controller.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA)) {
controller.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
}
กำหนดค่าอื่น ๆ
ยังมีค่าอื่น ๆ อีกมากมายที่นักพัฒนาสามารถกำหนดใน CameraController ได้ ไม่ว่าจะเป็น
- เปิด/ปิดการซูมกล้องด้วยการ Pinch to Zoom บน PreviewView
- เปิด/ปิดการปรับระยะโฟกัสด้วยการแตะภาพจาก View Finder บน PreviewView
- กำหนด Capture Mode ว่าจะเน้น Maximum Quality หรือ Minimize Latency
- กำหนดความกว้าง/สูงที่ต้องการสำหรับภาพจาก Image Capture หรือ Video Capture
- ฯลฯ
val controller: LifecycleCameraController = /* ... */
/* ... */
controller.isPinchToZoomEnabled = true
controller.isTapToFocusEnabled = true
controller.imageCaptureMode = ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
controller.imageCaptureTargetSize = CameraController.OutputSize(Size(1024, 1024))
controller.videoCaptureTargetSize = CameraController.OutputSize(Size(1920, 1080))
controller.setImageAnalysisAnalyzer(Executors.newFixedThreadPool(4), { imageProxy ->
/* ... */
})
สั่งให้กล้องเริ่มทำงาน
เมื่อกำหนดค่าต่างๆให้กับกล้องเสร็จเรียบร้อยแล้วจะต้องใช้คำสั่ง bindToLifecycle(...)
เพื่อให้กล้องเริ่มทำงาน
// SimpleCameraActivity.kt
class SimpleCameraActivity : AppCompatActivity() {
/* ... */
private fun bindCamera() {
/* ... */
controller.bindToLifecycle(this)
}
}
ให้เรียกคำสั่งดังกล่าวตอนที่ต้องการเปิดใช้งานกล้อง และกรณีที่ Activity หรือ Fragment นั้น ๆ ถูกปิดลง ก็ไม่ต้องทำอะไรเพิ่ม ปล่อยให้เป็นหน้าที่ของ LifecycleCameraController ได้เลย
แต่ถ้าต้องการหยุดใช้งานกล้องโดยไม่ต้องรอให้ Activity หรือ Fragment หยุดทำงาน ก็สามารถใช้คำสั่ง unbind()
ได้เลย
สลับกล้องหน้าและกล้องหลัง
ในการสลับไปมาระหว่างกล้องหน้ากับกล้องหลังจะต้องเช็คเองว่ากำลังเปิดใช้งานกล้องตัวไหนอยู่ เพื่อสั่งให้สลับไปเป็นกล้องอีกตัวหนึ่ง โดยจะทำผ่าน CameraController ทั้งหมดนั่นเอง
private fun toggleCamera() {
when {
controller.cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA &&
controller.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ->
controller.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
controller.cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA &&
controller.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ->
controller.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
}
}
การซูม
สำหรับการกำหนดค่าระยะซูมให้กับกล้องที่เป็น Default Camera นั่นหมายความว่าอุปกรณ์แอนดรอยด์ที่มีกล้องหลัง 3 ตัว ก็จะกำหนดระยะซูมโดยอ้างอิงจากตัวที่เป็น Default Camera เท่านั้น
โดยนักพัฒนาสามารถกำหนดระยะซูมได้ 2 แบบด้วยกันคือ Linear Zoom และ Zoom Ratio
Linear Zoom
เป็นการกำหนดระยะในการซูมด้วยค่าระหว่าง 0 - 1 โดยที่
- 0 คือระยะที่ใกล้ที่สุด โดยส่วนใหญ่จะมีระยะอยู่ที่ 1x (ไม่รวมระยะซูมที่ใกล้กว่า 1x ของกล้องที่เป็นเลนส์ Ultra Wide)
- 1 คือระยะที่ไกลที่สุด ซึ่งขึ้นอยู่กับว่ากล้องตัวนั้นซูมได้สูงสุดเท่าไร (ไม่รวมระยะซูมของกล้องที่เป็นเลนส์ Telephoto)
val cameraController: LifecycleCameraController = /* ... */
/* ... */
cameraController.setLinearZoom(0.4f)
ข้อดีของการใช้ Linear Zoom คือนักพัฒนาไม่ต้องรู้ว่าอุปกรณ์แอนดรอยด์แต่ละเครื่องมีระยะซูมเท่าไร สามารถกำหนดเป็นค่า Float ที่อยู่ระหว่าง 0 - 1 ได้เลย แต่จะไม่รู้ว่าระยะที่กำหนดไปนั้นเป็นระยะซูมที่เท่าไร
Zoom Ratio
เป็นการกำหนดระยะในการซูมด้วยค่าระยะซูมโดยตรง เช่น ต้องการให้ซูมที่ 1x ต้องกำหนดค่าเป็น 1f
และ 4f
สำหรับระยะซูมที่ 4x
val cameraController: LifecycleCameraController = /* ... */
/* ... */
cameraController.setZoomRatio(2f)
ข้อดีของการใช้ Zoom Ratio คือนักพัฒนาสามารถกำหนดระยะซูมที่ต้องการได้โดยตรง แต่ต้องเช็คระยะซูมใกล้สุดและสูงสุดจากกล้องในอุปกรณ์แอนดรอยด์แต่ละเครื่องเอง เพราะถ้ากำหนดค่าเกินที่ระยะซูมของกล้องจะได้เป็น IllegalArgumentException แทน
จะรู้ระยะซูมของกล้องได้ยังไง?
นักพัฒนาสามารถเช็คข้อมูลเกี่ยวกับระยะซูมของกล้องได้จาก Zoom State ของ CameraController ที่เป็น LiveData ซึ่งจะมีบอกให้ทั้ง Linear Zoom กับ Zoom Ratio รวมไปถึง Max/Min ของ Zoom Ratio สำหรับกล้องตัวนั้น ๆ ด้วย
val cameraController: LifecycleCameraController = /* ... */
/* ... */
controller.zoomState.observe(this) { zoomState ->
val linearZoom = zoomState.linearZoom
val zoomRatio = zoomState.zoomRatio
val maxZoomRatio = zoomState.maxZoomRatio
val minZoomRatio = zoomState.minZoomRatio
/* ... */
}
โดย Zoom State จะส่งค่ามาให้ตั้งแต่เปิดใช้งานกล้อง และจะอัปเดตค่าใหม่ให้ทุกครั้งที่ระยะซูมมีการเปลี่ยนแปลง
การถ่ายภาพ (Image Capture)
การสั่งให้กล้องถ่ายภาพได้ จะต้องกำหนด Use Case ของ IMAGE_CAPTURE
ด้วย โดยจะมีคำสั่งให้เลือก 2 แบบด้วยกัน
takePicture(executor: Executor, callback: ImageCapture.OnImageCapturedCallback)
takePicture(outputFileResults: ImageCapture.OutputFileResults, executor: Executor, callback: ImageCapture.OnImageSavedCallback)
โดยแต่ละคำสั่งจะมีจุดประสงค์ที่แตกต่างกัน
ถ่ายภาพแล้วบันทึกไฟล์ลงในเครื่องให้ทันที
สำหรับการทำงานที่ไม่ต้องการทำอะไรมากนัก แค่อยากให้กดถ่ายภาพแล้วบันทึกเป็นไฟล์ลงในเครื่องทันที จะต้องกำหนด Directory เพื่อบันทึกไฟล์ภาพด้วยคลาส ImageCapture.OutputFileResults
โดยคลาสดังกล่าวจะเปิดให้นักพัฒนาสามารถกำหนด Directory ที่ต้องการได้อย่างอิสระ ไม่ว่าจะกำหนดด้วยคลาส File, OutputStream หรือ Uri + ContentResolver ก็ได้
ImageCapture.OutputFileOptions.Builder(file: File)
ImageCapture.OutputFileOptions.Builder(outputStream: OutputStream)
ImageCapture.OutputFileOptions.Builder(contentResolver: ContentResolver, uri: Uri, contentValues: ContentValues)
ยกตัวอย่างเช่น เจ้าของบล็อกต้องการถ่ายภาพแล้วให้บันทึกไฟล์ภาพตามที่กำหนดไว้ในคลาส File ก็สามารถใช้คำสั่งแบบนี้ได้เลย
val file: File = /* ... */
val executor: Executor = /* ... */
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()
cameraView.takePicture(outputFileOptions, executor, object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
// Success
}
override fun onError(exception: ImageCaptureException) {
// Error
}
})
โดยคำสั่ง takePicture(...)
จะส่ง Callback กลับมาเป็น ImageCapture.OnImageSavedCallback
เพื่อให้นักพัฒนาสามารถรู้ได้ว่าถ่ายภาพเสร็จแล้ว
และจะเห็นว่า onImageSaved(...)
ที่ทำงานตอนถ่ายภาพสำเร็จจะมี ImageCapture.OutputFileResults
ส่งมาให้ด้วย โดยค่าของ OutputFileResults จะไม่เป็น null
ก็ต่อเมื่อตอนที่สร้าง ImageCapture.OutputFileOptions
ด้วย Uri + ContentResolver เท่านั้น ถ้าสร้างด้วย File หรือ OutputStream จะได้ค่าเป็น null
เสมอ
และถ้ามีปัญหาใดๆที่ไม่สามารถทำให้ถ่ายภาพไม่ได้ก็จะมีการส่ง ImageCaptureException มาใน onError(...)
โดยจะแนบ Error Code มาให้ด้วย เพื่อบอกว่าปัญหาเกิดจากอะไร
ImageCapture.ERROR_FILE_IO
ImageCapture.ERROR_CAPTURE_FAILED
ImageCapture.ERROR_CAMERA_CLOSED
ImageCapture.ERROR_INVALID_CAMERA
ImageCapture.ERROR_UNKNOWN
ถ่ายภาพอย่างเดียว ไม่ต้องบันทึกไฟล์
ในบางครั้งนักพัฒนาก็อาจจะต้องการถ่ายภาพเพื่อนำมาทำบางอย่างโดยที่ไม่ต้องการให้บันทึกไฟล์ลงในเครื่อง จึงต้องเปลี่ยนมาใช้คำสั่งแบบนี้แทน
val execute: Execute = /* ... */
cameraView.takePicture(execute, object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
// Success
}
override fun onError(exception: ImageCaptureException) {
// Error
}
})
โดยคำสั่งดังกล่าวไม่ต้องกำหนดคลาส ImageCapture.OutputFileOptions เพราะไม่ต้องการบันทึกไฟล์ลงในเครื่อง และได้เปลี่ยนมาใช้ Callback เป็น ImageCapture.OnImageCapturedCallback
แทน ซึ่งจะมี Override Method ข้างในแตกต่างกันตรงที่
override fun onCaptureSuccess(image: ImageProxy) {
val width: Int = image.width
val height: Int = image.height
val image: Image = image.image
val imageInfo: ImageInfo = image.imageInfo
val format: Int = image.format
val planes: Array<ImageProxy.PlaneProxy> = image.planes
val byteBuffer: ByteBuffer = planes[0].buffer
val cropRect: Rect = image.cropRect
/* ... */
}
จะเห็นว่าใน onCaptureSuccess(...)
จะส่งข้อมูลออกมาเป็น ImageProxy แทน ซึ่งเป็นข้อมูลของภาพถ่ายเพื่อให้นักพัฒนานำข้อมูลไปใช้ได้ตามต้องการ
การถ่ายวีดีโอ (Video Capture)
การสั่งให้กล้องถ่ายภาพได้ จะต้องกำหนด Use Case ของ VIDEO_CAPTURE
ด้วย โดยจะมีคำสั่งให้เลือก 3 แบบด้วยกัน
startRecording(file: File, executor: Executor, callback: VideoCapture.OnVideoSavedCallback)
startRecording(fileDescriptor: ParcelFileDescriptor, executor: Executor, callback: VideoCapture.OnVideoSavedCallback)
startRecording(outputFileOptions: OutputFileOptions, executor: Executor, callback: VideoCapture.OnVideoSavedCallback)
จะเห็นว่าคำสั่งทั้งหมดของ startRecording(...)
จะเป็นการถ่ายวีดีโอแล้วบันทึกเป็นไฟล์ลงในเครื่องทันที ซึ่งต่างจากการถ่ายภาพที่สามารถนำข้อมูลภาพมาใช้งานโดยตรงได้ โดยไม่จำเป็นต้องบันทึกลงในเครื่อง ทั้งนี้ก็เพราะว่าเราไม่ควรนำข้อมูลวีดีโอมาเก็บไว้ใน Memory เพื่อทำ Processing โดยตรง
ดังนั้นเวลาเรียกใช้งานจริง ก็จะได้ออกมาเป็น
val file: File = /* ... */
val executor: Executor = /* ... */
cameraView.startRecording(file, execute, object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved(file: File) {
// Success
}
override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
// Error
}
})
โดย onVideoSaved(...)
จะทำงานก็ต่อเมื่อบันทึกวีดีโอเสร็จ โดยจะมีคลาส OutputFileResults ส่งมาให้ด้วย ซึ่งจะมีค่าเป็น null
เสมอ เพราะนักพัฒนาไม่สามารถกำหนดค่าเป็น Uri สำหรับการไฟล์วีดีโอได้
ถ้ามีปัญหาใดๆก็ตามที่ทำให้ถ่ายวีดีโอหรือบันทึกไฟล์ไม่ได้ก็จะเรียก onError(...)
แทน โดยมี Error Code และ Message ส่งมาให้ด้วย
OnVideoSavedCallback.ERROR_RECORDING_IN_PROGRESS
OnVideoSavedCallback.ERROR_ENCODER
OnVideoSavedCallback.ERROR_MUXER
OnVideoSavedCallback.ERROR_INVALID_CAMERA
OnVideoSavedCallback.ERROR_FILE_IO
OnVideoSavedCallback.ERROR_UNKNOWN
และการบันทึกวีดีโอนั้นไม่ได้เหมือนกับการถ่ายภาพที่สั่งปุป และทำงานเสร็จภายในไม่กี่วินาที
cameraView.stopRecording()
cameraView.isRecording
ดังนั้นจึงต้องมีคำสั่งสำหรับหยุดบันทึกวีดีโอ และคำสั่งเช็คว่ากำลังบันทึกวีดีโออยู่หรือไม่ เพื่อให้นักพัฒนาเรียกใช้งานได้ตามความเหมาะสม
ทำไมต้องกำหนด Executor สำหรับการถ่ายภาพหรือวีดีโอ?
จะเห็นว่ามีคลาส Executor ที่นักพัฒนาจะต้องกำหนดในตอนเรียกคำสั่งสำหรับถ่ายภาพหรือวีดีโอด้วย ซึ่งเจ้าของบล็อกขอหยิบมาอธิบายในหัวข้อนี้แทน
ไม่ว่าจะเป็น Callback จากการถ่ายภาพหรือวีดีโอก็ตาม คำสั่งที่จะเรียกใช้งานในนั้นก็ขึ้นอยู่กับนักพัฒนาแต่ละคนด้วย บางคนอาจจะต้องการถ่ายภาพเสร็จแล้วแสดงผลบางอย่างบนหน้าจอทันที ในขณะที่บางคนอาจจะต้องการทำอะไรบางอย่างกับไฟล์ภาพก่อน
takePicture(executor: Executor, callback: ImageCapture.OnImageCapturedCallback)
takePicture(outputFileResults: ImageCapture.OutputFileResults, executor: Executor, callback: ImageCapture.OnImageSavedCallback)
startRecording(file: File, executor: Executor, callback: VideoCapture.OnVideoSavedCallback)
startRecording(fileDescriptor: ParcelFileDescriptor, executor: Executor, callback: VideoCapture.OnVideoSavedCallback)
startRecording(outputFileOptions: OutputFileOptions, executor: Executor, callback: VideoCapture.OnVideoSavedCallback)
จึงทำให้ CameraView ถูกออกแบบมาให้นักพัฒนาสามารถกำหนด Executor ที่จะทำงานใน Callback ได้ตามใจชอบว่าอยากให้คำสั่งที่ใส่ไว้ใน Callback นั้นทำงานบน Thread แบบไหน
ถ้าต้องการให้ทำงานใน Main Executor
ให้เรียก Main Executor ด้วยคำสั่ง
val context: Context = /* ... */
val executor: Executor = ContextCompat.getMainExecutor(context)
เมื่อกำหนดเป็น Main Executor ก็จะทำให้คำสั่งใน Callback ก็จะทำงานอยู่บน Executor ที่เป็น Main Thread แล้ว
ถ้าต้องการให้ทำงานใน Executor อื่น
สามารถสร้างขึ้นมาจากคลาส Executors ได้ตามใจชอบเลย
val singleThreadExecutor: Executor = Executors.newSingleThreadExecutor()
val fixedThreadPoolExecutor: Executor = Executors.newFixedThreadPool(3)
val cachedThreadPoolExecutor: Executor = Executors.newCachedThreadPool()
อยากจะได้แค่ Thread เดียว, จะใช้เป็น Thread Pool หรือแบบอื่นๆ ก็สามารถสร้างขึ้นมาได้ตามใจชอบ โดยนักพัฒนาควรสร้างให้ความเหมาะสมของคำสั่งที่จะทำงาน
ของดี ใช้ง่าย ใช้เถอะ
PreviewView และ CameraController ถูกสร้างขึ้นมาเพื่อลดความยุ่งยากในการเรียกใช้งานกล้องที่ไม่ต้องการลูกเล่นอะไรมากมาย เพียงแค่อยากจะเรียกใช้งานกล้องในแอปเพื่อถ่ายภาพเท่านั้น ซึ่งจะเห็นว่าความสามารถและคำสั่งต่างๆที่มีให้ใน CameraView นั้นครอบคลุมกับความต้องการส่วนใหญ่แล้ว
ดังนั้นอยากใช้งานกล้องแบบง่ายๆก็ต้อง Preview + CameraController แต่ถ้าต้องทำอะไรมากกว่านั้นก็ค่อยไปเขียนคำสั่งใน CameraX เองทั้งหมดแล้วกันเนอะ