Android CameraX 2026年完全ガイド:写真・動画撮影と面接対策質問集

CameraX 1.6による写真撮影・動画録画の実装方法を解説。Jetpack Compose統合、パーミッション処理、Android開発者面接でよく聞かれるCameraX質問と回答例を紹介します。

Android CameraX 2026年完全ガイド:写真・動画撮影と面接対策質問集

Android CameraXは、ライフサイクル対応APIによってハードウェアの複雑さを抽象化し、カメラ開発を大幅に簡素化します。バージョン1.6.2が安定版としてリリースされ、CameraPipeへの移行が完了した現在、CameraXはPixelカメラアプリと同じ高性能カメラスタックを提供しています。

CameraX 1.6の主要な変更点

CameraX 1.6ではCameraPipeへの移行、VideoCaptureにおけるMedia3 Muxerのデフォルト採用、SessionConfig APIの安定化が実現しました。Kotlin DSLビルダー(preview { }imageCapture { }videoCapture { })により、ボイラープレートコードが40%削減されます。

CameraXのアーキテクチャとユースケースモデル

CameraXは、同時に実行可能な4つの主要ユースケースを提供します:Preview、ImageCapture、ImageAnalysis、VideoCapture。各ユースケースはLifecycleOwnerにバインドされるため、カメラはActivityやFragmentと連動して自動的に開始・停止します。

CameraProviderはこれらのユースケースをインスタンス化し、カメラにバインドするファクトリとして機能します。この疎結合により、ベンダー固有の問題を心配することなく、異なるOEMのデバイスで同じコードが動作します。

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の呼び出しは、ユースケースをカメラハードウェアとライフサイクルの両方に接続します。LifecycleOwnerがSTARTED状態になるとカメラが開き、STOPPED状態になると自動的に閉じます。

ImageCaptureによる写真撮影の実装

ImageCaptureは2つのキャプチャモードを提供します:高速シャッターレスポンス用のCAPTURE_MODE_MINIMIZE_LATENCYと、処理負荷の高い撮影用のCAPTURE_MODE_MAXIMIZE_QUALITY。選択は、アプリが速度(SNSアプリ)を優先するか品質(ドキュメントスキャナー)を優先するかによって決まります。

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)キャプチャのサポートが追加され、未処理のセンサーデータを必要とするアプリに対応しています。RAW出力を有効にする前に、ImageCapture.getImageCaptureCapabilities()でデバイスの機能を確認してください。

VideoCaptureとRecorderによる動画録画

VideoCaptureはRecorderオブジェクトを使用してエンコードとマルチプレクシングを処理します。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ではスローモーション動画のサポートが追加されました。120fpsまたは240fps録画を有効にする前に、Recorder.getHighSpeedVideoCapabilities()で機能を確認してください。

Androidの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

Jetpack ComposeでのCameraX統合

CameraX 1.5ではcamera-composeアーティファクトとCameraXViewfinderコンポーザブルが導入されました。ViewベースのPreviewViewを使用しているアプリでは、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面接で取り上げられます。これらの質問は、アーキテクチャと実践的な実装詳細の理解度をテストします。

Camera2の代わりにCameraXを使用する理由

CameraXは、Camera2が直接公開するデバイス固有の問題を抽象化します。Camera2の実装では、何百ものデバイス固有の回避策を処理する必要があります。CameraXはこれらの修正をライブラリに組み込み、150以上のデバイスモデルをカバーするCameraX Test Labでテストされています。

ライフサイクル対応バインディングにより、手動でのリソース管理が不要になります。Camera2では、すべてのライフサイクルコールバックで正しく処理されないとリークを引き起こす明示的なopen/close呼び出しが必要です。

CameraProviderはどのようにユースケースをライフサイクルにバインドするか

CameraProviderは内部的にLifecycleObserverパターンを使用します。bindToLifecycle()を呼び出すと、提供されたLifecycleOwnerにオブザーバーを登録します。このオブザーバーはON_STARTとON_STOPイベントを受け取り、カメラを開閉します。

重要なポイント:CameraXはバインド時に即座にカメラを開始しません。ライフサイクルがSTARTED状態に達するまで待機し、これはActivity.onStart()のタイミングと一致します。

互換性のないユースケースをバインドするとどうなるか

すべてのユースケースの組み合わせがすべてのデバイスで動作するわけではありません。Preview + VideoCapture + ImageAnalysis + ImageCaptureは古いハードウェアでは失敗する可能性があります。CameraXは実行時ではなく、バインド時にIllegalArgumentExceptionをスローします。

解決策は、複雑な組み合わせをバインドする前にCameraProvider.hasCamera()とデバイスの機能を確認することです。重要なアプリでは、ローエンドデバイスで明示的にテストしてください。

CAPTURE_MODE_MINIMIZE_LATENCYとCAPTURE_MODE_MAXIMIZE_QUALITYの違い

MINIMIZE_LATENCYは、より高速な処理パイプラインを使用し、一部の後処理をスキップする可能性があることでシャッターラグを削減します。MAXIMIZE_QUALITYは、対応デバイスで完全なHDR+処理、ノイズリダクション、マルチフレームキャプチャを適用します。

トレードオフ:レイテンシモードは100-200msでキャプチャし、クオリティモードは照明条件によって500ms-1sかかる場合があります。SNSアプリは通常レイテンシを選択し、ドキュメントスキャンアプリはクオリティを選択します。

CameraXはデバイスの回転をどのように処理するか

CameraXはターゲット回転設定を通じて回転を自動的に処理します。デフォルトではディスプレイの回転を使用しますが、アプリはsetTargetRotation()でオーバーライドできます。出力画像のEXIFデータには正しい向きタグが含まれます。

PreviewViewの場合、scaleTypeがアスペクト比の違いを処理します。FILL_CENTERは塗りつぶすためにクロップし、FIT_CENTERはレターボックスを表示します。選択は、アプリがフルスクリーンプレビューを優先するか、クロッピングを避けることを優先するかによって決まります。

CameraX 1.6の依存関係とセットアップ

CameraXライブラリは複数のアーティファクトに分割されています。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
    }
}

マニフェストには機能宣言とパーミッションが必要です:

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>

CameraXのListenableFuture呼び出しをsuspend関数でラップする際には、Kotlin Coroutinesの関連パターンが適用されます。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

CameraX 1.6が本番アプリに与える影響

CameraX 1.6は、既存の実装に影響を与える3つの変更をもたらしました:

  1. CameraPipeバックエンド:内部カメラスタックがPixelカメラと同じアーキテクチャであるCameraPipeを使用するようになりました。これによりPixelデバイスでのパフォーマンスが向上し、高度な機能のためのより清潔な基盤が提供されます。既存のコードに変更は必要ありません。

  2. Media3 Muxerのデフォルト化:VideoCaptureがMediaMuxerの代わりにMedia3 Muxerを使用するようになりました。これによりクラッシュ耐性が向上し、録画中にアプリが終了しても動画ファイルは再生可能なままです。APIサーフェスに変更はありません。

  3. SessionConfig APIの安定化:アプリはexperimentalアノテーションなしでSessionConfigを通じて高度なカメラ設定を構成できるようになりました。これにより、対応デバイスでの前面/背面カメラの同時使用などの機能が可能になります。

移行パス:依存関係のバージョンを更新します。以前問題があったデバイスでテストします。CameraXリリースノートにバージョン間の動作変更が記載されています。

  • CameraXはライフサイクル対応バインディングを使用:bindToLifecycle()はユースケースをカメラとActivityライフサイクルの両方に接続
  • ImageCaptureモードはレイテンシと品質のトレードオフ:デバイスの機能ではなくユースケースに基づいて選択
  • VideoCaptureはバインド前にRecorderのセットアップが必要:開始前に出力オプションを準備
  • パーミッション処理はリクエスト前に目的を説明:拒否率を削減
  • CameraX 1.6は内部的にCameraPipeに移行:API変更は不要だが、ターゲットデバイスでテストが必要
  • 面接質問はライフサイクル管理、ユースケースの組み合わせ、Camera2との比較に焦点
今日のチャレンジ

Android のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年9月11日 更新

共有

関連記事