CameraXへの移行とその経緯

f:id:Rakuma:20210309112616p:plain こんにちは、ラクマで主にAndroidを担当している@shin_nosakaです。

ラクマAndroidでは、v7.34.0でバーコード出品の機能で使用されるCamera APIをCameraXへの移行を行いました。 今回は、Android界隈では鬼門と言われる、ラクマでのカメラ機能の実装と、CameraXへの移行にあたっての経緯についてお話ししようかと思います。

ラクマAndroidにおけるカメラ周りの実装の歴史

ラクマではバーコード出品機能をv7.15.0から提供しています。

f:id:Rakuma:20210305132732p:plain:w320

この頃、ラクマではAndroid 5.0未満(4.4)の端末をサポートしており、Camera2 APIはAndroid5.0以降でサポートされるということもあり、Camera API1で実装されました。

なお、Androidにおけるカメラ バージョンのサポートはこちらをご覧ください。

Camera API1での実装

カメラ関連のクラスはこんな感じです。

f:id:Rakuma:20210305123320p:plain

CameraSourcePreviewは内部でSurfaceViewを持っており、こちらにカメラの管理を行うためのクラスであるCameraSourceを保持しています。 CameraSourceは画面のライフサイクルを意識する必要があるため、onResumeでカメラの開始、onResumeでカメラの開始、onPauseでカメラの停止など、ライフサイクルに応じた制御を行うようにしています。

f:id:Rakuma:20210309110958p:plain

このCameraSourceでは、カメラから引き渡されたフレームデータを解析し、バーコード検出を行うBarcodeScanningProcessorを保持しています。

BarcodeScanningProcessorでは、バーコードの情報を表示するBarcodeGraphicと、カメラの情報を表示するCameraImageGraphicを生成し、CameraSourceにこれらの情報を表示するように制御されています。

なお、バーコード検出にはML Kit forFirebaseの機能を使用していました。

ご覧のとおり、Camera API1を使用した実装では、複雑な構造でバーコードスキャンの機能を実現しています🥺

Camera API1からCameraXへ

しかしながら、Android 5.0未満(4.4)のサポートも v7.17.0で終了し、CameraX Jetpackサポートライブラリが出てしばらく経ったこと、 加えて、ML Kit forFirebaseに代わり、スタンドアロンのMLキットSDKが提供されたこともあり、長らくレガシーなAPIや精度的に不安定だったCamera API1から、CameraXへの移行を行いました。

CameraXでの実装

CameraXでのクラスはこんな感じになりました。

f:id:Rakuma:20210305123909p:plain

お、とてもシンプルになりましたね🥰

各種グラフィックを描画を全体的に管理するGraphicOverlayや、バーコードの情報を表示するBarcodeGraphicはほぼそのままで、BarcodeAnalyzerでバーコードの情報を表示するBarcodeGraphicを生成し、描画しています。バーコード検出にはスタンドアロンのMLキットSDKを使用しています。

また、カメラの起動などの管理を行うためのクラスであるCameraSourceでの処理はViewModel側に移管されることになりました。 

val cameraProviderLiveData = MutableLiveData<ProcessCameraProvider>()

fun requestCameraProvider() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(getApplication())
    cameraProviderFuture.addListener({
        try {
            cameraProviderLiveData.setValue(cameraProviderFuture.get())
        } catch (e: Exception) {
            // エラー処理
        }
    }, ContextCompat.getMainExecutor(getApplication()))
}

Activityでは、次のようにカメラのプレビューや、BarcodeAnalyzerをImageAnalysisのアナライザに設定しています。 また、CameraXではライフサイクルに対応したカメラの管理をしてくれます。

fun setupCamera(provider: ProcessCameraProvider) {
    val preview = Preview.Builder()
        .build()
    val imageAnalysis = ImageAnalysis.Builder()
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
        .build()
    imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), barcodeAnalyzer)
    barcodeAnalyzer.callback = viewModel.barcodeAnalyzerCallback

    try {
        provider.unbindAll()
        val cameraSelector = CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()
        provider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis) // ライフサイクルを任せられる!
        preview.setSurfaceProvider(binding.scanBarcodeCamera.viewFinder.surfaceProvider)
    } catch (e: Exception) {
        // エラー処理
    }
}

おわりに

CameraXを使用すると、ライフサイクルを自動で処理してくれたり、端末によってさまざまなアスペクト比、画面の向き、回転などを考慮してくれたりと、煩雑になりがちなAndroidでのカメラ機能の実装を簡単に実装することができます。 コードラボで実際にコードを記述しながら、書くとこんな簡単にできるんだとびっくりしますよ!

codelabs.developers.google.com

最後に、ラクマでは、User Firstをコア・バリューの一つに掲げ、一緒にアプリ開発をしてくれるメンバーを募集しています。 とくに、Androidで我こそは!という方がいらっしゃったら、ぜひカジュアルにお話だけでもしてみませんか?