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 지정(어느 스레드에서 수행할 지 결정)

안드로이드 앱 컴포넌트

  • 액티비티: 사용자 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에 권한을 표시하고 설치시에 사용자에게 확인
  • 앱이 실행 중에 권한이 필요할 때 사용자에게 권한 부여 요청