=== 손전등 ===

 

- 기능 소개

앱에서 플래시를 켜고 끌 수 있음

위젯을 제공함으로써 앱을 실행하지 않고도 플래시를 켜고 끌 수 있음

 

 

- 주된 도구

Camera Manager (플래시 동작 제어)

Service (보이는 화면 없이 백그라운드에서 실행되는 컴포넌트)

App Widget (런처에 배치하여 앱의 기능을 빠르게 사용)

 

 

1. 프로젝트 생성

프로젝트 명 : Flashlight

minSdkVersion : 23 (Android 6.0 Marshmallow) - 6.0 이상에서 지원되는 방법이 제일 쉽기 때문임

   (5.0 미만 버전들에서는 공식적으로 기능이 제공되지 않으며 제조사별 방법도 다르고 매우 복잡함)

기본 액티비티 : Empty Activity

Anko 라이브러리 설정

 

 

 

2. 손전등 기능 구현

1) 별도의 클래스 파일로 Torch 클래스 작성

  File > New > 'Kotlin File/Class'   (파일명: Torch, 유형: class)

 

  -- Torch.kt

package com.tistory.flashlight

import android.content.Context
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager

class Torch(context: Context) {   // CameraManager 객체를 얻어야 하므로 Context를 생성자로 받았음
    private var cameraId: String? = null
    private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE)
            as CameraManager   // getSystemService()의 리턴값이 object형이므로 as로 형변환했음

    init {                         // 클래스가 초기화 될 때 실행됨
        cameraId = getCameraId()
    }

    fun flashOn() {
        if (cameraId != null) cameraManager.setTorchMode(cameraId!!, true)
    }

    fun flashOff() {
        if (cameraId != null) cameraManager.setTorchMode(cameraId!!, false)
    }

    private fun getCameraId():String? { // 카메라 ID는 각각의 내장 카메라에 부여된 고유의 ID이다
                                      // 카메라가 없다면 null을 반환해야 하므로 리턴형을 String?로 지정
        val cameraIds = cameraManager.cameraIdList   // 기기가 가진 모든 카메라 목록
        for (id in cameraIds) {
            val info = cameraManager.getCameraCharacteristics(id)
            val flashAvailable = info.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) // 플래시 가능 여부
            val lensFacing = info.get(CameraCharacteristics.LENS_FACING)   // 카메라 랜즈의 방향
            if (flashAvailable != null && flashAvailable && (lensFacing != null) && (lensFacing > 0)
                && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
                                                              // 플래시가 가능하고 카메라 방향이 뒷방향
                return id
            }
        }
        return null
    }
}

 

 

3. 액티비티에 스위치 제작

액티비티 정중앙에 switch 를 배치  (id: flashSwitch, text: '플래시 On/Off')

Switch는 두 가지 상태 값을 가지는 버튼 객체임

 

-- MainActivity 


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        val torch = Torch(this)

        flashSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
            if (isChecked) {
                torch.flashOn()
            } else {
                torch.flashOff()
            }
        }
    }
}

 

일단 동작을 한 번 확인해 봅시다!

(가운데 버튼을 켜면 플래시가 켜질 거예요)

 

 

4. 손전등 제어에 '서비스'를 활용

액티비티에서 했던 손전등 제어를 이제 서비스에서 구현해 봅시다. ('위젯'을 사용)

  (서비스는 이미 전술했듯 4대 컴포넌트의 하나이며 백그라운드에서 동작)

 

액티비티는 제어 기능은 그대로 살리되, 직접 제어 대신 서비스를 호출만 하도록 바꿔보죠.

  (즉 액티비티는 서비스 호출함으로써, 앱 위젯에서는 직접 서비스를 제어함으로써 Torch클래스를 동작시킴)

 

 

 

※ 안드로이드의 서비스

 

 

서비스 역시 액티비티처럼 생명주기용 콜백메서드들을 가짐

 

onStartCommand() : 일반적으로 실행할 작업을 여기에 작성함

 

onDestroy() : 서비스가 중지될 때 호출됨

  stopSelf()    서비스 내부에서 서비스 중지

  stopService() 서비스 외부에서 서비스 중지

 

 

 

 

 

 

 

 

 

 

 

 

 

위젯 / 서비스 사용

1) 서비스 생성

File > New > Service > Service      (클래스명 : TorchService)

 

2) TorchService 클래스 코딩

class TorchService : Service() {     // 자동으로 Service 클래스를 상속받는 군!

    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the service.")
    }

    // 본 TorchService 클래스에서 Torch 클래스를 사용할 것임
    // Torch 클래스의 인스턴스를 얻는 방법으로 onCreate() 와 by lazy 중 하나를 이용할 수 있음
    // onCreate() 콜백서비스를 이용하면 코드가 길어지므로 by lazy를 사용했음
    // by lazy는 torch 객체를 처음 사용할 때 아래 코드가 초기화됨
    private val torch: Torch by lazy {
        Torch(this)
    }

    // 외부에서 startService()로 본 TorchService 서비스를 호출하면
    // onStartCommand()가 호출됨
    // 보통 인텐트에 action 값을 저장하여 호출함
    // (참고: 서비스는 메모리 부족 등의 이유로 강제 종료될 수 있음)
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
 
        when (intent?.action) {

            // 앱에서 실행할 경우
            "on" -> {
                torch.flashOn()
            }

            "off" -> {
                torch.flashOff()
            }
        }

        return super.onStartCommand(intent, flags, startId)  // *1
    }
}

 
/* *1

  onStartCommand()의 반환값

  이 반환값들에 따라 시스템이 강제 종료된 후, 다시 서비스 복원을 어떻게 할지를 결정함

     START_STICKY (null 인텐트로 재시작. 무기한 실행 대기하는 미디어 플레이어에 적합)
     START_NOT_STICKY (재시작 안함)
     START_REDELIVER_INTENT (마지막 인텐트로 재시작. 능동적으로 수행 중인 파일 다운로드 서비스등에 적합)

 */
 

 

3) MainActivity.kt에서 torch 객체에 직접 접근해 스위치를 켜고 끄게 되어 있는 코드를

  인텐트에 "on" 또는 "off" 액션을 보내 TorchService으로 서비스를 시작하도록 수정.


flashSwitch.setOnCheckedChangeListener { buttonView, isChecked ->

      if (isChecked) {
         // torch.flashOn()
         startService(intentFor<TorchService>().setAction("on"))   // *1 Anko 코드
      } else {
         // torch.flashOff()
         startService(intentFor<TorchService>().setAction("off"))
      }
   }

 /* *1
   Anko를 사용하지 않는다면,

   val intent = Intent(this, TorchService::class.jave)
   intent.action = "on"
   startService(intent)
 */
 

 

 

5. 앱 위젯 작성

웹 위젯이란 런처에 배치하여 빠르게 앱 기능을 사용할 수 있게 해 주는 컴포넌트입니다.

간단히 '위젯' 이라고도 부릅니다.

 

1) 위젯 추가

  File > New > Widget > App Widget       (클래스명 : TorchAppWidget)

 

 

※ 위젯 작성 마법사 화면의 옵션 항목들

 

  Placement  위젯 배치 위치

    Home-screen only  (홈 화면에만 배치)

    Home-screen and Keyguard  (홈 화면과 잠금 화면에 배치)

    Keyguard only (API 17+)  (잠금 화면에만 배치)

 

 Resizable (API 12+)  위젯 크기 변경

   Horizontally and vertically  (가로 세로 크기 변경 가능)

   Only Horizontally

   Only vertically

   Not resizable

 

 Minimum Width (cells)  가로 크기를 1~4 중 선택

 Minimum Height (cells)  세로 크기를 1~4 중 선택

 Configuration Screen  위젯의 환경설정 액티비티를 생성

 Source Language  자바와 코틀린 중 선택

 

 

2) 생성된 위젯용 파일들

 

  TorchAppWidget.kt    위젯 동작 작성용 파일

  TorchAppWidget.xlm   위젯의 레이아웃 정의 파일

  dimens.xml dimens.xml (v14)   위젯의 여백 값이 API 14부터 바뀌었음. v14 버전 이하와 이상 분기 파일

  Torch_app_widget_info.xml    각종 설정용 파일

 

 

3) 위젯 레이아웃 수정 - layout/torch_app_widget.xml

  텍스트 뷰 속성 > text 속성 수정 버튼 클릭 > App_name 선택 > "Edit Translations"

  appwidget_text 문자열 값에 "손전등"

 

위젯 레이아웃 전체에 클릭 이벤트를 연결해야 하므로 전체 레이아웃 id를 정합시다.

트리 창에서 RelativeLayout을 선택 > id 속성 : appwidget_layout

 

 

4) TorchAppWidget.kt 코딩


class TorchAppWidget : AppWidgetProvider() {

    // AppWidgetProvider라는 일종의 브로드캐스트 리시버 클래스를 상속
    override fun onUpdate(    // 위젯이 업데이트 돼야 할 때 호출됨됨
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        // There may be multiple widgets active, so update all of them
        for (appWidgetId in appWidgetIds) {   // 위젯이 여러 개 라면 모든 위젯을 업데이트
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    override fun onEnabled(context: Context) {  // 위젯이 처음 생성될 때 호출됨
    }

    override fun onDisabled(context: Context) {  // 위젯이 여러개일 때 마지막 위젯이 제거될 때 호출됨
    }

    companion object {
        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)  // 위젯 전체 레이아웃 정보

            // 텍스트 값을 변경
            views.setTextViewText(R.id.appwidget_text, widgetText)

            // 위젯 클릭 시 처리할 작업
            val intent = Intent(context, TorchService::class.java)
            val pendingIntent = PendingIntent.getService(context, 0, intent, 0)
                   // *1   (각 0 : 사용하지 않을 때 0 값을 전달)

            // 클릭 이벤트 연력 (위젯 클릭시 위에서 정의한 인텐트 실행)
            views.setOnClickPendingIntent(R.id.appwidget_layout, pendingIntent)    //   *1

            // 레이아웃 수정이 완료되면 appWidgetManager을 사용해서 위젯을 업데이트 함
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}


/* *1
  PendingIntent 객체는 실행할 인텐트 정보를 가지고 있다가 실행함
  어떤 인텐트를 실행힐 지에 따라서 다음의 적당한 메서드를 사용해야 함

    PendingIntent.getActivity()    액티비티 실행
    PendingIntent.getService()    서비스 실행
    PendingIntent.getBroadcast()  브로드캐스트 실행
*/


  

 

 

5) TorchService.kt 수정

  TorchService는 인텐트에 on/off 액션을 지정해서 켜거나 껐으나 위젯은 어떤 경우가

  on인지 off인지 알 수 없으므로 액션을 지정할 수 없습니다. 때문에 액션이 지정되지 않아도

  플래시가 동작하도록 TorchService.kt를 수정해야 합니다.


class TorchService : Service() {
    ...

    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()
                }
            }
        }
        ...
    }
}


 

앱을 실행하면 위젯 코드가 반영됩니다.

앱을 실행했다가 바로 종료합시다.

휴대폰의 위젯 모음에 들어가보면 제작한 위젯이 추가되어 있을 것이다. 홈 화면에 끌어다 놓고 위젯으로

손전등을 동작 시켜 봅시다.

 

 

 참고) 위젯 모양 바꾸기

  xml/torch_app_widget_info.xml 파일에 위젯 모양에 대한 정보가 있습니다.

  drawable/example_appwidget_preview.png 아이콘이 지정되어 있음을 확인할 수 있네요.

 

 

 

※ 참고) 앱 위젯에 배치하는 뷰

앱 위젯에 배치하는 뷰는 정해져 있습니다.

 

1) 레이아웃으로 가능

   FrameLayout

   LinearLayout

   RelativeLayout

   GridLayout

 

2) 레이아웃에 배치 가능

   AnalogClock

   Button

   Chronometer

   ImageButton

   ImageView

   ProgressBar

   TextView

   ViewFlipper

   ListView

   GridView

   StackView

   AdapterViewFlipper

 

 

 

 

 

<<< 이전 글 보기      다음 글 보기 >>>

 

 

 

 

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

+ Recent posts