Androidアプリ開発

Androidアプリにカメラ機能を開発する手順をわかりやすく解説!

Androidアプリを開発するとき、カメラや写真を使ったアプリを作りたいというケースはとても多いです。アプリ開発でデバイスの機能へアクセスするときは、初心者だと少しわかりにくい所も多いです。今回は、カメラの機能にアクセスする方法や注意点について解説していきます。

Androidでカメラ機能を使うためには

Androidアプリでカメラの機能を使うためには、以下の2つの方法があります。

  1. 標準で提供されているカメラを利用する方法
  2. 自分でカスタムしたカメラ作ってを利用する

それぞれ、利用するための手順が異なります。それぞれの利用方法を1つずつ紹介していきます。

Android Developer - Camera2 APIドキュメント

Camera2 APIの呼び出し方と注意点

AndroidのCamera2 APIを使うためには android.hardware.camera2 をインポートすることで利用することができます。標準カメラを利用する場合と、カスタムしたカメラを利用する場合を紹介していきます。

import android.hardware.camera2;

標準のカメラ機能の利用

開発しているAndroidアプリで、標準のカメラアプリを呼び出す手順を紹介します。

機能を利用を定義する

Androidアプリがカメラの機能にアクセスするためには、AndroidManifest.xmlに以下のuses-featureを定義します。targetSdkVersion 30以上の場合、パーミッションではなくqueriesを定義する必要があります。


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.example.sample_profile">

        <uses-feature android:name="android.hardware.camera" />

    <!-- queries is needed if targetSdkVersion is equal or over 30 -->
    <queries>
    <!-- Camera -->
        <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
        </intent>
    </queries>

    <!-- 省略 -->

</manifest>

コードで標準カメラを呼び出す

開発しているAndroidアプリで標準カメラを利用するには、Intentというクラスを使って呼び出します。以下のコードで、呼び出しの手順を解説します。

/* カメラの機能が使えるかチェック */
if (!packageManager && packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {

    /* 標準カメラを呼び出すためのIntentを作成*/
    Intent intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

    /* 標準カメラを呼び出すことができるかチェック */
    if(intent && intent.resolveActivity(packageManager)){

        /* 標準カメラの呼び出し */
        startActivityForResult(intent, REQUEST_IMAGE_CAPTURE)
    }
}

カスタムカメラ機能の利用

Androidで提供されているCamera2 APIというものを利用します。Camera2 APIを使ってカメラ機能を実装することで、オリジナルのUIにしたり標準アプリでは提供されていない機能を追加したりすることができるようになります。

カメラ機能 - Android Developers Guide

パーミッションを追加する

AndroidアプリがCamera2 APIにアクセスするためには、パーミッションを追加する必要があります。パーミッションとは、アプリがカメラの機能を勝手に利用できないように、Android OSがユーザーに許可を取る必要があるように制限をしています。AndroidManifest.xmlに以下の追加することでパーミッションを追加できます。


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="com.example.sample_profile">
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />

    <!-- queries is needed if targetSdkVersion is equal or over 30 -->
    <queries>
        <!-- Camera -->
        <intent>
            <action android:name="android.media.action.IMAGE_CAPTURE" />
        </intent>
        </queries>

    <!-- 省略 -->
</manifest>

作る機能をピックアップ

カメラは次の順番で作っていきます。

  1. Layoutの実装
  2. カメラ有無チェックとアクセス
  3. プレビューの実装
  4. カメラアクティビティの実装

1. Layoutの実装

カメラのプレビューを表示するため、アクティビティとレイアウトを実装していきます。プレビューには、テクスチャと撮影用のボタンを設置しカメラっぽくします。また、Intentを設定することで標準カメラと同様に画面を呼び出せるようにしていきます。

次にCameraActivity名前で、カメラのアクティビティを作っていきましょう。onCreateで先ほど作ったレイアウトファイルを読み込みます。また、後ほどプレビューを起動するためのsetupメソッドを実装しておきます。


package com.example.sample_profile
import android.app.Activity
import android.graphics.SurfaceTexture
import android.os.Bundle
import android.view.TextureView
import androidx.appcompat.app.AppCompatActivity

class CameraActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.camera)
        setup()
    }

    fun setup() {
        // Previewの起動処理
    }
}

2. カメラ有無のチェックとアクセス

ここからは、カメラの処理を実装するためのCameraProcessorクラスを実装していきます。


/* ... */
import android.R
import android.Manifest
import android.app.AlertDialog
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import android.view.TextureView
import android.app.Activity

class CameraProcessor {

    private var cameraDevice: CameraDevice? = null

    private val REQUEST_CAMERA_PERMISSION = 1

    private var activity: Activity
    private val textureView: TextureView

    constructor(textureView: TextureView, activity: Activity) {
        this.textureView = textureView
        this.activity = activity
    }

    private fun showDialogCameraPermission() {
        if(!ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA)) {
            ActivityCompat.requestPermissions(
                activity, arrayOf(Manifest.permission.CAMERA),
                REQUEST_CAMERA_PERMISSION
            )
            return
        }

        AlertDialog.Builder(activity)
            .setMessage("R string request permission")
            .setPositiveButton(R.string.ok) { dialog, which ->
                ActivityCompat.requestPermissions(
                    activity, arrayOf(Manifest.permission.CAMERA),
                    REQUEST_CAMERA_PERMISSION
                )
            }
            .setNegativeButton(R.string.cancel) { dialog, which ->  }
            .create()
    }

    fun setup() {
        // カメラ搭載の有無をチェック
        if (activity.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) === false) return

        // カメラのアクセス権限を確認して、なければ許可を求める
        val currentPermssion = ActivityCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)
        if (currentPermssion != PackageManager.PERMISSION_GRANTED) {
            showDialogCameraPermission()
            return
        }

        // すでにカメラが起動していたら何もしない
        if (cameraDevice !== null) {
            return
        }

        // 背面カメラの取得
    }

}

3. プレビューの実装

アクセスが許可されていない場合は、許可を求める処理を呼び出します。ユーザーの許可が取得できたら、プレビューの初期化を行ってカメラのセッションを起動します。


/* ... */
import android.hardware.camera2.*

class CameraProcessor {
    /* ... */

    private var mBackgroundThread: HandlerThread? = null
    private var mBackgroundHandler: Handler? = null

    /* constructor... */

    private fun startBackgroundThread() {
        val thread = HandlerThread("CameraBackground");

        mBackgroundHandler = Handler(thread.looper)
        mBackgroundThread = thread

        thread.start();

    }

    private fun stopBackgroundThread() {
        mBackgroundThread?.quitSafely()

        mBackgroundThread?.join()
        mBackgroundThread = null
        mBackgroundHandler = null
    }

    fun setup() {

        /* ... */

        // 背面カメラの取得
        val cameraId: String = cameraManager.cameraIdList[0]
        cameraManager.openCamera(cameraId, object: CameraDevice.StateCallback() {
            override fun onOpened(cameraDevice: CameraDevice) {
                // カメラが取得できたらプレビューを生成する
                this@CameraProcessor.cameraDevice = cameraDevice
                createCameraPreviewSession()
            }

            override fun onDisconnected(cameraDevice: CameraDevice) {
                // カメラの取得に失敗したら終了する
                cameraDevice.close()
                this@CameraProcessor.cameraDevice = null
                stopBackgroundThread()
            }

            override fun onError(cameraDevice: CameraDevice, error: Int) {
                // カメラの取得に失敗したら終了する
                cameraDevice.close()
                this@CameraProcessor.cameraDevice = null
                stopBackgroundThread()
            }
        }, mBackgroundHandler)
    }

    fun createCameraPreviewSession() {
        // カメラ起動処理
    }
}

カメラに無事アクセスできた場合は、カメラの映像をテクスチャに表示してプレビューにします。


import android.os.Handler
import android.os.HandlerThread
import android.view.Surface
import android.content.Context
import android.media.ImageReader
import android.graphics.ImageFormat
import android.hardware.camera2.params.OutputConfiguration
import android.hardware.camera2.params.SessionConfiguration
import java.util.*

class CameraProcessor {
    /* ... */
    private var cameraCaptureSession: CameraCaptureSession? = null

    private fun createPreviewRequest(cameraDevice: CameraDevice): CaptureRequest {

        val surface = Surface(this.textureView.surfaceTexture)
        val imageReader = ImageReader.newInstance(
            this.textureView.width,
            this.textureView.height,
            ImageFormat.JPEG,
            2
        )

        // プレビューテクスチャの設定
        val previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
        previewRequestBuilder.addTarget(surface)
        previewRequestBuilder.set(
            CaptureRequest.CONTROL_AF_MODE,
            CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
        )

        return previewRequestBuilder.build()
    }

    fun createCameraPreviewSession() {
        // カメラ起動処理
        if (this.cameraDevice === null) return

        val cameraDevice = this.cameraDevice!!

        // プレビュー用のテクスチャ取得
        val texture = this.textureView.surfaceTexture

        val surface = Surface(texture)
        val imageReader = ImageReader.newInstance(
            this.textureView.width,
            this.textureView.height,
            ImageFormat.JPEG,
            2
        )

        val surfaces: List = Arrays.asList(surface, imageReader?.surface)

        val type = SessionConfiguration.SESSION_REGULAR
        val configurations: List = surfaces.map { OutputConfiguration(it) }
        val executor = this.activity.mainExecutor

        val previewRequest = this.createPreviewRequest(cameraDevice)
        val callback = object: CameraCaptureSession.StateCallback() {
            override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
                // 起動成功時に呼ばれる
                this@CameraProcessor.cameraCaptureSession = cameraCaptureSession
                cameraCaptureSession.setRepeatingRequest(previewRequest, null, null)
            }

            override fun onConfigureFailed(session: CameraCaptureSession) {
                // 起動失敗時に呼ばれる
            }
        }

        // 起動
        val configuration = SessionConfiguration(type, configurations, executor, callback)
        cameraDevice.createCaptureSession(configuration)
    }
}

4. カメラアクティビティの実装

撮影をしてBitmapとしてデータを保持する処理を実装します。Bitmapはローカルに保存したり、ImageViewにセットしたりして利用することができます。


class CameraProcessor {
    /* ... */

    fun take() {
        this.cameraCaptureSession?.stopRepeating()
        this.bitmap = this.textureView.bitmap

        val request = this.createPreviewRequest(this.cameraDevice!!)
        this.cameraCaptureSession?.setRepeatingRequest(request, null, null)
    }
}

Androidアプリ開発のLayoutの基礎的な使い方をわかりやすく解説!Prev

Androidアプリでダークモードに対応する方法を紹介Next

Related post

  1. Androidアプリ開発

    Androidアプリ開発で覚えておきたいKotlinの記法を紹介

    Androidアプリ開発するためにKotlinを利用するケースは多くな…

  2. Androidアプリ開発

    Android開発者になる人が読んでいる本を紹介

    Androidエンジニアになるために、どんな本を読んで勉強すれば良いだ…

  3. Androidアプリ開発

    AndroidアプリのActivityとFragmentの違いや使い方を詳しく解説

    アプリを作り始めで画面の作り方がよく分からない、ActivityやFr…

  4. debugging

    Androidアプリ開発

    Androidアプリ開発で初心者が覚えるべきデバッグ3つの手法

    Androidアプリを作り始めるといろんな不具合やエラーに悩まされるこ…

  5. Androidアプリ開発

    Androidアプリの開発の手順や必要な知識を詳しく解説!

    Androidアプリ開発を始めたいけれど、どういった知識や準備が必要な…

  6. google play service

    Androidアプリ開発

    作ったAndroidアプリをGoogle Playからリリースする手順を解説!

    Androidアプリを作ったら、次はGoogle Playでリリースを…

PAGE TOP