안녕하세요. Simple& Happy Dev입니다.
구글 플레이에는 수많은 손전등 / 플래시라이트(이하 손전등) 앱이 존재합니다.
일반적으로 스위치 버튼 하나 정도만 있으면 되는 비교적 간단한 UI에 카메라를 다뤄본 경험이 있다면 쉽게 만들 수 있기 때문일 겁니다. 여기서 카메라를 언급한 이유는 손전등의 불빛은 카메라 플래시(Camera Flash LED)를 사용하기에 그렇습니다.
손전등 앱을 만드는 방법은 몇 가지가 있습니다.
앞서 설명했듯이 카메라 플래시를 사용한다는 공통점이 있으나, 카메라 플래시 제어를 위해서 어떻게 접근하는지에 따라서 구분됩니다.
[손전등 앱의 카메라 플래시 제어하는 3가지 방법]
아래 그림에 보듯이 크게 카메라 권한을 가지고 (= 카메라 권한 필요) 플래시를 제어하는 방식과 카메라 권한 없이(카메라 권한 불필요) 플래시를 제어하는 방식으로 만들 수 있습니다.
![카메라 플래시 제어 접근 방식](https://t1.daumcdn.net/cfile/tistory/9977B13D5C4F1DF010)
방법1 (Camera API 이용하는 방법)
- 장점: 안드로이드 구버전도 지원 가능, 구현 용이
- 단점: 카메라 권한 필요, deprecated API, 느린 속도(but, 약간 개선 가능 - 이글의 마지막에 다룰 예정)
방법2 (Camera2 API 이용하는 방법)
- 장점: 최신 Camera API
- 단점: 카메라 권한 필요, 구현 복잡, 안드로이드 5.0 (API Level 21) 이상부터 가능
방법3 (Flashlight API 이용하는 방법)
- 장점: 카메라 권한 불필요, 구현 매우 용이, 빠른 속도
- 단점: 안드로이드 6.0 (API Level 23) 이상부터 가능
Camera2 API 이용하는 방법은 거의 단점만 존재해서 일반적으로 사용하지 않는 방법입니다.
하지만, 궁금하시거나 해당 방법이 필요하신 분들이 있을 수도 있어서 다음 글에 다루도록 하겠습니다.
[Camera API 이용한 손전등 앱]
애초에 플래시는 카메라를 보조해주기 위해서 들어간 디바이스입니다.
그래서, 하드웨어적인 연결이나 소프트웨어적인 접근이 카메라에 가깝게 되어 있습니다.
그 결과 플래시를 제어하기 위해서는 카메라에 접근한 이후 카메라 파라미터로 플래시를 넘겨줘야 했습니다.
플래시가 동작하는 순서는 다음과 같습니다.
플래시 켜기: 카메라 객체의 인스턴스 생성 및 연결(open)
- 카메라 파라미터에 플래시 모드를 Torch로 설정(setFlashMode)
- 카메라 프리뷰 시작(startPreview)
플래시 끄기 방법1: 카메라 프리뷰 종료 (stopPreview) - 카메라 연결 해제 및 릴리즈(release)
플래시 끄기 방법2: 카메라 파라미터에 플래시 모드를 Off로 설정(setFlashMode)
(중요 - 추후 카메라 프리뷰 종료 및 카메라 인스턴스를 릴리즈하는 코드가 존재해야 합니다.)
동작하는 순서에 대한 설명은 여기까지 하고 이제 소스로 들어가 보겠습니다.
아래 소스는 플래시를 끌 때 위에 있는 "플래시 끄기 방법1"로 하게 되어 있습니다.
"플래시 끄기 방법2"는 글의 마지막 부분에 설명되어 있습니다.
[소스 설명]
레이아웃 (activity_main.xml)
화면 한가운데 별 모양 버튼을 하나 추가해서 이걸 누르면 플래시가 켜지도록 할 예정입니다. 그리고, 한 번 더 누르면 버튼이 토글되어서 꺼지도록 하겠습니다. 별 모양 버튼은 SDK에 내장된 리소스를 사용하시면 됩니다.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageButton
android:id="@+id/ibFlashOnOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/btn_star_big_off"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
자바 (MainActivity.java)
onCreate에서는 기기에 플래시가 지원하지 않으면 메시지 출력하고 3초 후 종료하도록 처리(delayedFinish())하도록 만들어 줍니다.
또한 별 모양 버튼에 대한 클릭 리스너를 만들어서 클릭 시 플래시를 켜주거나 꺼주고(flashlight()), 플래시 켜짐 상태에 따른 별 모양 이미지(켜짐: 노란색 별, 꺼짐: 흰색 별)를 변경하도록 처리해줍니다.
package com.example.help.permissionflashlight;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private static Camera mCamera = null;
private ImageButton mImageButtonFlashOnOff;
private boolean mFlashOn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
Toast.makeText(getApplicationContext(), "There is no camera flash.\n The app will finish!", Toast.LENGTH_LONG).show();
delayedFinish();
return;
}
mImageButtonFlashOnOff = findViewById(R.id.ibFlashOnOff);
mImageButtonFlashOnOff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
flashlight();
mImageButtonFlashOnOff.setImageResource(mFlashOn ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);
}
});
}
private void delayedFinish() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 3000);
}
초기에는 플래시가 꺼진 상태이고, 카메라 인스턴스인 mCamera가 null이므로 onCreate에 있던 flashlight()는 openCamera()에서 카메라 객체의 인스턴스를 생성 및 연결하고 문제가 없으면 true로 리턴하고, 이후 mFlashOn를 토글시켜주어서 켜진 상태(mFlashOn => true)로 세팅됩니다.
그리고, 플래시를 Torch 모드로 파라미터 세팅을 해주고, 프리뷰를 시작해주면 플래시가 켜지게 됩니다.
반대로 플래시가 켜진 상태에서 openCamera()에 진입하면 이전에 mCamera가 생성된 상태라서 별다른 처리 없이 true로 리턴되고 mFlashOn이 토글되면서 꺼진 상태(mFlashOn => false)로 세팅됩니다.
꺼진 상태일 경우 프리뷰를 종료하도록 하고, 카메라 인스턴스를 릴리즈해줍니다. 이후 mCamera는 꼭 null로 만들어주어야 이후 오동작을 하지 않게 됩니다.
private boolean openCamera() {
if (mCamera == null) {
try {
mCamera = Camera.open();
} catch (RuntimeException e) {
Toast.makeText(getApplicationContext(), "Camera open failed", Toast.LENGTH_LONG).show();
e.printStackTrace();
return false;
}
}
return true;
}
private void flashlight() {
if (openCamera()) {
mFlashOn = !mFlashOn;
if (mFlashOn) {
Camera.Parameters params = mCamera.getParameters();
params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
mCamera.setParameters(params);
mCamera.startPreview();
} else {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
} else {
delayedFinish();
}
}
}
여기까지 작업 후 실행을 하게 되면 아래와 같은 에러를 만나게 되고 openCamera()의 try ~ catch 에 의해 강제 종료(Force Close)가 되지 않고, 메시지 출력 후 openCamera() 리턴을 false로 해서 3초 후 종료할 수 있게 되어 있습니다.
W 3858 3858 ServiceManager: Permission failure: android.permission.CAMERA from uid=10210 pid=3165
E 3858 3858 CameraService: Permission Denial: cant use the camera pid=3165, uid=10210
W 3165 3165 CameraBase: An error occurred while connecting to camera 0: Service not available
W 3165 3165 System.err: java.lang.RuntimeException: Fail to connect to camera service
W 3165 3165 System.err: at android.hardware.Camera.<init>(Camera.java:519)
W 3165 3165 System.err: at android.hardware.Camera.open(Camera.java:379)
W 3165 3165 System.err: at com.example.help.permissionflashlight.MainActivity.openCamera(MainActivity.java:50)
W 3165 3165 System.err: at com.example.help.permissionflashlight.MainActivity.flashlight(MainActivity.java:61)
W 3165 3165 System.err: at com.example.help.permissionflashlight.MainActivity$1.onClick(MainActivity.java:32)
위와 같은 현상이 일어난 것은 에러 메시지의 내용과 같이 카메라 권한이 없는 상태에서 접근했기 때문에 발생한 것입니다.
메니페스트(AndroidManifest.xml)
카메라 권한을 추가하기 위해서는 아래와 같이 <uses-permission android:name="android.permission.CAMERA" />를 메니페스트 파일에 넣어주면 됩니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.help.permissionflashlight">
<uses-permission android:name="android.permission.CAMERA" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
프로젝트 압축화일
PermissionFlashlight.zip
실행 결과
![](https://t1.daumcdn.net/cfile/tistory/992122345BABACB721)
cf) 플래시 끄기 방법2
플래시 끄기 1번의 방법은 stopPreview 및 release도 해주기 때문에 플래시가 꺼질 때 시간이 좀 걸립니다.
개선된 방법인 플래시 끄기 방법2에서는 카메라 플래시 모드를 FLASH_MODE_OFF로 파라미터 설정해주기만 하면 되기에 속도가 빠릅니다.
그 외에 몇 군데 다르게 처리해야 하는 부분들도 있습니다.
앞서 간단히 언급한 부분이기도 한데, 플래시 끄는 곳에서는 카메라 파라미터에 플래시 모드를 off 해주는 것 외에는 다른 처리를 하지 않고 있어서 카메라 프리뷰 종료 및 카메라 인스턴스를 릴리즈하는 코드는 onPause 쪽에 넣어주어야 합니다.
그리고, 그 코드가 onPause로 이동했기에 객체의 인스턴스 생성하는 코드는 onResume에 넣어주는 게 적당합니다.
자세한 코드는 소스를 첨부하니 참고하시길 바랍니다.
PermissionFlashlight2.zip
이상으로 "손전등 앱 만들기 첫 번째 - 개요 및 Camera API 이용방법"에 대해서 설명해 드렸습니다.
다음번에는 Camera2 API 이용해서 손전등 앱을 만들어 보도록 하겠습니다.
![](https://i1.daumcdn.net/deco/contents/emoticon/things_11.gif?v=2)
![](http://i1.daumcdn.net/deco/contents/emoticon/things_13.gif?v=2)
조금이나마 도움이 되셨으면 아래 ♡공감 버튼을 눌러주세요. ![](http://i1.daumcdn.net/deco/contents/emoticon/things_11.gif?v=2)
![](http://i1.daumcdn.net/deco/contents/emoticon/things_13.gif?v=2)
![](http://i1.daumcdn.net/deco/contents/emoticon/things_14.gif?v=2)
(If this article helps you, please press the ♡button below.)