Androidアプリを開発するとき、カメラや写真を使ったアプリを作りたいというケースはとても多いです。アプリ開発でデバイスの機能へアクセスするときは、初心者だと少しわかりにくい所も多いです。今回は、カメラの機能にアクセスする方法や注意点について解説していきます。
Androidでカメラ機能を使うためには
Androidアプリでカメラの機能を使うためには、以下の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>
作る機能をピックアップ
カメラは次の順番で作っていきます。
- Layoutの実装
- カメラ有無チェックとアクセス
- プレビューの実装
- カメラアクティビティの実装
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)
}
}