서비스란?
✔ 메인 스레드를 사용하는 화면이 없는 액티비티
✔ 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 |