Android CameraX ปี 2026: คู่มือถ่ายภาพและวิดีโอพร้อมคำถามสัมภาษณ์

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

Android CameraX ปี 2026: คู่มือถ่ายภาพและวิดีโอพร้อมคำถามสัมภาษณ์

Android CameraX ช่วยลดความซับซ้อนในการพัฒนากล้องโดยการ abstract ความซับซ้อนของฮาร์ดแวร์ผ่าน API ที่รับรู้ lifecycle ด้วยเวอร์ชัน 1.6.2 ที่เสถียรแล้วและการย้ายไปยัง CameraPipe เสร็จสมบูรณ์ CameraX มอบ camera stack ประสิทธิภาพสูงเทียบเท่ากับแอปกล้อง Pixel

การเปลี่ยนแปลงสำคัญใน CameraX 1.6

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 และผูกเข้ากับกล้อง การแยกส่วนนี้ทำให้โค้ดเดียวกันทำงานได้บนอุปกรณ์จากผู้ผลิตต่างๆ โดยไม่ต้องกังวลเกี่ยวกับความแตกต่างเฉพาะของแต่ละผู้ผลิต

CameraSetup.ktkotlin
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 สำหรับการถ่ายภาพที่ต้องการการประมวลผลมาก ทางเลือกขึ้นอยู่กับว่าแอปให้ความสำคัญกับความเร็ว (แอปโซเชียล) หรือคุณภาพ (เครื่องสแกนเอกสาร)

PhotoCaptureManager.ktkotlin
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 ที่จัดการการซิงค์เสียงโดยอัตโนมัติ

VideoRecordingManager.ktkotlin
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 ให้ความสามารถในการทำงานร่วมกัน

CameraPreviewComposable.ktkotlin
@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 จำเป็นเฉพาะเมื่อบันทึกวิดีโอพร้อมเสียง กระบวนการขอสิทธิ์ควรอธิบายเหตุผลที่ต้องการเข้าถึงก่อนที่จะขอ

PermissionHandler.ktkotlin
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

build.gradle.kts (Module: app)kotlin
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 ต้องการการประกาศฟีเจอร์และสิทธิ์:

xml
<!-- 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 มีสามการเปลี่ยนแปลงที่ส่งผลต่อการใช้งานที่มีอยู่:

  1. Backend CameraPipe: Camera stack ภายในตอนนี้ใช้ CameraPipe สถาปัตยกรรมเดียวกับกล้อง Pixel ซึ่งปรับปรุงประสิทธิภาพบนอุปกรณ์ Pixel และให้ foundation ที่สะอาดกว่าสำหรับฟีเจอร์ขั้นสูง โค้ดที่มีอยู่ไม่ต้องเปลี่ยนแปลง

  2. Media3 Muxer เป็นค่าเริ่มต้น: VideoCapture ตอนนี้ใช้ Media3 Muxer แทน MediaMuxer ซึ่งปรับปรุงความทนทานต่อ crash: หากแอปหยุดระหว่างการบันทึก ไฟล์วิดีโอยังเล่นได้ พื้นผิว API ไม่เปลี่ยนแปลง

  3. 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

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 11 กันยายน 2569

แชร์

บทความที่เกี่ยวข้อง