프로젝트명 | Flashlight |
기능 |
|
핵심구성요소 |
|
라이브러리 설정 | Anko : 인텐트, 다이얼로그, 로그 등을 구현하는 데 도움이 되는 라이브러리 |
|
준비하기
- 플래시를 켜는 방법은 안드로이드 6.0 (minSdkVersion : 23) 이상에서 제공하는 방법을 사용한다. 그 이유는 5.0 에서는 코드가 복잡하고, 5.0 미만에서는 공식적인 플래시 조작방법이 따로 없기 때문(제조사마다 다른 방법을 사용)이다.
- 애뮬레이터에서는 플래시가 없으므로 테스트는 안드로이드 6.0 이상의 기기에서 테스트 한다.
스텝1 손전등 기능구현
손전등 기능을 Torch 클래스에 작성하기
New -> Kotlin File/Class 클릭, 이름 Torch, 종류 Class 입력 후 OK 클릭 다음과 같이 코드를 작성한다.
- 플래시를 켜려면 CarmeraManager객체가 필요하고 이를 얻으려면 Context 객체가 필요하기 때문에 생성자로 Context인자를 받는다.
- 카메라를 켜고 끌때 카메라 ID가 필요한데, 클래스 초기화시 얻는다. 카메라ID는 기기에 내장된 카메라마다 고유한 ID가 부여된다.
- context의 getSystemService() 메서드는 안드로이드 시스템에서 제공하는 각종 서비스를 관리하는 매니저 클래스를 생성한다. 여기서는 CAMERA_SERVICE를 지정한다. Object형을 반환하기 때문에 CameraManager로 형변환 한다.
- 주석 8 에서 cameraManager.cameraIdList는 기기가 가지고 있는 모든 카메라에 대한 정보 목록을 제공한다.
- 주석 9는 각 ID별로 세부정보를 가지는 객체를 얻는다.
- 주석 11 : 플래시 가능여부를 알수 있다.
- 주석 12 : 카메라 랜즈방향을 알 수 있다.
- 주석 13,14 : 플래시가 가능하고 카메라 기기의 뒷면을 향하고 있는 카메라의 ID를 찾았다면 이 값을 반환한다.
- 주석 15 : 해당하는 카메라ID를 찾지 못했다면 null을 반환한다.
package abstractask.example.flashight
import android.content.Context
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
class Torch(context: Context) { // 1
private var cameraId: String? = null // 2
private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager // 3
init { // 4
cameraId = getCameraId()
}
fun flashOn(){ // 5
cameraManager.setTorchMode(cameraId.toString(), true)
}
fun flashOff(){ // 6
cameraManager.setTorchMode(cameraId.toString(), false)
}
private fun getCameraId(): String? { // 7
val cameraIds = cameraManager.cameraIdList // 8
for(id in cameraIds) { // 9
val info = cameraManager.getCameraCharacteristics(id) // 10
val flashAvailable = info.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) // 11
val lensFacing = info.get(CameraCharacteristics.LENS_FACING) // 12
if(flashAvailable != null
&& flashAvailable
&& lensFacing != null
&& lensFacing == CameraCharacteristics.LENS_FACING_BACK) { // 13
return id // 14
}
}
return null // 15
}
}
스텝2 액티비티에서 손전등 기능 사용
화면작성
배치 | Autoconnect 모드로 Switch를 레이아웃의 중앙에 배치 |
ID | flashSwitch |
text | 플래시 On/Off |
액티비티에서 손전등 켜기
- Torch클래스 인스턴스 화 한다.
- 스위치가 켜지면 flashOn() 꺼지면 flashOff()를 호출하여 플래시를 끈다.
package abstractask.example.flashight
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val torch = Torch(this) // 1
flashSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
if(isChecked) {
torch.flashOn() // 2
} else {
torch.flashOff() // 3
}
}
}
}
스텝3 서비스에서 손전등 기능 사용
앱 실행없이 위젯을 사용해 플래시는 켜는 기능도 만들어볼 수 있다. 이것은 액티비티가 아닌 서비스를 이용하면 조작 가능하다.
서비스 소개
서비스란 안드로이드 4대 컴포넌트 중 하난로 화면이 없고 백그라운드에서 수행하는 작업을 작성하는 컴포넌트이다.
서비스의 생명 주기
- onCreate() : 서비스가 생성될 때 호출되는 콜백 클래스이다. 초기화등을 수행한다.
- onStartCommand() : 서비스가 액티비티와 같은 다른 컴포넌트로 부터 startService() 메서드로 호출되면 불리는 콜백 메서드 이다. 실행할 작업을 여기에 작성한다.
- onDestroy() : 서비스 내부에서 stopSelf()를 호출하거나 외부에서 stopService()로 서비스를 종료하면 호출된다.
출처 및 참조 : https://developer.android.com/guide/components/services
서비스로 손전등 기능 옮기기
- File -> New -> Service -> Service를 클릭한다.
- 클래스명은 TorchService로 하고 Finish를 클릭한다.
- TorchService 클래스는 Service 클래스를 상속받는다.
- TorchService가 Torch클래스를 사용해야 한다. Torch 클래스의 인스턴스를 얻는 방법에는 onCreate메서드를 사용하는 방법과 by lazy를 사용하는 방법이 있다.
- 외부에서 onStartService() 메서드로 TorchService 서비스를 호출하면 onStartCommand() 콜백 메서드가 호출된다. 보통 인텐트에 action 값을 설정하여 호출하는데 "on"과 "off"문자열을 액션으로 받았을 때 when문을 사용하여 각각 플래시를 켜고 끄는 동작을 하도록 코드를 작성했다.
- 서비스는 메모리 부족등의 이유로 시스템에 의해서 강제 종료될 수 있다.onStartCommand() 메서드는 다음 중 하나를 반환한다. 이 값에 따라 시스템이 강제 종료한 후에 시스템 자원이 회복되어 다시 서비스를 시작할 수 있을때 어떻게 할지를 결정한다.
- START_STICKY : null 인텐트로 다시 시작한다. 명령을 실행하지는 않지만 무기한으로 실행 중이며 작업을 기다리고 있는 미디어 플레이어와 비슷한 경우에 적합하다.
- START_NOT_STICKY : 다시 시작하지 않음
- START_REDELIVER_INTENT : 마지막 인텐트로 다시 시작함. 능동적으로 수행 중인 파일 다운로드와 같은 서비스에 적합하다.
- 일반적인 경우에는 super.onStartCommand() 메서드를 호출하면 내부적으로 START_STICKY를 반환한다.
package abstractask.example.flashight
import android.app.Service
import android.content.Intent
import android.os.IBinder
class TorchService : Service() { // 1
private val torch: Torch by lazy { // 2
Torch(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when( intent?.action ) { // 3
// 앱에서 실행할 경우
"on" -> {
torch.flashOn()
}
"off" -> {
torch.flashOff()
}
}
return super.onStartCommand(intent, flags, startId) // 4
}
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
}
액티비티에서 서비스를 사용해 손전등 켜기
package abstractask.example.flashight
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import org.jetbrains.anko.intentFor
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/*
val torch = Torch(this)
flashSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
if(isChecked) {
torch.flashOn()
} else {
torch.flashOff()
}
*/
// 서비스로 변경
flashSwitch.setOnCheckedChangeListener { _, isChecked ->
if(isChecked) {
/*
val intent Intent(this, TorchService::class.java)
intent.action = "on"
startService(intent)
의 Anko 라이브러리 버젼
*/
startService(intentFor<TorchService>().setAction("on"))
} else {
startService(intentFor<TorchService>().setAction("off"))
}
}
}
}
스텝4 앱 위젯 작성
앱 위젯 추가
File -> New -> Widget -> App Widget을 클릭
항목 | 설명 |
Placement | 위젯을 어디에 배치하는지 설정
|
Resizable (API 12+) | 위젯 크기를 변경하는지 설정
|
Minimum Width (cells) | 가로 크기를 1 ~ 4중 선택한다. |
Minimum Height (cells) | 세로 크기를 1 ~ 4중 선택한다. |
Configuration Screen | 위젯 환경설정 액티비를 생성한다. |
Configuration Screen | 위젯 환경설정 액티비티를 생성한다. |
Source Language | 자바와 코틀린 중에서 선택 |
앱 위젯이 생성한 코드 살펴보기
- TorchAppWidget.kt : 앱 위젯을 클릭할 때 동작을 작성하는 파일
- torch_app_widget.xml : 앱 위젯의 레이아웃을 정의한 파일
- dimens.xml : 앱 위젯의 여백 값을 작성하는 파일 (API 14 버젼 부터는 여백값이 바뀌었기 때문에 두개의 파일로 분기되어 있다)
- torch_app_widget_info.xml : 앱 위젯의 각종 설정을 하는 파일
앱 위젯 레이아웃 수정
- strings.xml파일을 연다.
- Open editor를 클릭한다.
- Translations Editor가 열리고 appwidget_text를 "손전등" 으로 변경한다.
- torch_app_widget.xml파일 에서 ID가 appwidget_text인 TextView 에 "손전등" 으로 값이 변경된 것을 확인 할 수 있다.
- RelativeLayout의 컴포넌트는 ID속성을 appwidget_layout 으로 수정한다.
앱 위젯에서 손전등 켜기
TourchAppWidget.kt 파일 내에 자동적으로 작성된 파일의 모습이다.
package abstractask.example.flashight
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
// 앱 위젯용 파일은 AppWidgetProvider라는 일종의 브로드캐스트 리시버를 상속받는다.
class TorchAppWidget : AppWidgetProvider() {
// 위젯이 업데이트 되어야 할 때 호출된다.
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// 위젯이 여러개 배치되었다면 모든 위젯을 업데이트 한다.
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
}
// 위젯이 처음 생성뙬 때 호출된다.
override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
}
// 여러 개일 경우 마지막 위젯이 제거될 때 호출된다.
override fun onDisabled(context: Context) {
// Enter relevant functionality for when the last widget is disabled
}
}
// 위젯을 업데이트 할 때 수행되는 코드이다.
internal fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
val widgetText = context.getString(R.string.appwidget_text)
// 위젯은 액티비티에서 레이아웃을 다루는 것과 조금 다르다. 위젯에 패치하는 뷰는 따로 있는데,
// RemoteViews객체로 가져올 수 있다.
val views = RemoteViews(context.packageName, R.layout.torch_app_widget)
// setTextViewText()는 RemoteViews 객체용으로 텍스트 값을 변경하는 메서드이다.
views.setTextViewText(R.id.appwidget_text, widgetText)
// ==== 추가로 작성할 부분 ====
// 레이아웃을 모두 수정했다면 AppWidgetManager를 사용해 위젯을 업데이트 한다.
appWidgetManager.updateAppWidget(appWidgetId, views)
}
추가로 작성할 부분에 코드를 추가한 내용이다.
- 클릭 이벤트를 연결하려면 setOnClickPendingIntent() 메서드를 사용한다.
- 발생할 뷰의 ID
- PendingIntent 객체
- TorchService 서비스를 실행하는데 Pending.getService() 메서드를 사용한다.
- 사용한 인자는 다음과 같다.
- 컨텍스트
- 리퀘스트 코드 ( 사용하지 않음 0 )
- 서비스 인텐트 ( 주석 3의 intent )
- 플래그 ( 사용하지 않음 0 )
- PendingIntent는 실행할 인텐트 정보를 가지고 있다가 수행해준다. 어떤 인텐트를 실행할지에 따라서 다른 메서드를 사용해야 한다.
- PendingIntent.getActivity() : 액티비티 실행
- PendingIntent.getService() : 서비스 실행
- PendingIntent.getBroadcast() : 브로드캐스트 실행
- 사용한 인자는 다음과 같다.
- 위젯을 클릭하면 TorchService 서비스가 시작된다.
// 위젯을 업데이트 할 때 수행되는 코드이다.
internal fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
val widgetText = context.getString(R.string.appwidget_text)
// RemoteView객체를 구성
val views = RemoteViews(context.packageName, R.layout.torch_app_widget)
views.setTextViewText(R.id.appwidget_text, widgetText)
// ==== 추가로 작성한 부분 시작 ====
// 실행할 Intent 작성
val intent = Intent(context, TorchService::class.java) // 3
val pendingIntent = PendingIntent.getService(context, 0, intent, 0) // 2
//위젯을 클릭하면 위에서 정의한 Intent 실행
views.setOnClickPendingIntent(R.id.appwidget_layout, pendingIntent) // 1
// ==== 추가로 작성한 부분 끝 ====
// 위젯 관리자에게 위젯을 업데이트 하도록 지시
appWidgetManager.updateAppWidget(appWidgetId, views)
}
TorchService 서비스는 인텐트에 ON, OFF 액션을 지정해서 켜거나 껏다. 위젯의 경우 어떤 경우가 ON이고 OFF인지 알 수 없기 때문에 액션을 지정할 수 없다. 액션이 지정되지 않아도 플래시가 작동하도록 TorchService.kt파일을 수정해야 한다.
- 위젯에서 서비스가 시작될 때는 액션 값이 설정되지 않기 때문에 else문이 실행된다.
- 여기서 isRunning값에 따라서 플래시를 켜거나 끄는 동작이 결정된다.
package abstractask.example.flashight
import android.app.Service
import android.content.Intent
import android.os.IBinder
class TorchService : Service() {
private val torch: Torch by lazy {
Torch(this)
}
private var isRunning = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when( intent?.action ) {
// 앱에서 실행할 경우
"on" -> {
torch.flashOn()
isRunning = true
}
"off" -> {
torch.flashOff()
isRunning = false
}
// 서비스에서 실행할 경우
else -> {
isRunning = !isRunning
if(isRunning) {
torch.flashOn()
} else {
torch.flashOff()
}
}
}
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
}
더보기
앱 위젯에 배치하는 뷰
- 레이아웃 4가지
- FrameLayout
- LinearLayout
- RelativeLayout
- GridLayout
- 레이아웃에 베치하는 뷰 12가지
- AnalogClock
- Button
- Chronometer
- ImageButton
- ImageView
- ProgressBar
- TextView
- ViewFlipper
- ListView
- GridView
- StackView
- AdapterViewFlipper
앱 위젯 배치
아래 이미지는 실제 위젯선택시 이미지(오른쪽), 위젯이 배치되어 크기를 조절(왼쪽)한 것이다.
res/xml/torch_app_widget_info.xml 파일에 위젯에 관한 이미지가 설정되어 있고, 이미지를 바꾸고 싶으면 해당파일을 수정하면된다.
'책 > 오준석의 안드로이드 생존코딩 코틀린편' 카테고리의 다른 글
13. Todo 리스트 (0) | 2020.06.26 |
---|---|
12. 실로폰 (0) | 2020.06.24 |
10. 지도와 GPS (0) | 2020.06.21 |
8. 수평 측정기 (0) | 2020.06.16 |
7. 나만의 웹 브라우저 (0) | 2020.06.15 |