Design-loving front-end engineer
Ryong
Design-loving front-end engineer
전체 방문자
오늘
어제
    • Framework
    • React
      • Concept
      • Library
      • Hook
      • Component
      • Test
    • NodeJS
    • Android
      • Concept
      • Code
      • Sunflower
      • Etc
    • Flutter
      • Concept
      • Package
    • Web
    • Web
    • CSS
    • Language
    • JavaScript
    • TypeScript
    • Kotlin
    • Dart
    • Algorithm
    • Data Structure
    • Programmers
    • Management
    • Git
    • Editor
    • VSCode
    • Knowledge
    • Voice
Design-loving front-end engineer

Ryong

[Android] [Kotlin] 서비스
Android/Concept

[Android] [Kotlin] 서비스

2021. 9. 22. 21:20

서비스란?

✔  메인 스레드를 사용하는 화면이 없는 액티비티

✔  New - Service - Service로 새로운 서비스를 만들면 AndroidManifest.xml 파일에 <service>가 등록된다.

 

서비스 시작 방식에 따른 분류

Started 서비스

✔  startService() 메서드로 호출하며 액티비티와 상관없이 독립적으로 동작할 때 사용한다.

✔  Started 서비스가 이미 동작 중인 상태에서 Started 서비스의 재시작을 요청할 경우, 새로 만들지 않고 생성되어 있는 서비스의 메서드를 호출한다.

MainActivity.kt

class MainActivity : AppCompatActivity() {

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

    // Started 서비스 실행
    fun serviceStart(view: View) {  // 버튼 이벤트
        val intent = Intent(this, MyService::class.java)
        intent.action = MyService.ACTION_START  // 넘겨줄 인텐트 액션 설정
        startService(intent)
    }

    // Started 서비스 종료
    fun serviceStop(view: View) {  // 버튼 이벤트
        val intent = Intent(this, MyService::class.java)
        stopService(intent)
    }
}

◽  버튼의 onClick 속성에 구체적인 이벤트 이름으로 설정하여, 버튼 클릭 시 바로 메서드를 실행하도록 한다.

 

MyService.kt

class MyService : Service() {

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val action = intent?.action  // 넘어오는 인텐트의 액션
        Log.d("StartedService", "action=$action")

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

    // Started 서비스 종료 확인
    override fun onDestroy() {
        Log.d("Service", "서비스가 종료되었습니다.")
        super.onDestroy()
    }

    companion object {
        // 명령어 작성 : "패키지명 + 명령어"
        val ACTION_START = "com.example.servicetest.START"
        val ACTION_RUN = "com.example.servicetest.RUN"
        val ACTION_STOP = "com.example.servicetest.STOP"
    }
}

 

Bound 서비스

✔  bindService() 메서드로 호출하며 액티비티와 값을 주고받을 필요가 있을 때 사용한다.

✔  여러 개의 액티비티가 같은 서비스를 사용할 수 있어서 기존에 생성되어 있는 서비스를 바인딩해서 재사용할 수 있다.

✔  값을 주고 받을 수 있으나, 값을 주고 받기 위한 인터페이스가 복잡하고 액티비티가 종료되면 서비스도 종료되기 때문에 잘 사용되진 않는다.

✔  Bound 서비스를 만들기 위해서는 서비스와 액티비티 간 연결 장치인 ServiceConnection을 생성해야 한다.

✔  Bound 서비스는 Started 서비스와 다르게 액티비티에서 서비스의 메서드를 직접 호출해서 사용할 수 있다.

MyService.kt

class MyService : Service() {

    inner class MyBinder : Binder() {
        // 액티비티와 서비스가 연결되면 바인더의 getService() 메서드를 통해 서비스에 접근
        fun getService() : MyService {
            return this@MyService
        }
    }
    val binder = MyBinder()

    override fun onBind(intent: Intent): IBinder {
        return binder
    }
    
    // Bound 서비스 메세지 직접 호출
    fun serviceMessage() : String {
        return "Hello Activity! I am Service!"
    }
}

 

MainActivity.kt

class MainActivity : AppCompatActivity() {
	
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
    
    // 서비스와 연결할 수 있는 서비스 커넥션 만들기
    // 만든 서비스 커넥션을 bindService() 메서드를 통해 시스템에 전달하면 서비스와 연결할 수 있음
    var myService: MyService? = null
    var isService = false
    val connection = object : ServiceConnection {
        // 서비스가 연결되면 호출된다.
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            val binder = service as MyService.MyBinder
            myService = binder.getService()
            isService = true  // onServiceDisconnected 구조에 때문에 서비스 연결 상태를 확인하는 로직이 필요
            Log.d("BoundService", "연결되었습니다.")
        }
        // 정상적으로 연결 해제되었을 때는 호출되지 않고, 비정상적으로 서비스가 종료되었을 때만 호출된다.
        override fun onServiceDisconnected(name: ComponentName) {
            isService = false
        }
    }
    
    fun serviceBind(view: View) {  // 버튼 이벤트
        val intent = Intent(this, MyService::class.java)
        bindService(intent, connection, Context.BIND_AUTO_CREATE)
        // 서비스를 호출하면서 커넥션을 같이 넘겨준다.
        // BIND_AUTO_CREATE : 서비스가 생성되어 있지 않으면 생성 후 바인딩을 하고 생성되어 있으면 바로 바인딩
    }

    fun serviceUnbind(view: View) {  // 버튼 이벤트
        if (isService) {  // 서비스가 실행되고 있을 때
            unbindService(connection)  // 바인드 해제
            isService = false
        }
    }
    
    // Bound 서비스 메세지 직접 호출
    fun callServiceFunction(view: View) {  // 버튼 이벤트
        if (isService) {
            val message = myService?.serviceMessage()  // 서비스에 있는 메서드 사용
            Toast.makeText(this, "message=${message}", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(this, "서비스가 연결되지 않았습니다.", Toast.LENGTH_SHORT).show()
        }
    }
}

 

실행 구조에 따른 분류 : Foreground 서비스

✔  기본적인 서비스는 모두 백그라운드이다.

✔  Foreground 서비스는 사용자에게 알림을 통해 현재 작업이 진행 중이라는 것을 알려줘야 한다.

✔  Background 서비스는 앱이 꺼지거나 가용 자원이 부족하면 시스템에 의해 제거될 수 있지만, Foreground 서비스는 사용자가 알림을 통해 서비스가 동작하고 있다는 것을 인지하고 있기 때문에 가용 자원과 같은 이유로 종료되지 않는다.

AndroidManifest.xml

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

 

Foreground.kt

class Foreground : Service() {

    val CHANNEL_ID = "ForegroundChannel"  // 상태 바에 뜰 알림 이름

    override fun onBind(intent: Intent): IBinder {
        return Binder()  // 오류를 막기 위해 비어 있는 Binder() 리턴
    }

    // Foreground 서비스에 사용할 알림을 실행하기 전에 알림 채널을 생성하는 메서드
    // 모든 알림은 오레오 버전 이후로 채널 단위로 동작한다.
    fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val serviceChannel = NotificationChannel(
                CHANNEL_ID,
                "Foreground Service Channel",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            val manager = getSystemService(
                NotificationManager::class.java
            )
            manager.createNotificationChannel(serviceChannel)
        }
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        createNotificationChannel()  // 알림 생성

        val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Foreground Service")
            .setSmallIcon(R.mipmap.ic_launcher_round)
            .build()

        startForeground(1, notification)  // 알림 생성

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

 

MainActivity.kt

class MainActivity : AppCompatActivity() {

    val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.buttonStart.setOnClickListener {
            val intent = Intent(this, Foreground::class.java)
            ContextCompat.startForegroundService(this, intent)  // Foreground 서비스 실행
        }

        binding.buttonStop.setOnClickListener {
            val intent = Intent(this, Foreground::class.java)
            stopService(intent)
        }
    }
}

 

저작자표시 (새창열림)

'Android > Concept' 카테고리의 다른 글

[Android] [Kotlin] 네트워크  (0) 2021.09.25
[Android] [Kotlin] 콘텐트 리졸버  (0) 2021.09.23
[Android] [Kotlin] 코루틴  (0) 2021.09.21
[Android] [Kotlin] 스레드와 루퍼  (0) 2021.09.20
[Android] [Kotlin] Room 데이터베이스  (0) 2021.09.19
    'Android/Concept' 카테고리의 다른 글
    • [Android] [Kotlin] 네트워크
    • [Android] [Kotlin] 콘텐트 리졸버
    • [Android] [Kotlin] 코루틴
    • [Android] [Kotlin] 스레드와 루퍼
    Design-loving front-end engineer
    Design-loving front-end engineer
    디자인에 관심이 많은 모바일 앱 엔지니어 Ryong입니다.

    티스토리툴바