์ค์ ํ๋ก์ ํธ
์ค์ต - ์์ ๋ฑ
์ฃผ์๊ตฌ์ฑ
- ์์ ๋ฑ ์ฑ์ ์ฑ๊ณผ ์์ ฏ์ผ๋ก ๊ตฌ์ฑ
- ์ฑ๊ณผ ์์ ฏ ๋ชจ๋ ์์ ๋ฑ์ ์ผ๊ณ ๋๋ ์ธํฐํ์ด์ค๋ง์ ์ ๊ณตํ๊ณ ํต์ฌ ๊ธฐ๋ฅ์ ์๋น์ค์์ ์ํ
โป ์์ ๋ฑ ๊ธฐ๋ฅ์ ๊ฐ์๊ธฐ๊ธฐ๋ก ๋ถ๊ฐ!
์๋๋ก์ด๋ ์คํ๋์ค์ ํด๋ํฐ ์ฐ๊ฒฐ ๋ฐฉ๋ฒ
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’ ์ ํ