![](https://t1.daumcdn.net/cfile/tistory/99E567485BE3A2D603)
안녕하세요. Simple& Happy Dev입니다.
안드로이드폰에는 많은 센서가 존재합니다. 오늘은 그중에서 조도 센서, 근접 센서, 가속도 센서에 대해서 간략하게 알아보고 이것들을 조합한 앱도 만들어 보겠습니다.
[조도 센서]
빛에 민감하게 반응하는 소자를 이용한 센서로써 조도에 따른 처리를 위한 용도로 많이 사용되고 있습니다.
폰의 주변 조도에 따른 화면의 밝기 조절에 사용하는 것이 대표적인 예입니다.
폰에서 조도 센서의 주목적인 화면의 밝기에 관련이 있어서 폰의 상단 베젤 부분에 위치해 있습니다.
고사양 모델에 주로 들어갑니다.
센서 측정값의 변화가 있으면 onSensorChanged()를 호출해서 값을 넘겨줍니다.
이때 넘어오는 값은 lx(조도의 단위) 입니다.
관련 코드
단말기 제조업체에서는 폰에 조도 센서가 존재할 경우에 "android.hardware.sensor.light"를 정의해서 앱에서 아래의 feature를 사용할 수 있도록 합니다. 아랫 글에 보면 이와 관련해서 조금 더 자세한 내용이 있습니다.
2018/10/20 - Google Play에 등록한 앱이 특정 기기에서 검색되지 않는 이유 및 해결방법
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes a light sensor.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light";
조도 센서가 존재하는지 체크하는 방법 #1
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT)) { //use light sensor
}
조도 센서가 존재하는지 체크하는 방법 #2
Sensor lightSensor;
lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
if (lightSensor != null) {
//use light sensor
}
SensorEventListener 인터페이스의 리스너 등록
mSensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
SensorEventListener 인터페이스의 onSensorChanged() 구현
@Override
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
if (event.sensor.getType() == Sensor.TYPE_LIGHT) {
Log.d(TAG, "Light sensor value = " + event.values[0]);
}
}
[근접 센서]
센서 가까이 물체가 접근 시 감지할 수 있는 센서입니다.
근접 센서의 대표적인 예는 통화하려고 폰을 볼과 귀 가까이 댈 경우 통화 중의 발열이 인체에 피해를 줄이기 위해서 근접 센서로 통화상태에 볼과 귀 가까이 접근하게 되면 감지해서 화면이 꺼지게 만들어 줍니다. 이 동작은 화면을 꺼지게 해서 통화중의 소모전류를 줄여주는 역할도 합니다.
대부분의 폰에 들어가 있는 센서입니다. 폰의 상단 베젤에 위치하며, 보통 조도 센서가 있는 경우 바로 옆에 존재합니다.
센서 측정값의 변화가 있으면 onSensorChanged()를 호출해서 값을 넘겨줍니다.
이때 넘어오는 값은 cm입니다.
관련 코드
단말기 제조업체에서는 폰에 조도 센서가 존재할 경우에 "android.hardware.sensor.proximity"를 정의해서 앱에서 아래의 feature를 사용할 수 있도록 합니다.
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes a proximity sensor.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity";
근접 센서가 존재하는지 체크하는 방법 #1
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_PROXIMITY)) { //use proximity sensor
}
근접 센서가 존재하는지 체크하는 방법 #2
Sensor proximitySensor;
proximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
if (proximitySensor != null) {
//use proximity sensor
}
SensorEventListener 인터페이스의 리스너 등록
mSensorManager.registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
SensorEventListener 인터페이스의 onSensorChanged() 구현
@Override
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
Log.d(TAG, "Proximity sensor value = " + event.values[0]);
}
}
[가속도 센서]
3축에 대한 가속도를 측정할 수 있는 센서입니다.
가속도 센서는 폰의 방향을 감지하고 동작을 인식하는 데 주로 사용됩니다.
센서 측정값의 변화가 있으면 onSensorChanged()를 호출해서 값을 넘겨줍니다.
3축(x,y,z)에 대한 값들은 event.values[0], event.values[1], event.values[2]로 각각 전달됩니다.
이때 넘어오는 값은 ㎨(가속도의 단위) 입니다.
관련 코드
단말기 제조업체에서는 폰에 조도 센서가 존재할 경우에 "android.hardware.sensor.accelerometer"를 정의해서 앱에서 아래의 feature를 사용할 수 있도록 합니다.
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes an accelerometer.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
가속도 센서가 존재하는지 체크하는 방법 #1
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_ACCELEROMETER)) {
//use accelerometer sensor
}
가속도 센서가 존재하는지 체크하는 방법 #2
Sensor accSensor;
accSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (accSensor != null) {
//use accelerometer sensor
}
SensorEventListener 인터페이스의 리스너 등록
mSensorManager.registerListener(this, accSensor, SensorManager.SENSOR_DELAY_NORMAL);
SensorEventListener 인터페이스의 onSensorChanged() 구현
@Override
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
Log.d(TAG, "Accelerometer sensor value = " + event.values[0] + ", " + event.values[1] + ", " + event.values[2]);
}
}
▣응용 예제▣
위의 세 가지 센서를 모두 이용해서 플래시가 동작하는 앱을 만들어 보겠습니다.
각 센서의 역할
- 조도 센서: 주변 밝기에 따른 플래시 On/Off
- 근접 센서: 폰에 손바닥 접근 정도에 따른 플래시 On/Off
- 가속도 센서: 폰을 흔들면 서비스를 종료하도록 처리
동작 과정
- 앱 실행시 조도 센서가 있으면 먼저 조도 센서를 선택하고 없으면 근접 센서를 선택하게 합니다. (둘 중에 하나만 선택)
- 조도 센서 및 근접 센서는 서비스로 동작합니다. 앱을 종료해도 백그라운드에서 계속 동작하도록 합니다.
- 조도 센서의 경우 어두워진 상황이면 자동으로 플래시가 켜지도록 합니다. 다시 밝아지면 끄도록 합니다.
- 근접 센서의 경우 근접 센서 가까이 손바닥이 오면 플래시가 켜지도록 합니다. 손바닥이 멀어지면 끄도록 합니다.
- 백그라운드로 동작 중인 서비스의 경우 가속도 센서를 이용해서 흔들면 서비스를 종료하도록 합니다.
레이아웃 (activity_main.xml)
액티비티는 센서와 플래시 작동을 위한 서비스를 시작하게 하는 버튼과 그 아래에 도움말 텍스트로 구성되어 있습니다.
<?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">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="Sensor service"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvHowItWorks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text=""
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
<TextView
android:id="@+id/tvShakeYourPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="- Shake your phone to stop the service."
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvHowItWorks" />
</android.support.constraint.ConstraintLayout>
자바 (MainActivity.java)
서비스 종료 시 인텐트(ACTION_SERVICE_STOP)를 받아서 토스트를 띄우고 3초 후에 앱을 종료하게 만듭니다.
public class MainActivity extends AppCompatActivity {
private static final String ACTION_SERVICE_STOP = "com.tistory.gh0st.sensorapplication.servicestop";
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_SERVICE_STOP.equals(action)) {
Toast.makeText(getApplicationContext(), "You shook to stop the service..\n The app will finish!", Toast.LENGTH_LONG).show();
delayedFinish();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SERVICE_STOP);
registerReceiver(mIntentReceiver, filter);
}
private void delayedFinish() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 3000);
}
}
존재하는 센서 종류에 따라서 버튼 내의 문구와 도움말 텍스트가 달라지도록 처리합니다.
서비스 시작 버튼을 누르게 되면 버튼을 비활성화시키고, 도움말 문구는 보이도록 만들고, 센서 서비스를 시작합니다.
public class MainActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tvHowItWorks = findViewById(R.id.tvHowItWorks);
final TextView tvShakeYourPhone = findViewById(R.id.tvShakeYourPhone);
final Button startButton = findViewById(R.id.button);
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT)) {
startButton.setText("[Light sensor] service");
tvHowItWorks.setText("- Please adjust the light around the phone.");
} else if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_PROXIMITY)) {
startButton.setText("[Proximity sensor] service");
tvHowItWorks.setText("- Please take your hand near the proximity sensor.");
} else {
startButton.setEnabled(false);
Toast.makeText(getApplicationContext(), "There is no light sensor or proximity sensor.\n The app will finish!"
, Toast.LENGTH_LONG).show();
delayedFinish();
}
startButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startButton.setEnabled(false);
tvHowItWorks.setVisibility(View.VISIBLE);
tvShakeYourPhone.setVisibility(View.VISIBLE);
startService(new Intent(getApplicationContext(), SensorService.class));
}
});
}
...
}
자바 (SensorService.java)
서비스 동작 시 먼저 onStartCommand()가 호출됩니다.
여기서는 존재하는 센서에 따라서 센서 객체의 인스턴스를 얻고, 이벤트 리스너를 등록합니다.
앞서 언급했듯이 조도 센서나 근접 센서 두개 중의 하나가 사용됩니다. 즉, 조도 센서 + 가속도 센서 또는 근접 센서 + 가속도 센서 조합으로 사용됩니다.
근접 센서의 경우 주변의 물체를 인지할 수 있는 기준 거리를 구합니다. (5cm 또는 센서의 최대값 중의 작은 값)
public class SensorService extends Service implements SensorEventListener {
private static final String TAG = "SensorService";
private static final String ACTION_SERVICE_STOP = "com.tistory.gh0st.sensorapplication.servicestop";
private static final boolean ON = true;
private static final boolean OFF = false;
private static final float LIGHT_DARK_THRESHOLD = 0.0f;
private static final float LIGHT_BRIGHT_THRESHOLD = 30.0f;
private static final float PROXIMITY_THRESHOLD = 5.0f; //5cm
private static final float SHAKE_THRESHOLD = 20.0f;
private SensorManager mSensorManager;
private float mProximityThreshold;
private CameraManager mCameraManager;
private String mCameraId;
public SensorService() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Sensor proximitySensor;
Sensor lightSensor;
Sensor accSensor;
mSensorManager = (SensorManager) getApplicationContext().getSystemService(SENSOR_SERVICE);
mCameraManager = (CameraManager) getApplicationContext().getSystemService(CAMERA_SERVICE);
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT)) {
Log.d(TAG, "App will use the Light sensor!");
lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
mSensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
} else {
Log.d(TAG, "Light sensor does not exist!!!");
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_PROXIMITY)) {
Log.d(TAG, "App will use the Proximity sensor!");
proximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
if (proximitySensor != null) {
mProximityThreshold = Math.min(proximitySensor.getMaximumRange(), PROXIMITY_THRESHOLD);
}
mSensorManager.registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
} else {
Log.d(TAG, "Proximity sensor also does not exist!!!");
}
}
accSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, accSensor, SensorManager.SENSOR_DELAY_NORMAL);
return super.onStartCommand(intent, flags, startId);
}
SensorEventListener 인터페이스의 onSensorChanged() 구현은 센서별로 나눠서 처리하도록 합니다.
조도 센서의 경우 센서 이벤트값이 0인 경우(Dark)에 플래시를 켜줍니다. 30 이상 인 경우(Bright)에는 플래시를 꺼줍니다.
근접 센서의 경우 센서 이벤트값이 기준 영역(0~5cm) 이하이면 물체가 접근한 상태(positive=true)이며, 그 외의 경우는 접근하지 않는 상태(positive=false)입니다. 이때, 접근 여부에 따라서 플래시를 켜주도록 합니다.
가속도 센서의 경우 센서 이벤트값인 x, y, z 축의 가속도 값이 20.0 이상이면 흔들었다고 판단해서 플래시를 꺼주고, 종료 인지를 위해서 진동을 발생시킵니다.
그리고, 이벤트 리스너 등록을 해지하고, 서비스를 멈추게(stopSelf()) 만들고, ACTION_SERVICE_STOP 인텐트를 날려서, 이후 액티비티에서 이를 받아서 앱을 종료하도록 만듭니다.
@Override
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
if (event.sensor.getType() == Sensor.TYPE_LIGHT) {
Log.d(TAG, "Light sensor value = " + event.values[0]);
if (event.values[0] == LIGHT_DARK_THRESHOLD) {
flashlight(ON);
} else if(event.values[0] >= LIGHT_BRIGHT_THRESHOLD) {
flashlight(OFF);
}
} else if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
Log.d(TAG, "Proximity sensor value = " + event.values[0]);
final float distance = event.values[0];
boolean positive = distance >= 0.0f && distance < mProximityThreshold;
flashlight(positive);
} else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
Log.d(TAG, "Accelerometer sensor value = " + event.values[0] + ", " + event.values[1] + ", " + event.values[2]);
float accVal;
accVal = (float)Math.sqrt(Math.pow(event.values[0], 2) + Math.pow(event.values[1], 2) + Math.pow(event.values[2], 2));
Log.d(TAG, "Accelerometer sensor acc = " + accVal);
if (accVal >= SHAKE_THRESHOLD) {
flashlight(OFF);
Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
try {
vibrator.vibrate(500, null);
} catch (Exception e) {
Log.w(TAG, "Failed to vibrate during stop service.", e);
}
mSensorManager.unregisterListener(this);
stopSelf();
sendBroadcast(new Intent(ACTION_SERVICE_STOP));
}
}
}
FlashAPI를 이용해서 플래시를 제어하는 부분입니다.
이와 관련해서는 아래를 참조하면 됩니다.
2018/10/15 - 손전등 앱 만들기 #3 - Flashlight API 이용방법 (Flashlight app using Flashlight API)
void flashlight(boolean state) {
if (mCameraId == null) {
try {
for (String id : mCameraManager.getCameraIdList()) {
CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
if (flashAvailable != null && flashAvailable
&& lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
mCameraId = id;
break;
}
}
} catch (CameraAccessException e) {
mCameraId = null;
e.printStackTrace();
mSensorManager.unregisterListener(this);
stopSelf();
return;
}
}
try {
mCameraManager.setTorchMode(mCameraId, state);
} catch (CameraAccessException e) {
e.printStackTrace();
mSensorManager.unregisterListener(this);
stopSelf();
}
}
메니페스트(AndroidManifest.xml)
종료 인지를 위해서 사용하는 진동의 경우 android.permission.VIBRATE 권한 사용을 추가해줘야 합니다.
uses-feature 부분들은 구글 Play에 등록하지 않는다면 빼주어도 무방합니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tistory.gh0st.sensorapplication">
<uses-feature android:name="android.hardware.sensor.light" android:required="false" />
<uses-feature android:name="android.hardware.sensor.proximity" android:required="true" />
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.flash" />
<uses-permission android:name="android.permission.VIBRATE" />
<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>
<service
android:name=".SensorService"
android:enabled="true"
android:exported="false"></service>
</application>
</manifest>
프로젝트 압축화일
SensorApplication.zip
이상으로 조도/근접/가속도 센서에 대해서 간략하게 알아보았고, 이들을 응용한 앱도 만들어 보았습니다.
앞서보셨듯이 생각보다 어렵지 않습니다. 다만 센서의 경우 종료 시 이벤트 리스너 등록 해지와 같은 리소스 정리를 깔끔하게 해주셔야 소모전류, 성능 이슈 발생하지 않는다는 점 꼭 기억하시길 바랍니다. 이상으로 글을 마무리합니다. 감사합니다.
![](https://i1.daumcdn.net/deco/contents/emoticon/things_11.gif?v=2)
![](https://i1.daumcdn.net/deco/contents/emoticon/things_13.gif?v=2)
조금이나마 도움이 되셨으면 아래 ♡공감 버튼을 눌러주세요. ![](https://i1.daumcdn.net/deco/contents/emoticon/things_11.gif?v=2)
![](https://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.)