๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Study/Android

[์•ˆ๋“œ๋กœ์ด๋“œ ์ฝ”ํ‹€๋ฆฐ] 11. ์‹ค์ „ํ”„๋กœ์ ํŠธ(4) - ์†์ „๋“ฑ / + ์„œ๋น„์Šค

์‹ค์ „ ํ”„๋กœ์ ํŠธ

์‹ค์Šต - ์†์ „๋“ฑ

์ฃผ์š”๊ตฌ์„ฑ

- ์†์ „๋“ฑ ์•ฑ์„ ์•ฑ๊ณผ ์œ„์ ฏ์œผ๋กœ ๊ตฌ์„ฑ

- ์•ฑ๊ณผ ์œ„์ ฏ ๋ชจ๋‘ ์†์ „๋“ฑ์„ ์ผœ๊ณ  ๋„๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋งŒ์„ ์ œ๊ณตํ•˜๊ณ  ํ•ต์‹ฌ ๊ธฐ๋Šฅ์€ ์„œ๋น„์Šค์—์„œ ์ˆ˜ํ–‰

 

โ€ป ์†์ „๋“ฑ ๊ธฐ๋Šฅ์€ ๊ฐ€์ƒ๊ธฐ๊ธฐ๋กœ ๋ถˆ๊ฐ€!

์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค์™€ ํœด๋Œ€ํฐ ์—ฐ๊ฒฐ ๋ฐฉ๋ฒ•

https://curryyou.tistory.com/375

 

1. ์†์ „๋“ฑ ๊ธฐ๋Šฅ ๊ตฌํ˜„

์†์ „๋“ฑ ๊ธฐ๋Šฅ์„ Torch ํด๋ž˜์Šค ์ž‘์„ฑ

- ์†์ „๋“ฑ ๊ธฐ๋Šฅ์„ ๋ณ„๋„์˜ ํด๋ž˜์Šค๋กœ ๋ถ„๋ฆฌํ•ด์„œ Torch ํด๋ž˜์Šค๋ฅผ ์ƒˆ๋กœ ์ž‘์„ฑ

 

โ‘  ํ”„๋กœ์ ํŠธ ์ฐฝ์˜ ํŒจํ‚ค์ง€๋ช…์—์„œ ๋งˆ์šฐ์Šค ์šฐํด๋ฆญ์„ ํ•˜์—ฌ New → Kotlin File/Class๋ฅผ ํด๋ฆญํ•˜๋ฉด ํŒŒ์ผ ์ข…๋ฅ˜์™€ ์ด๋ฆ„์„ ๊ฒฐ์ •ํ•˜๋Š” ํ™”๋ฉด์ด ํ‘œ์‹œ

โ‘ก ํŒŒ์ผ ์ด๋ฆ„์œผ๋กœ Torch๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์ข…๋ฅ˜๋Š” Class๋ฅผ ์„ ํƒํ•˜์—ฌ OK๋ฅผ ํด๋ฆญ

 

โ‘  ์ด ํด๋ž˜์Šค๋Š” ํ”Œ๋ž˜์‹œ๋ฅผ ์ผœ๋Š” flashOn( ) ์™€ ํ”Œ๋ž˜์‹œ๋ฅผ ๋„๋Š” flashOff ( ) ์ œ๊ณต

โ‘ก ํ”Œ๋ž˜์‹œ๋ฅผ ์ผœ๋ ค๋ฉด CameraManager ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๊ณ  ์ด๋ฅผ ์–ป์œผ๋ ค๋ฉด Context ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒ์„ฑ์ž๋กœ Context ๋ฅผ ๋ฐ›์Œ

โ‘ข ์นด๋ฉ”๋ผ๋ฅผ ์ผœ๊ณ  ๋Œ ๋•Œ ์นด๋ฉ”๋ผ ID ๊ฐ€ ํ•„์š”ํ•จ

โ‘ฃ ํด๋ž˜์Šค ์ดˆ๊ธฐํ™” ๋•Œ ์นด๋ฉ”๋ผ ID ๋ฅผ ์–ป์–ด์•ผ ํ•จ

โ‘ค context ์˜ getSystemService( ) : ์•ˆ๋“œ๋กœ์ด๋“œ ์‹œ์Šคํ…œ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฐ์ข… ์„œ๋น„์Šค๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋งค๋‹ˆ์ € ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ

โ‘ฅ getCameraId( ) : ์นด๋ฉ”๋ผ์˜ ID ๋ฅผ ์–ป๋Š” ๋ฉ”์„œ๋“œ

โ‘ฆ CameraManager ๋Š” ๊ธฐ๊ธฐ๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ชจ๋“  ์นด๋ฉ”๋ผ์— ๋Œ€ํ•œ ์ •๋ณด ๋ชฉ๋ก์„ ์ œ๊ณต

โ‘ง ์ด ๋ชฉ๋ก์„ ์ˆœํšŒํ•˜๋ฉด์„œ ๊ฐ ID๋ณ„๋กœ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๋Š” ๊ฐ์ฒด๋ฅผ ์–ป์Œ

โ‘จ ๊ฐ์ฒด๋กœ๋ถ€ํ„ฐ ํ”Œ๋ž˜์‹œ ๊ฐ€๋Šฅ ์—ฌ๋ถ€์™€ ์นด๋ฉ”๋ผ์˜ ๋ Œ์ฆˆ ๋ฐฉํ–ฅ์„ ์•Œ ์ˆ˜ ์žˆ์Œ

โ‘ฉ ํ”Œ๋ž˜์‹œ๊ฐ€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ณ  ๊ธฐ๊ธฐ์˜ ๋’ท๋ฉด์„ ํ–ฅํ•˜๊ณ  ์žˆ๋Š” ์นด๋ฉ”๋ผ์˜ ID ๋ฅผ ์ฐพ์•˜๋‹ค๋ฉด ์ด ๊ฐ’์„ ๋ฐ˜ํ™˜

โ‘ช ํ•ด๋‹นํ•˜๋Š” ์นด๋ฉ”๋ผ ID ๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ๋‹ค๋ฉด โ‘ฎ null ์„ ๋ฐ˜ํ™˜ํ•จ

 

package com.example.flashlight

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

class Torch (context: Context) {    // ์นด๋ฉ”๋ผ ์ œ์–ด๋ฅผ ์œ„ํ•œ CameraManager ๊ฐ์ฒด๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด Context ๊ฐ์ฒด ํ•„์š” -> ์ƒ์„ฑ์ž๋กœ Context ๋ฐ›์Œ

    private var cameraId: String? = null    // ์นด๋ฉ”๋ผ ๊ฐ’์˜ id ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜
    private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager

    init {          // ์ดˆ๊ธฐ ์„ค์ •
        cameraId = getCameraID()
    }

    fun flashOn() {
        cameraId?.let { cameraManager.setTorchMode(it, true) }    // it: ์นด๋ฉ”๋ผ id
    }

    fun flashOff() {
        cameraId?.let { cameraManager.setTorchMode(it, false) }
    }

    // ์กฐ๊ฑด์— ๋งž๋Š” ์นด๋ฉ”๋ผ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฉ”์†Œ๋“œ
    private fun getCameraID(): String? {
        val cameraIds = cameraManager.cameraIdList      // ์นด๋ฉ”๋ผ id ๋ฆฌ์ŠคํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
        for (id in cameraIds) {     // for ๋ฌธ์„ ์ด์šฉํ•ด ์กฐ๊ฑด์— ๋งž๋Š” id ์ฐพ๊ธฐ
            val info = cameraManager.getCameraCharacteristics(id)    // ์นด๋ฉ”๋ผ id๋กœ๋ถ€ํ„ฐ ์ข€ ๋” ์ƒ์„ธํ•œ ์ •๋ณด ์–ป์–ด์˜ค๊ธฐ
            val flashAvailable = info.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)    // ํ”Œ๋ž˜์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ์ง€ ํ™•์ธํ•˜๊ธฐ(True: ๋ฐ˜ํ™˜๊ฐ€๋Šฅ)
            val lensFacing = info.get(CameraCharacteristics.LENS_FACING)    // ๋ Œ์ฆˆ ๋ฐฉํ–ฅ ํ™•์ธํ•˜๊ธฐ(1: Back)
            if(flashAvailable !== null && flashAvailable
                && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK){     // ํ”Œ๋ž˜์‹œ ์‚ฌ์šฉ๊ฐ€๋Šฅ, ๋ Œ์ฆˆ ํ›„๋ฉด
                return id
            }
        }
        return null
    }

}

 

[์ฐธ๊ณ ] Camera2 API ํ™œ์šฉ ์ˆœ์„œ
CameraManager ๊ฐ์ฒด ์–ป๊ธฐ
- getSystemService ( ) 
    - ์นด๋ฉ”๋ผ ํ”Œ๋ž˜์‹œ ์ œ์–ด
    - ๋งค๊ฐœ๊ฐ’: CAMERA_SERVICE (์นด๋ฉ”๋ผ ์„œ๋น„์Šค ๊ฐ’)

์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ์นด๋ฉ”๋ผ์˜ id ์–ป๊ธฐ 
- getCameraIdList() 
    - ์ „๋ฉด, ํ›„๋ฉด ๊ฐ๊ฐ 1๊ฐœ  2๊ฐœ ์‚ฌ์šฉ ์˜ˆ์ •

์นด๋ฉ”๋ผ ์ •๋ณด ์–ป๊ธฐ 
- getCameraCharacteristics(cameraId) 
- CameraCharacteristics ๊ฐ์ฒด ์–ป์Œ 
     characteristics.LENS_FACING : ์นด๋ฉ”๋ผ ๋ Œ์ฆˆ ๋ฐฉํ–ฅ
        - LENS_FACING_FRONT: ์ „๋ฉด ์นด๋ฉ”๋ผ. value : 0
        - LENS_FACING_BACK: ํ›„๋ฉด ์นด๋ฉ”๋ผ. value : 1 
        - LENS_FACING_EXTERNAL: ๊ธฐํƒ€ ์นด๋ฉ”๋ผ. value : 2 
     FLASH_INFO_AVAILABLE : ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ”Œ๋ž˜์‹œ ์œ ๋ฌด

 

2. ์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ์†์ „๋“ฑ ๊ธฐ๋Šฅ ๊ตฌํ˜„

ํ™”๋ฉด ์ž‘์„ฑ

 

Switch ์†Œ๊ฐœ 

- Switch๋Š” ์ผœ๊ฑฐ๋‚˜ ๋„๋Š” ๋‘ ๊ฐ€์ง€ ์ƒํƒœ ๊ฐ’์„ ๊ฐ€์ง€๋Š” ๋ฒ„ํŠผ ๊ฐ์ฒด

- setOnChecked ChangeListener๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰

- buttonView ์ธ์ž๋Š” ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋œ Switch ๊ฐ์ฒด ์ž์‹ ์ด๊ณ , isChecked ์ธ์ž๋Š” On/Off ์ƒํƒœ๋ฅผ Boolean์œผ๋กœ ํ‘œ์‹œํ•˜์—ฌ ๊ฐ๊ฐ(์ผœ์กŒ์„ ๋•Œ/๊บผ์กŒ์„ ๋•Œ)์— ๋Œ€ํ•ด ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Œ

 

์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ์†์ „๋“ฑ ์ผœ๊ธฐ

์Šค์œ„์น˜์˜ ์ƒํƒœ์— ๋”ฐ๋ผ ํ”Œ๋ž˜์‹œ๋ฅผ ์ผœ๊ณ  ๋„๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€

โ‘  ์ž‘์„ฑํ•œ Torch ํด๋ž˜์Šค๋ฅผ ์ธ์Šคํ„ด์Šคํ™”ํ•จ 

โ‘ก ์Šค์œ„์น˜๊ฐ€ ์ผœ์ง€๋ฉด flashOn ( ) ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ”Œ๋ž˜์‹œ๋ฅผ ์ผฌ 

โ‘ข ์Šค์œ„์น˜๊ฐ€ ๊บผ์ง€๋ฉด flashOff ( ) ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ”Œ๋ž˜์‹œ๋ฅผ ๋”

- ์•ฑ์„ ์•ˆ๋“œ๋กœ์ด๋“œ 6.0 ์ด์ƒ ์‹ค์ œ ๊ธฐ๊ธฐ์—์„œ ์‹คํ–‰ํ•˜์—ฌ ํ”Œ๋ž˜์‹œ๊ฐ€ ๋™์ž‘ ์—ฌ๋ถ€ ํ™•์ธ

class MainActivity : AppCompatActivity() {

    lateinit var flahSwitch: Switch

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        flahSwitch = findViewById(R.id.flashSwitch)
        val torch = Torch(this)    // Torch ํด๋ž˜์Šค์˜ ๊ธฐ๋Šฅ์„ ๊ฐ€์ ธ๋‹ค ์“ฐ๊ธฐ ์œ„ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ

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

 

3. ์„œ๋น„์Šค์—์„œ ์†์ „๋“ฑ ๊ธฐ๋Šฅ ์‚ฌ์šฉ

์„œ๋น„์Šค ์†Œ๊ฐœ

- ์•ˆ๋“œ๋กœ์ด๋“œ์˜ 4๋Œ€ ์ปดํฌ๋„ŒํŠธ ์ค‘ ํ•˜๋‚˜๋กœ ํ™”๋ฉด์ด ์—†๊ณ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ˆ˜ํ–‰ํ•˜๋Š” ์ž‘์—…์„ ์ž‘์„ฑํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ

์„œ๋น„์Šค์˜ ์ƒ๋ช…์ฃผ๊ธฐ

→ ์Šคํƒ€ํ‹ฐ์Šค ์„œ๋น„์Šค, ๋ฐ”์šด๋“œ ์„œ๋น„์Šค (์–ด๋Š ์„œ๋น„์Šค์ธ์ง€ ๋ช…์‹œ๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ ์Šคํƒ€ํ‹ฐ๋“œ ์„œ๋น„์Šค์ž„)

- ์„œ๋น„์Šค๋Š” ์•กํ‹ฐ๋น„ํ‹ฐ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ƒ๋ช…์ฃผ๊ธฐ์šฉ ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Œ

    - onCreate( ) : ์„œ๋น„์Šค๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ๋กœ ์ดˆ๊ธฐํ™” ๋“ฑ ์ˆ˜ํ–‰ 

    - onStartCommand( ) : ์„œ๋น„์Šค๊ฐ€ ์•กํ‹ฐ๋น„ํ‹ฐ์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋กœ๋ถ€ํ„ฐ startService ( ) ๋ฉ”์„œ๋“œ๋กœ ํ˜ธ์ถœ๋˜๋ฉด ๋ถˆ๋ฆฌ๋Š” ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ์ž„. ์‹คํ–‰ํ•  ์ž‘์—…์„ ์—ฌ๊ธฐ์— ์ž‘์„ฑ 

    - onDestroy( ) : ์„œ๋น„์Šค ๋‚ด๋ถ€์—์„œ stopSelf ( )๋ฅผ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ ์™ธ๋ถ€์—์„œ stopService ( )๋กœ ์„œ๋น„์Šค๋ฅผ ์ข…๋ฃŒํ•˜๋ฉด ํ˜ธ์ถœ๋จ

 

์„œ๋น„์Šค๋กœ ์†์ „๋“ฑ ๊ธฐ๋Šฅ ์˜ฎ๊ธฐ๊ธฐ

- ์šฐํด๋ฆญ ๋˜๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค์˜ ๋ฉ”๋‰ด ์ค‘ File → New → Service → Service ํด๋ฆญ

 

 

TorchService.kt์— ์ฝ”๋“œ ์ถ”๊ฐ€ 

โ‘  TorchService๋Š” Service ํด๋ž˜์Šค ์ƒ์† 

โ‘ก TorchService๊ฐ€ Torch ํด๋ž˜์Šค ์‚ฌ์šฉ

โ‘ขTorch ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์–ป๋Š” ๋ฐฉ๋ฒ•์—๋Š” onCreate( ) ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ by lazy๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Œ

โ‘ฃ ์™ธ๋ถ€์—์„œ startService ( ) ๋ฉ”์„œ๋“œ๋กœ TorchService ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด onStartCommand( ) ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋จ

โ‘ค onStartCommand( ) ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ๋ฐ˜ํ™˜

    - START_STICKY: null ์ธํ…ํŠธ๋กœ ๋‹ค์‹œ ์‹œ์ž‘ ex. ๋ฏธ๋””์–ดํ”Œ๋ ˆ์ด์–ด

    - START_NOT_STICKY: ๋‹ค์‹œ ์‹œ์ž‘ํ•˜์ง€ ์•Š์Œ

    - START_REDELIVER_INTENT: ๋งˆ์ง€๋ง‰ ์ธํ…ํŠธ๋กœ ๋‹ค์‹œ ์‹œ์ž‘ํ•จ ex. ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ

package com.example.flashlight

import android.app.Service
import android.content.Intent
import android.os.IBinder

class TorchService : Service() {

    private val torch: Torch by lazy {      // by lazy๋Š” val ํƒ€์ž…์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
        Torch(this)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        when (intent?.action) {        // intent๋กœ ๊ฐ’ ๋„˜์–ด์˜ค๋Š”๋ฐ action๊ฐ’์— ์˜ํ•ด ๊ฒฐ์ •๋จ
            "on" -> {
                torch.flashOn()
            }
            "off" -> {
                torch.flashOff()
            }
        }

        return super.onStartCommand(intent, flags, startId)
    }

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

 

์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•ด ์†์ „๋“ฑ ์ผœ๊ธฐ

- ์„œ๋น„์Šค๋ฅผ ์‹œ์ž‘ํ•˜๋ ค๋ฉด startService ( ) ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ

- TorchService๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ”Œ๋ž˜์‹œ๋ฅผ ์ผœ๋Š” ์ธํ…ํŠธ์— “on” ์•ก์…˜์„ ์„ค์ •ํ•˜์—ฌ ์„œ๋น„์Šค๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ์ฝ”๋“œ

class MainActivity : AppCompatActivity() {

    lateinit var flahSwitch: Switch

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        flahSwitch = findViewById(R.id.flashSwitch)
        val torch = Torch(this)    // Torch ํด๋ž˜์Šค์˜ ๊ธฐ๋Šฅ์„ ๊ฐ€์ ธ๋‹ค ์“ฐ๊ธฐ ์œ„ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ
        val intent = Intent(this, TorchService::class.java)

        flahSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
            // intent ๊ฐ’์„ ์„œ๋น„์Šค๋กœ ๋„˜๊น€
            if(isChecked) {
                // torch.flashOn()
                intent.action = "on"
                startService(intent)    // intent ๋ณด๋‚ด๊ธฐ
            }
            else {
                // torch.flashOff()
                intent.action = "off"
                startService(intent)    // intent ๋ณด๋‚ด๊ธฐ
            }
        }
    }
}

 

- ์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ์ง์ ‘ Torch ํด๋ž˜์Šค๋ฅผ ์กฐ์ž‘ํ•˜์—ฌ ํ”Œ๋ž˜์‹œ๋ฅผ ์ผœ๋˜ ๊ตฌ์กฐ์—์„œ, ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•ด ํ”Œ๋ž˜์‹œ๋ฅผ ์ผœ๋Š” ๊ตฌ์กฐ๋กœ ๋ณ€๊ฒฝ๋จ

 

4. ์•ฑ ์œ„์ ฏ ๊ตฌํ˜„

์•ฑ ์œ„์ ฏ ์ถ”๊ฐ€

- ํ”„๋กœ์ ํŠธ ์ฐฝ์˜ ํŒจํ‚ค์ง€๋ช…์—์„œ ์šฐํด๋ฆญ ๋˜๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค ๋ฉ”๋‰ด์—์„œ File → New → Widget → App Widget์„ ํด๋ฆญ

 

์•ฑ ์œ„์ ฏ์ด ์ƒ์„ฑํ•œ ์ฝ”๋“œ ํ™•์ธ

โ‘  ์•ฑ ์œ„์ ฏ์„ ํด๋ฆญํ•  ๋•Œ์˜ ๋™์ž‘์„ ์ž‘์„ฑํ•˜๋Š” ๊ณณ์ž„ 

โ‘ก ์•ฑ ์œ„์ ฏ์˜ ๋ ˆ์ด์•„์›ƒ์„ ์ •์˜ํ•œ ํŒŒ์ผ

โ‘ข ์•ฑ ์œ„์ ฏ์˜ ์—ฌ๋ฐฑ ๊ฐ’์„ ์ •์˜ํ•œ ํŒŒ์ผ

โ‘ฃ ์•ฑ ์œ„์ ฏ์˜ ๊ฐ์ข… ์„ค์ •์„ ํ•˜๋Š” ํŒŒ์ผ

 

์•ฑ ์œ„์ ฏ ๋ ˆ์ด์•„์›ƒ ์ˆ˜์ •

- ํ”„๋กœ์ ํŠธ ์ฐฝ์—์„œ layout/torch_app_widget.xml ํŒŒ์ผ ์ˆ˜์ •

    - ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ ์ฐฝ์—์„œ ํ…์ŠคํŠธ ๋ทฐ๋ฅผ ์„ ํƒํ•˜๊ณ  Translations Editor์—์„œ Key๊ฐ’์ด appwidget_text์ธ ๋ฌธ์ž์—ด์˜ ๊ฐ’์„ ‘์†์ „๋“ฑ’์œผ๋กœ ์ˆ˜์ •, ๋˜๋Š” values/strings.xml ํŒŒ์ผ์„ ์—ด์–ด์„œ ์ง์ ‘ ์ˆ˜์ • ๊ฐ€๋Šฅ

<resources>
    <string name="app_name">Flashlight</string>
    <string name="appwidget_text">์†์ „๋“ฑ</string>
    <string name="add_widget">Add widget</string>
    <string name="app_widget_description">This is an app widget description</string>
</resources>

 

- ์œ„์ ฏ์„ ํด๋ฆญํ•˜๋ฉด ํ”Œ๋ž˜์‹œ๋ฅผ ์ผœ์•ผ ํ•จ. ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ ์ฐฝ์—์„œ RelativeLayout์„ ์„ ํƒํ•˜๊ณ  ์†์„ฑ ์ฐฝ์—์„œ ID๋ฅผ appwidget_layout์œผ๋กœ ์ˆ˜์ •

 

์•ฑ ์œ„์ ฏ์—์„œ ์†์ „๋“ฑ ์ผœ๊ธฐ

- ์œ„์ ฏ์„ ํด๋ฆญํ•˜๋ฉด ํ”Œ๋ž˜์‹œ๊ฐ€ ์ผœ์ง€๋„๋ก ์ฝ”๋“œ ์ž‘์„ฑ

- TorchAppWidget.kt ํŒŒ์ผ์„ ์—ด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋ณธ ์ฝ”๋“œ๊ฐ€ ์ž‘์„ฑ๋˜์–ด ์žˆ์Œ

โ‘  ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ์—ฐ๊ฒฐํ•˜๋ ค๋ฉด setOnClickPendingIntent ( ) ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉ

โ‘ก TorchService ์„œ๋น„์Šค๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ฐ PendingIntent.getService ( ) ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉ

โ‘ข ์ „๋‹ฌํ•˜๋Š” ์ธ์ž๋Š” ์ปจํ…์ŠคํŠธ, ๋ฆฌํ€˜์ŠคํŠธ ์ฝ”๋“œ, ์„œ๋น„์Šค ์ธํ…ํŠธ, ํ”Œ๋ž˜๊ทธ 4๊ฐœ์ž„

...

internal fun updateAppWidget(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetId: Int
) {
    val widgetText = context.getString(R.string.appwidget_text)
    // Construct the RemoteViews object
    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)    // ํด๋ฆญ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ํ›„ ์‹คํ–‰ํ•  ์ธํ…ํŠธ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ์ •๋ณด ๊ฐ€์ง€๊ณ  ์žˆ์„ ํŽœ๋”ฉ ์ธํ…ํŠธ ๊ฐ์ฒด ์ƒ์„ฑ

    // ์œ„์ ฏ ํด๋ฆญ์‹œ ์ธํ…ํŠธ ์ •๋ณด ๊ฐ€์ง€๊ณ  ์‹คํ–‰ํ•  ๋ฉ”์†Œ๋“œ
    views.setOnClickPendingIntent(R.id.appwidget_layout, pendingIntent) // ๊ฐ’ ์ „๋‹ฌ

    // Instruct the widget manager to update the widget
    appWidgetManager.updateAppWidget(appWidgetId, views)
}

 

class TorchService : Service() {

    private val torch: Torch by lazy {      // by lazy๋Š” val ํƒ€์ž…์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
        Torch(this)
    }

    private var isRunning = false  // ์‹คํ–‰ ์ค‘์ธ์ง€ ํ™•์ธ

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        when (intent?.action) {        // intent๋กœ ๊ฐ’ ๋„˜์–ด์˜ค๋Š”๋ฐ action๊ฐ’์— ์˜ํ•ด ๊ฒฐ์ •๋จ
            ...

            // ์œ„์ ฏ
            else -> {
                isRunning = !isRunning
                if (isRunning) {
                    torch.flashOn()
                } else {
                    torch.flashOff()
                }
            }
        }

        return super.onStartCommand(intent, flags, startId)
    }
   
    ...
}

 

์•ฑ ์œ„์ ฏ ๋ฐฐ์น˜

์œ„์ ฏ ์ฝ”๋“œ๊ฐ€ ๋ฐ˜์˜๋˜๋„๋ก ์•ฑ์„ ์‹คํ–‰ํ•˜๊ณ  ๋ฐ”๋กœ ์ข…๋ฃŒ

๋Ÿฐ์ฒ˜์˜ ๋นˆ ๊ณต๊ฐ„์„ ๊ธธ๊ฒŒ ํด๋ฆญํ•˜๋ฉด ์œ„์ ฏ์„ ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Œ

โ‘  ‘์œ„์ ฏ’์„ ํด๋ฆญํ•˜๊ณ  ์Šคํฌ๋กคํ•˜์—ฌ ์šฐ๋ฆฌ๊ฐ€ ๊ตฌํ˜„ํ•œ โ‘ก Flashlight ์œ„์ ฏ์„ ๋‹ค์‹œ ๊ธธ๊ฒŒ ํด๋ฆญ

๊ทธ๋Ÿฐ ๋‹ค์Œ ๋Ÿฐ์ฒ˜์˜ โ‘ข ๋นˆ ๊ณต๊ฐ„์— ๋†“์Œ. โ‘ก ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€๋Š” ์•ฑ์œ„์ ฏ์„ ์ƒ์„ฑ ์‹œ drawable ํด๋”์— ์ƒ์„ฑ๋œ example_appwidget_preview.png ํŒŒ์ผ

 

 

์ตœ์ข…

package com.example.flashlight

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Switch

class MainActivity : AppCompatActivity() {

    lateinit var flahSwitch: Switch

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        flahSwitch = findViewById(R.id.flashSwitch)
        val torch = Torch(this)    // Torch ํด๋ž˜์Šค์˜ ๊ธฐ๋Šฅ์„ ๊ฐ€์ ธ๋‹ค ์“ฐ๊ธฐ ์œ„ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ
        val intent = Intent(this, TorchService::class.java)

        flahSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
            // intent ๊ฐ’์„ ์„œ๋น„์Šค๋กœ ๋„˜๊น€
            if(isChecked) {
                // torch.flashOn()
                intent.action = "on"
                startService(intent)    // intent ๋ณด๋‚ด๊ธฐ
            }
            else {
                // torch.flashOff()
                intent.action = "off"
                startService(intent)    // intent ๋ณด๋‚ด๊ธฐ
            }
        }
    }
}

 

package com.example.flashlight

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

class Torch (context: Context) {    // ์นด๋ฉ”๋ผ ์ œ์–ด๋ฅผ ์œ„ํ•œ CameraManager ๊ฐ์ฒด๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด Context ๊ฐ์ฒด ํ•„์š” -> ์ƒ์„ฑ์ž๋กœ Context ๋ฐ›์Œ

    private var cameraId: String? = null    // ์นด๋ฉ”๋ผ ๊ฐ’์˜ id ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜
    private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager

    init {          // ์ดˆ๊ธฐ ์„ค์ •
        cameraId = getCameraID()
    }

    fun flashOn() {
        cameraId?.let { cameraManager.setTorchMode(it, true) }    // it: ์นด๋ฉ”๋ผ id
    }

    fun flashOff() {
        cameraId?.let { cameraManager.setTorchMode(it, false) }
    }

    // ์กฐ๊ฑด์— ๋งž๋Š” ์นด๋ฉ”๋ผ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฉ”์†Œ๋“œ
    private fun getCameraID(): String? {
        val cameraIds = cameraManager.cameraIdList      // ์นด๋ฉ”๋ผ id ๋ฆฌ์ŠคํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
        for (id in cameraIds) {     // for ๋ฌธ์„ ์ด์šฉํ•ด ์กฐ๊ฑด์— ๋งž๋Š” id ์ฐพ๊ธฐ
            val info = cameraManager.getCameraCharacteristics(id)    // ์นด๋ฉ”๋ผ id๋กœ๋ถ€ํ„ฐ ์ข€ ๋” ์ƒ์„ธํ•œ ์ •๋ณด ์–ป์–ด์˜ค๊ธฐ
            val flashAvailable = info.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)    // ํ”Œ๋ž˜์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ์ง€ ํ™•์ธํ•˜๊ธฐ(True: ๋ฐ˜ํ™˜๊ฐ€๋Šฅ)
            val lensFacing = info.get(CameraCharacteristics.LENS_FACING)    // ๋ Œ์ฆˆ ๋ฐฉํ–ฅ ํ™•์ธํ•˜๊ธฐ(1: Back)
            if(flashAvailable !== null && flashAvailable
                && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK){     // ํ”Œ๋ž˜์‹œ ์‚ฌ์šฉ๊ฐ€๋Šฅ, ๋ Œ์ฆˆ ํ›„๋ฉด
                return id
            }
        }
        return null
    }

}

 

package com.example.flashlight

import android.app.Service
import android.content.Intent
import android.os.IBinder

class TorchService : Service() {

    private val torch: Torch by lazy {      // by lazy๋Š” val ํƒ€์ž…์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
        Torch(this)
    }

    private var isRunning = false  // ์‹คํ–‰ ์ค‘์ธ์ง€ ํ™•์ธ

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        when (intent?.action) {        // intent๋กœ ๊ฐ’ ๋„˜์–ด์˜ค๋Š”๋ฐ action๊ฐ’์— ์˜ํ•ด ๊ฒฐ์ •๋จ
            "on" -> {
                torch.flashOn()
            }
            "off" -> {
                torch.flashOff()
            }

            // ์œ„์ ฏ
            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.")
    }
}

 

package com.example.flashlight

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews

/**
 * Implementation of App Widget functionality.
 */
class TorchAppWidget : 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) {
        // 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)
    // Construct the RemoteViews object
    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, PendingIntent.FLAG_IMMUTABLE)    // ํด๋ฆญ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ํ›„ ์‹คํ–‰ํ•  ์ธํ…ํŠธ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ์ •๋ณด ๊ฐ€์ง€๊ณ  ์žˆ์„ ํŽœ๋”ฉ ์ธํ…ํŠธ ๊ฐ์ฒด ์ƒ์„ฑ

    // ์œ„์ ฏ ํด๋ฆญ์‹œ ์ธํ…ํŠธ ์ •๋ณด ๊ฐ€์ง€๊ณ  ์‹คํ–‰ํ•  ๋ฉ”์†Œ๋“œ
    views.setOnClickPendingIntent(R.id.appwidget_layout, pendingIntent) // ๊ฐ’ ์ „๋‹ฌ

    // Instruct the widget manager to update the widget
    appWidgetManager.updateAppWidget(appWidgetId, views)
}

 

 

์ •๋ฆฌ

- CameraManager ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์นด๋ฉ”๋ผ์™€ ํ”Œ๋ž˜์‹œ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Œ

- ํ”Œ๋ž˜์‹œ๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ๋ฒ„์ „๋ณ„๋กœ API ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Œ

- ์„œ๋น„์Šค๋Š” ํ™”๋ฉด์ด ์—†๊ณ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์–ด๋– ํ•œ ๋™์ž‘์„ ํ•˜๋Š” ์•กํ‹ฐ๋น„ํ‹ฐ์™€ ๋ณ„๊ฐœ๋กœ ๋™์ž‘ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ

- ์•ฑ ์œ„์ ฏ์€ ๋Ÿฐ์ฒ˜์—์„œ ํŠน์ • ์ธํ…ํŠธ, ์„œ๋น„์Šค, ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Œ

- ํ”Œ๋ž˜์‹œ๋ฅผ ์„œ๋น„์Šค์— ๊ตฌํ˜„ํ•˜์—ฌ ์•ฑ ์œ„์ ฏ์—์„œ ์ด ์„œ๋น„์Šค๋ฅผ ์‹œ์ž‘ํ•˜์—ฌ ํ”Œ๋ž˜์‹œ๋ฅผ ์ผœ๊ณ  ๋Œ ์ˆ˜ ์žˆ์Œ

 


[์ฐธ๊ณ ] ์„œ๋น„์Šค

์„œ๋น„์Šค(service)

- ์ผ๋ฐ˜์ ์œผ๋กœ ํ™”๋ฉด ์—†์ด ๋™์ž‘ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๋œปํ•จ

- ๋ฐ๋ชฌ(daemon), ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ”„๋กœ์„ธ์Šค(background process)๋ผ๊ณ ๋„ ํ•จ

- ์„œ๋น„์Šค๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋˜๋ฏ€๋กœ ํ™”๋ฉด๊ณผ ์ƒ๊ด€์—†์ด ๊ณ„์† ๋™์ž‘ํ•จ

 

ํ™”๋ฉด์ด ์ข…๋ฃŒ๋˜์–ด๋„ ๊ณ„์†๋˜๋Š” ์Œ์•… ์„œ๋น„์Šค ๋งŒ๋“ค๊ธฐ

- ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์Œ์•…์ด ์‹œ์ž‘๋˜๊ณ , ํ™”๋ฉด์ด ์ข…๋ฃŒ๋˜์–ด๋„ ์Œ์•…์ด ๊ณ„์† ํ๋ฅด๋Š” ์„œ๋น„์Šค

- ๋กœ๊ทธ์บฃ์„ ํ™œ์šฉํ•˜์—ฌ ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ์ˆœ์„œ๋ฅผ ํ™•์ธํ•ด๋ด„

 

 

ํ™”๋ฉด ๋””์ž์ธ ํŽธ์ง‘

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnStart"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="์Œ์•…์„œ๋น„์Šค ์‹œ์ž‘" />

    <Button
        android:id="@+id/btnStop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="์Œ์•…์„œ๋น„์Šค ์ค‘์ง€" />

</LinearLayout>

 

Kotlin ์ฝ”๋“œ ์ž‘์„ฑ ๋ฐ ์ˆ˜์ •

- Service ํด๋ž˜์Šค์˜ ์ƒ์†์„ ๋ฐ›๋Š” MusicService ํด๋ž˜์Šค ์ •์˜

    - [java]-[ํŒจํ‚ค์ง€ ์ด๋ฆ„]์—์„œ [New]-[Service] -[Service]๋ฅผ ์„ ํƒ

- onCreate( ), onDestroy( ), onStartCommand( ) ๋ฉ”์†Œ๋“œ ์•ˆ์— ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋„๋ก ์ฝ”๋”ฉํ•˜๋ฉฐ, onBind( )๋Š” null ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •

class MusicService : Service() {

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onCreate() {
        super.onCreate()
    }

    override fun onDestroy() {
        super.onDestroy()
    }
}

 

- MusicService ํด๋ž˜์Šค์— ์Œ์•…์„ ์‹œ์ž‘ํ•˜๊ณ , ์ •์ง€ํ•˜๋Š” ์ฝ”๋“œ ์ถ”๊ฐ€ 

    - res ํด๋” ์•„๋ž˜์— raw ํด๋”๋ฅผ ์ƒ์„ฑ (new > Folder > Raw Resources Folder)

    - MP3 ํŒŒ์ผ์„ ํ•˜๋‚˜ ๋ณต์‚ฌ

    - ์ „์—ญ๋ณ€์ˆ˜๋กœ MediaPlayer ๋ณ€์ˆ˜ 1๊ฐœ ์„ ์–ธ

    - onStartCommand( ) ๋ฉ”์†Œ๋“œ์— MP3 ํŒŒ์ผ์„ ์‹œ์ž‘ํ•˜๋Š” ์ฝ”๋“œ ์ถ”๊ฐ€

    - onDestroy( ) ๋ฉ”์†Œ๋“œ์— ์Œ์•…์„ ์ค‘์ง€์‹œํ‚ค๋Š” ์ฝ”๋“œ ์ถ”๊ฐ€

package com.example.musicserviceapp

import android.app.Service
import android.content.Intent
import android.media.MediaPlayer
import android.os.IBinder
import android.util.Log

class MusicService : Service() {

    lateinit var mp: MediaPlayer   // ๋ฏธ๋””์–ด ํ”Œ๋ ˆ์ด์–ด ๊ฐ์ฒด ์ƒ์„ฑ์œ„ํ•œ ๋ณ€์ˆ˜

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        Log.i("์„œ๋น„์Šค ํ…Œ์ŠคํŠธ", "onStartCommand()")    // ๋กœ๊ทธ ๊ธฐ๋ก ์ถ”๊ฐ€

        mp = MediaPlayer.create(this, R.raw.song1)   // ๋ฏธ๋””์–ด ํ”Œ๋ ˆ์ด์–ด ๊ฐ์ฒด ์ƒ์„ฑ
        mp.isLooping = true     // ๋ฐ˜๋ณต ์žฌ์ƒ
        mp.start()

        return super.onStartCommand(intent, flags, startId)
    }

    override fun onCreate() {
        Log.i("์„œ๋น„์Šค ํ…Œ์ŠคํŠธ", "onCreate()")    // ๋กœ๊ทธ ๊ธฐ๋ก ์ถ”๊ฐ€

        super.onCreate()
    }

    override fun onDestroy() {
        Log.i("์„œ๋น„์Šค ํ…Œ์ŠคํŠธ", "onDestroy()")    // ๋กœ๊ทธ ๊ธฐ๋ก ์ถ”๊ฐ€

        mp.stop()    // ๋ฏธ๋””์–ด ํŒŒ์ผ ์ค‘์ง€
        super.onDestroy()
    }
}

 

- ๋ฉ”์ธ Kotlin ์ฝ”๋“œ ์™„์„ฑ

    - MusicService ํด๋ž˜์Šค๋ฅผ ์ ์šฉํ•  ์ธํ…ํŠธ ๋ณ€์ˆ˜ 1๊ฐœ์™€ ๋ฒ„ํŠผ ๋ณ€์ˆ˜ 2๊ฐœ๋ฅผ ์ „์—ญ๋ณ€์ˆ˜๋กœ ์„ ์–ธ

    - ์ธํ…ํŠธ ๋ณ€์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜๋ฉด์„œ MusicService ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ์ž์— ๋„˜๊น€

    - activity_main.xml์˜ ๋ฒ„ํŠผ 2๊ฐœ๋ฅผ ๋ฒ„ํŠผ ๋ณ€์ˆ˜์— ์ ์šฉ์‹œํ‚ด

    - <์‹œ์ž‘>์„ ํด๋ฆญํ•˜๋ฉด startService( )๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋กœ๊ทธ๋ฅผ ๋‚จ๊น€

    - <์ค‘์ง€>๋ฅผ ํด๋ฆญํ•˜๋ฉด stopService( )๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋กœ๊ทธ๋ฅผ ๋‚จ๊น€

package com.example.musicserviceapp

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button

class MainActivity : AppCompatActivity() {

    lateinit var soundIntent: Intent
    lateinit var btnStart: Button
    lateinit var btnStop: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        soundIntent = Intent(this, MusicService::class.java)
        btnStart = findViewById(R.id.btnStart)
        btnStop = findViewById(R.id.btnStop)

        btnStart.setOnClickListener {
            startService(soundIntent)
            Log.i("์„œ๋น„์Šค ํ…Œ์ŠคํŠธ", "startService()")
        }

        btnStop.setOnClickListener {
            stopService(soundIntent)
            Log.i("์„œ๋น„์Šค ํ…Œ์ŠคํŠธ", "stopService()")
        }
    }
}

 

AndroidManifest.xml์„ ์—ด๊ณ  ์•ˆ์— ์„œ๋น„์Šค ๋“ฑ๋ก

๋“ฑ๋กํ•˜์ง€ ์•Š์•„๋„ ์ž๋™ ๋“ฑ๋ก ๋จ

 

ํ”„๋กœ์ ํŠธ ์‹คํ–‰ ๋ฐ ๊ฒฐ๊ณผ ํ™•์ธ

- ๋กœ๊ทธ ํ™”๋ฉด์„ ๋ณด๊ธฐ ์œ„ํ•œ ๋กœ๊ทธ์บฃ ํ™”๋ฉด์ด ๋ณด์ด์ง€ ์•Š์œผ๋ฉด Android Studio ์•„๋ž˜์ชฝ์˜ [Logcat] ํƒญ ํด๋ฆญ

    - [Logcat] ํ™”๋ฉด ๊นจ๋—ํ•˜๊ฒŒ ํ•˜๊ธฐ : ๋นˆ ๊ณณ์— ๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ณ  ‘Clear logcat’ ์„ ํƒ