Development/Kotlin&Android
코루틴, 서비스, 권한
우봉수
2023. 5. 30. 18:30
코루틴
- 코틀린에서 병행(비동기) 수행을 프로그래밍하는 방법
- 스레드를 직접 다루지 않고 편리하게 사용하는 방법
- 가볍고 취소가 가능하며 Jetpack에서 코루틴을 이용한 API를 제공한다.
- 스레드 풀에 존재하는 스레드를 가져다 쓰는 방식
코루틴 사용법
val textView by lazy { findViewById<TextView>(R.id.textView) }
private lateinit var scope: CoroutineScope
private fun startCoroutine() {
scope.launch {
for(i in 1..10){
delay(1000)
withContext(Dispatchers.Main){
textView.text = "$i"
}
}
}
scope.launch {
for(i in 10 .. 20){
delay(1000)
withContext(Dispatchers.Main){
textView.text = "$i"
}
}
}
}
private fun stopCoroutine() = scope.cancel
- 코루틴 범위(CoroutineScope)를 생성, 지정: CoroutineScope(Dispatchers.Default)
- 코루틴 생성: lanch
- 코루틴 컨텍스트: Dispatcher 지정(어느 스레드에서 수행할 지 결정)
- Dispatcher.Main
- Dispatcher.Default
- Dispatcher.IO
안드로이드 앱 컴포넌트
- 액티비티: 사용자 UI를 제공하는 컴포넌트
- 서비스: 백그라운드에서 수행할 작업을 위한 컴포넌트
- 브로드캐스트 수신자: 다양한 이벤트(인텐트)를 수신하기 위한 컴포넌트
- 컨텐트 제공자: 다른 앱과 공유할 데이터를 관리하고 제공하는 컴포넌트
서비스
class MainActivity : AppCompatActivity() {
val textView by lazy { findViewById<TextView>(R.id.textView) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val buttonGetCount = findViewById<Button>(R.id.buttonGetCount)
buttonGetCount.setOnClickListener {
textView.text = "count: ${myService?.count}"
}
}
private var myService : MyService? = null
private val serviceConnection = object : ServiceConnection{
// 2. Bind 된 서비스 사용
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
myService = (service as MyService.LocalBinder).getService()
}
override fun onServiceDisconnected(name: ComponentName?) {
myService = null
}
}
override fun onStart() {
super.onStart()
val intent = Intent(this,MyService::class.java)
// 1. 서비스 Bind
bindService(intent,serviceConnection, BIND_AUTO_CREATE)
}
override fun onStop() {
super.onStop()
// 3. 서비스 종료후 Bind 해제
unbindService(serviceConnection)
}
class MyService : Service(){
// Bound 서비스
private val binder = LocalBinder()
// MyClass가 정의가 먼져 되어야 하기 때문에 inner 클래스로 생
inner class LocalBinder: Binder(){
// Bound 서비스 객체 제공
fun getService() = this@MyService
}
// 로컬바인드 객체를 가지고 있다 리턴 하는 함수
override fun onBind(intent: Intent?): IBinder? {
return binder
}
override fun onCreate() {
super.onCreate()
if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.O){
createNotificationChannel() // 알림 채널 생성
}
}
// 백그라운드 서비스
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(notificationID, createNotification()) // 포그라운드 서비스
// TO DO
return super.onStartCommand(intent, flags, startId)
}
private val channelID = "default"
private val notificationID = 1
// 알림 코드
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel() {
// 채널 아이디, 채널 이름
val channel = NotificationChannel(channelID, "default channel",
NotificationManager.IMPORTANCE_DEFAULT)
channel.description = "description text of this channel."
// 채널 생성
NotificationManagerCompat.from(this).createNotificationChannel(channel)
}
// 알림의 모양을 결정하는 함수
private fun createNotification(progress: Int = 0) = NotificationCompat.Builder(this, channelID)
.setContentTitle("MyService")
.setContentText("Service is running...")
.setSmallIcon(R.drawable.icon)
.setOnlyAlertOnce(true) // importance 에 따라 알림 소리가 날 때, 처음에만 소리나게 함
.setProgress(100, progress, false)
.build()
}
- 안드로이드 앱 컴포넌트 중 하나로 백그라운드에서 수행할 작업을 위한 컴포넌트
- 원칙적으로 사용자 인터페이스를 제공하지 않음
- Started 서비스
- Foreground 서비스: 알림(Notification)을 띄우고 사용자에게 상태를 보여주면서 동작하는 서비스 startForeground() 호출
- Background 서비스: UI 전혀 제공하지 않는 서비스 onStartCommand() 재정의
- 바운드 서비스: 클라이언트-서버와 유사 등록하고 요청하면 해당 함수를 호출하는 방식
권한
- 제한된 정보나 행위를 접근/수행하기 위해 권한 필요
- 권한이 없는 앱이 특정 정보에 접근하려고 하면 예외 발생
- 해당 정보 접근에 필요한 권한을 앱에 부여해야 함
- 부여 방법: 설치시 권한 부여, 동적 권한 부여
동적 권한 부여
<resources>
<string name="app_name">week13</string>
<string name="req_permission_reason">App has no permission %1$s. Some features would not work correctly.</string>
<string name="no_permission">App requires %1$s permission. Could you allow the permission?</string>
</resources>
private fun requestSinglePermission(permission: String) { // 한번에 하나의 권한만 요청하는 예제
if (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) // 권한 유무 확인
return
val requestPermLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { // 권한 요청 컨트랙트
if (it == false) { // permission is not granted!
AlertDialog.Builder(this).apply {
setTitle("Warning")
setMessage(getString(R.string.no_permission, permission))
}.show()
}
}
if (shouldShowRequestPermissionRationale(permission)) { // 권한 설명 필수 여부 확인
// you should explain the reason why this app needs the permission.
AlertDialog.Builder(this).apply {
setTitle("Reason")
setMessage(getString(R.string.req_permission_reason, permission))
setPositiveButton("Allow") { _, _ -> requestPermLauncher.launch(permission) }
setNegativeButton("Deny") { _, _ -> }
}.show()
} else {
// should be called in onCreate()
requestPermLauncher.launch(permission) // 권한 요청 시작
}
}
- AndroidManifest.xml에 권한을 표시하고 설치시에 사용자에게 확인
- 앱이 실행 중에 권한이 필요할 때 사용자에게 권한 부여 요청