Android CameraX ปี 2026: คู่มือถ่ายภาพและวิดีโอพร้อมคำถามสัมภาษณ์
เรียนรู้ CameraX บน Android 2026: การถ่ายภาพ การบันทึกวิดีโอ การผสานรวม Jetpack Compose และคำถามสัมภาษณ์ทางเทคนิคสำหรับนักพัฒนา Android

Android CameraX ช่วยลดความซับซ้อนในการพัฒนากล้องโดยการ abstract ความซับซ้อนของฮาร์ดแวร์ผ่าน API ที่รับรู้ lifecycle ด้วยเวอร์ชัน 1.6.2 ที่เสถียรแล้วและการย้ายไปยัง CameraPipe เสร็จสมบูรณ์ CameraX มอบ camera stack ประสิทธิภาพสูงเทียบเท่ากับแอปกล้อง Pixel
CameraX 1.6 ย้ายไปใช้ CameraPipe ใช้ Media3 Muxer เป็นค่าเริ่มต้นสำหรับ VideoCapture และทำให้ SessionConfig API เสถียร Kotlin DSL builders (preview { }, imageCapture { }, videoCapture { }) ลด boilerplate ได้ถึง 40%
สถาปัตยกรรม CameraX และโมเดล Use Case
CameraX มี use case หลักสี่ตัวที่สามารถทำงานพร้อมกันได้: Preview, ImageCapture, ImageAnalysis และ VideoCapture แต่ละ use case เชื่อมต่อกับ LifecycleOwner ดังนั้นกล้องจะเริ่มและหยุดโดยอัตโนมัติตาม activity หรือ fragment
CameraProvider ทำหน้าที่เป็น factory ที่สร้าง instance ของ use case และผูกเข้ากับกล้อง การแยกส่วนนี้ทำให้โค้ดเดียวกันทำงานได้บนอุปกรณ์จากผู้ผลิตต่างๆ โดยไม่ต้องกังวลเกี่ยวกับความแตกต่างเฉพาะของแต่ละผู้ผลิต
class CameraSetup(
private val context: Context,
private val lifecycleOwner: LifecycleOwner
) {
private lateinit var cameraProvider: ProcessCameraProvider
private var imageCapture: ImageCapture? = null
private var videoCapture: VideoCapture<Recorder>? = null
suspend fun initialize() {
// ProcessCameraProvider binds use cases to lifecycle
cameraProvider = ProcessCameraProvider.getInstance(context).await()
}
fun bindUseCases(previewView: PreviewView) {
// Unbind existing use cases before rebinding
cameraProvider.unbindAll()
// Preview use case displays camera feed
val preview = Preview.Builder()
.build()
.apply { setSurfaceProvider(previewView.surfaceProvider) }
// ImageCapture for photos
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// VideoCapture with Recorder
val recorder = Recorder.Builder()
.setQualitySelector(QualitySelector.from(Quality.FHD))
.build()
videoCapture = VideoCapture.withOutput(recorder)
// Bind all use cases to lifecycle
cameraProvider.bindToLifecycle(
lifecycleOwner,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageCapture,
videoCapture
)
}
}การเรียก bindToLifecycle เชื่อมต่อ use case กับทั้งฮาร์ดแวร์กล้องและ lifecycle เมื่อ LifecycleOwner เข้าสู่สถานะ STARTED กล้องจะเปิด เมื่อเข้าสู่ STOPPED กล้องจะปิดโดยอัตโนมัติ
การใช้งาน ImageCapture สำหรับถ่ายภาพ
ImageCapture มีโหมดถ่ายสองแบบ: CAPTURE_MODE_MINIMIZE_LATENCY สำหรับการตอบสนองชัตเตอร์ที่รวดเร็ว และ CAPTURE_MODE_MAXIMIZE_QUALITY สำหรับการถ่ายภาพที่ต้องการการประมวลผลมาก ทางเลือกขึ้นอยู่กับว่าแอปให้ความสำคัญกับความเร็ว (แอปโซเชียล) หรือคุณภาพ (เครื่องสแกนเอกสาร)
class PhotoCaptureManager(
private val context: Context,
private val imageCapture: ImageCapture
) {
private val mainExecutor = ContextCompat.getMainExecutor(context)
fun capturePhoto(onResult: (Uri?) -> Unit) {
// Generate unique filename with timestamp
val filename = "IMG_\${System.currentTimeMillis()}.jpg"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
// Store in Pictures/CameraX folder on Android 10+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX")
}
}
val outputOptions = ImageCapture.OutputFileOptions.Builder(
context.contentResolver,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
).build()
imageCapture.takePicture(
outputOptions,
mainExecutor,
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(results: ImageCapture.OutputFileResults) {
// results.savedUri contains the MediaStore URI
onResult(results.savedUri)
}
override fun onError(exception: ImageCaptureException) {
Log.e("PhotoCapture", "Capture failed: \${exception.message}")
onResult(null)
}
}
)
}
}CameraX 1.5 เพิ่มการรองรับการถ่าย DNG (RAW) สำหรับแอปที่ต้องการข้อมูลเซ็นเซอร์ดิบ ตรวจสอบความสามารถของอุปกรณ์ด้วย ImageCapture.getImageCaptureCapabilities() ก่อนเปิดใช้งาน RAW output
การบันทึกวิดีโอด้วย VideoCapture และ Recorder
VideoCapture ใช้ออบเจกต์ Recorder เพื่อจัดการ encoding และ muxing Recorder API ใช้รูปแบบ prepare-start-stop ที่จัดการการซิงค์เสียงโดยอัตโนมัติ
class VideoRecordingManager(
private val context: Context,
private val videoCapture: VideoCapture<Recorder>
) {
private var activeRecording: Recording? = null
fun startRecording(onEvent: (VideoRecordEvent) -> Unit): Boolean {
// Check if already recording
if (activeRecording != null) return false
val filename = "VID_\${System.currentTimeMillis()}.mp4"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX")
}
}
val mediaStoreOutput = MediaStoreOutputOptions.Builder(
context.contentResolver,
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
)
.setContentValues(contentValues)
.build()
// Prepare recording with audio if permission granted
val pendingRecording = videoCapture.output
.prepareRecording(context, mediaStoreOutput)
if (hasAudioPermission()) {
pendingRecording.withAudioEnabled()
}
// Start recording and store reference
activeRecording = pendingRecording.start(
ContextCompat.getMainExecutor(context)
) { event ->
onEvent(event)
// Clear reference when recording completes
if (event is VideoRecordEvent.Finalize) {
activeRecording = null
}
}
return true
}
fun stopRecording() {
activeRecording?.stop()
}
fun pauseRecording() {
activeRecording?.pause()
}
fun resumeRecording() {
activeRecording?.resume()
}
private fun hasAudioPermission(): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED
}
}CameraX 1.5 เพิ่มการรองรับวิดีโอ slow-motion สอบถามความสามารถด้วย Recorder.getHighSpeedVideoCapabilities() ก่อนเปิดใช้งานการบันทึก 120fps หรือ 240fps
พร้อมที่จะพิชิตการสัมภาษณ์ Android แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
CameraX กับ Jetpack Compose
CameraX 1.5 แนะนำ artifact camera-compose พร้อม composable CameraXViewfinder สำหรับแอปที่ยังใช้ PreviewView แบบ View-based AndroidView ให้ความสามารถในการทำงานร่วมกัน
@Composable
fun CameraPreview(
modifier: Modifier = Modifier,
onPreviewViewCreated: (PreviewView) -> Unit
) {
AndroidView(
modifier = modifier.fillMaxSize(),
factory = { context ->
PreviewView(context).apply {
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
scaleType = PreviewView.ScaleType.FILL_CENTER
onPreviewViewCreated(this)
}
}
)
}
@Composable
fun CameraScreen(viewModel: CameraViewModel = viewModel()) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(Unit) {
viewModel.initializeCamera(context, lifecycleOwner)
}
Box(modifier = Modifier.fillMaxSize()) {
CameraPreview(
onPreviewViewCreated = { previewView ->
viewModel.bindPreview(previewView)
}
)
// Capture buttons overlay
Row(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(32.dp),
horizontalArrangement = Arrangement.spacedBy(24.dp)
) {
IconButton(
onClick = { viewModel.capturePhoto() }
) {
Icon(Icons.Default.Camera, "Take photo")
}
IconButton(
onClick = { viewModel.toggleRecording() }
) {
Icon(
if (viewModel.isRecording) Icons.Default.Stop
else Icons.Default.Videocam,
"Record video"
)
}
}
}
}สำหรับการพัฒนาที่เน้น Compose คู่มือ คำถามสัมภาษณ์ Jetpack Compose ครอบคลุมรูปแบบการรวม ViewModel ที่สามารถนำไปใช้โดยตรงกับการจัดการสถานะกล้อง
การจัดการสิทธิ์กล้อง
Android ต้องการสิทธิ์ CAMERA ขณะรันไทม์ RECORD_AUDIO จำเป็นเฉพาะเมื่อบันทึกวิดีโอพร้อมเสียง กระบวนการขอสิทธิ์ควรอธิบายเหตุผลที่ต้องการเข้าถึงก่อนที่จะขอ
class CameraPermissionHandler(private val activity: ComponentActivity) {
private val requiredPermissions = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
private val permissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val cameraGranted = permissions[Manifest.permission.CAMERA] == true
val audioGranted = permissions[Manifest.permission.RECORD_AUDIO] == true
if (cameraGranted) {
onCameraPermissionGranted(audioGranted)
} else {
onCameraPermissionDenied()
}
}
fun checkAndRequestPermissions() {
when {
hasAllPermissions() -> onCameraPermissionGranted(hasAudioPermission())
shouldShowRationale() -> showPermissionRationale()
else -> permissionLauncher.launch(requiredPermissions)
}
}
private fun hasAllPermissions(): Boolean {
return requiredPermissions.all { permission ->
ContextCompat.checkSelfPermission(
activity, permission
) == PackageManager.PERMISSION_GRANTED
}
}
private fun hasAudioPermission(): Boolean {
return ContextCompat.checkSelfPermission(
activity,
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED
}
private fun shouldShowRationale(): Boolean {
return requiredPermissions.any { permission ->
ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)
}
}
}คำถามสัมภาษณ์ CameraX
CameraX มักปรากฏในการสัมภาษณ์ Android เมื่อพูดถึงการรวมกล้อง การจัดการ lifecycle หรือการ abstract ฮาร์ดแวร์ คำถามเหล่านี้ทดสอบความเข้าใจเกี่ยวกับสถาปัตยกรรมและรายละเอียดการใช้งานจริง
ทำไมต้องใช้ CameraX แทน Camera2?
CameraX abstract ความแตกต่างเฉพาะอุปกรณ์ที่ Camera2 เปิดเผยโดยตรง การใช้งาน Camera2 ต้องจัดการ workaround เฉพาะอุปกรณ์หลายร้อยรายการ CameraX รวมการแก้ไขเหล่านี้ไว้ในไลบรารี ซึ่งทดสอบกับ CameraX Test Lab ที่ครอบคลุมอุปกรณ์มากกว่า 150 รุ่น
การ binding ที่รับรู้ lifecycle ขจัดความจำเป็นในการจัดการทรัพยากรด้วยตนเอง Camera2 ต้องการการเรียก open/close อย่างชัดเจน ซึ่งอาจทำให้เกิด memory leak หากไม่จัดการอย่างถูกต้องในทุก callback lifecycle
CameraProvider ผูก use case กับ lifecycle อย่างไร?
CameraProvider ใช้รูปแบบ LifecycleObserver ภายใน เมื่อเรียก bindToLifecycle() มันจะลงทะเบียน observer บน LifecycleOwner ที่ให้มา Observer นี้รับ event ON_START และ ON_STOP เพื่อเปิดและปิดกล้อง
ประเด็นสำคัญ: CameraX ไม่เริ่มกล้องทันทีเมื่อ binding มันรอจนกว่า lifecycle จะถึงสถานะ STARTED ซึ่งตรงกับเวลา Activity.onStart()
เกิดอะไรขึ้นเมื่อผูก use case ที่ไม่เข้ากัน?
ไม่ใช่ทุกชุด use case จะทำงานได้บนทุกอุปกรณ์ Preview + VideoCapture + ImageAnalysis + ImageCapture อาจล้มเหลวบนฮาร์ดแวร์รุ่นเก่า CameraX โยน IllegalArgumentException ตอน binding ไม่ใช่ตอน runtime
ทางแก้คือตรวจสอบ CameraProvider.hasCamera() และความสามารถของอุปกรณ์ก่อนผูกชุดที่ซับซ้อน สำหรับแอปที่สำคัญ ควรทดสอบอย่างชัดเจนบนอุปกรณ์ระดับล่าง
อธิบายความแตกต่างระหว่าง CAPTURE_MODE_MINIMIZE_LATENCY และ CAPTURE_MODE_MAXIMIZE_QUALITY
MINIMIZE_LATENCY ลด lag ของชัตเตอร์โดยใช้ pipeline ประมวลผลที่เร็วกว่า อาจข้ามการ post-processing บางส่วน MAXIMIZE_QUALITY ใช้การประมวลผล HDR+ เต็มรูปแบบ การลดนอยส์ และ multi-frame capture บนอุปกรณ์ที่รองรับ
การแลกเปลี่ยน: โหมด latency ถ่ายใน 100-200ms โหมด quality อาจใช้เวลา 500ms-1s ขึ้นอยู่กับแสง แอปโซเชียลมักเลือก latency แอปสแกนเอกสารเลือก quality
CameraX จัดการการหมุนอุปกรณ์อย่างไร?
CameraX จัดการการหมุนโดยอัตโนมัติผ่านการตั้งค่า target rotation โดยค่าเริ่มต้นจะใช้ rotation ของ Display แต่แอปสามารถ override ด้วย setTargetRotation() ข้อมูล EXIF ของภาพ output มี tag orientation ที่ถูกต้อง
สำหรับ PreviewView scaleType จัดการความแตกต่างของอัตราส่วนภาพ FILL_CENTER ครอปเพื่อเติมเต็ม FIT_CENTER แสดง letterboxing ทางเลือกขึ้นอยู่กับว่าแอปให้ความสำคัญกับ preview เต็มจอหรือหลีกเลี่ยงการครอป
Dependencies และการตั้งค่าสำหรับ CameraX 1.6
ไลบรารี CameraX แบ่งออกเป็นหลาย artifact รวมเฉพาะสิ่งที่แอปใช้เพื่อลดขนาด APK
dependencies {
val cameraxVersion = "1.6.2"
// Core library required for all use cases
implementation("androidx.camera:camera-core:\$cameraxVersion")
// Camera2 implementation (required)
implementation("androidx.camera:camera-camera2:\$cameraxVersion")
// Lifecycle integration
implementation("androidx.camera:camera-lifecycle:\$cameraxVersion")
// VideoCapture use case
implementation("androidx.camera:camera-video:\$cameraxVersion")
// PreviewView and camera UI components
implementation("androidx.camera:camera-view:\$cameraxVersion")
// Optional: Extensions (Night mode, HDR, etc.)
implementation("androidx.camera:camera-extensions:\$cameraxVersion")
// Optional: Real-time effects
implementation("androidx.camera:camera-effects:\$cameraxVersion")
}
android {
// CameraX requires Java 11 bytecode
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}Manifest ต้องการการประกาศฟีเจอร์และสิทธิ์:
<!-- AndroidManifest.xml -->
<manifest>
<!-- Camera hardware requirement -->
<uses-feature android:name="android.hardware.camera.any" />
<!-- Runtime permissions -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>รูปแบบที่เกี่ยวข้องสำหรับ Kotlin Coroutines นำไปใช้เมื่อ wrap การเรียก ListenableFuture ของ CameraX ในฟังก์ชัน suspend
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
การเปลี่ยนแปลง CameraX 1.6 สำหรับแอป Production
CameraX 1.6 มีสามการเปลี่ยนแปลงที่ส่งผลต่อการใช้งานที่มีอยู่:
-
Backend CameraPipe: Camera stack ภายในตอนนี้ใช้ CameraPipe สถาปัตยกรรมเดียวกับกล้อง Pixel ซึ่งปรับปรุงประสิทธิภาพบนอุปกรณ์ Pixel และให้ foundation ที่สะอาดกว่าสำหรับฟีเจอร์ขั้นสูง โค้ดที่มีอยู่ไม่ต้องเปลี่ยนแปลง
-
Media3 Muxer เป็นค่าเริ่มต้น: VideoCapture ตอนนี้ใช้ Media3 Muxer แทน MediaMuxer ซึ่งปรับปรุงความทนทานต่อ crash: หากแอปหยุดระหว่างการบันทึก ไฟล์วิดีโอยังเล่นได้ พื้นผิว API ไม่เปลี่ยนแปลง
-
SessionConfig API เสถียร: แอปตอนนี้สามารถกำหนดค่าการตั้งค่ากล้องขั้นสูงผ่าน SessionConfig โดยไม่ต้องใช้ annotation experimental ซึ่งเปิดใช้งานฟีเจอร์เช่นการใช้กล้องหน้า/หลังพร้อมกันบนอุปกรณ์ที่รองรับ
เส้นทางการย้าย: อัปเดตเวอร์ชัน dependency ทดสอบบนอุปกรณ์ที่เคยมีปัญหา บันทึกการเผยแพร่ CameraX บันทึกการเปลี่ยนแปลงพฤติกรรมระหว่างเวอร์ชัน
- CameraX ใช้ binding ที่รับรู้ lifecycle:
bindToLifecycle()เชื่อมต่อ use case กับกล้องและ lifecycle ของ Activity - โหมด ImageCapture แลกเปลี่ยน latency กับ quality: เลือกตาม use case ไม่ใช่ความสามารถของอุปกรณ์
- VideoCapture ต้องการการตั้งค่า Recorder ก่อน binding: เตรียม output options ก่อนเริ่ม
- การจัดการสิทธิ์ควรอธิบายวัตถุประสงค์ก่อนขอ: ลดอัตราการปฏิเสธ
- CameraX 1.6 ย้ายไปยัง CameraPipe ภายใน: ไม่ต้องเปลี่ยนแปลง API แต่ทดสอบบนอุปกรณ์เป้าหมาย
- คำถามสัมภาษณ์เน้นการจัดการ lifecycle ชุด use case และการเปรียบเทียบกับ Camera2
คุณหาบั๊กใน Android เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 11 กันยายน 2569
แชร์
บทความที่เกี่ยวข้อง

การทำ Modularization บน Android ปี 2026: สถาปัตยกรรม Multi-Module และคำถามสัมภาษณ์
เชี่ยวชาญสถาปัตยกรรม multi-module ของ Android ด้วย convention plugins, Gradle version catalogs และ feature modules พร้อมคำถามสัมภาษณ์ที่พบบ่อยเกี่ยวกับกลยุทธ์ modularization

Jetpack Navigation Compose 2026: การนำทางแบบ Type-Safe และคำถามสัมภาษณ์งาน
เรียนรู้ Jetpack Navigation Compose 2.10 ที่มีการนำทางแบบ type-safe ด้วย Kotlin Serialization คู่มือฉบับสมบูรณ์พร้อมตัวอย่างโค้ดและคำถามสัมภาษณ์ Android

Android WorkManager 2026: คู่มือฉบับสมบูรณ์ Background Tasks, Constraints และคำถามสัมภาษณ์งาน
เรียนรู้ Android WorkManager สำหรับการรัน background tasks พร้อม constraints, chaining และ CoroutineWorker รวมถึงคำถามสัมภาษณ์งานสำหรับ Android developer