일반적으로 화면 없이 동작하는 프로그램으로 백그라운드 프로세스
서비스 특징
유저와 상호작용 불가
액티비티의 생명주기에 비종속
별도의 스레드에서 동작하지 않고 '메인스레드'에서 작동
액티비티가 비활성화 되면 액티비티보다 우선순위 높음
서비스는 background에서 장시간 동작하면 시스템에서 강제종료하므로 app의 작동을 notification 영역에 표시하는 foreground service가 되어야 한다.
1. StartService
앱 내의 액티비티 같은 컴포넌트가 호출했을 때 실행됨
백 그라운드에서 한가지 일을 하며, 결과를 호출했던 컴포넌트에게 보내지 않음
2. bindService
자신을 호출했던 컴포넌트와 인터랙션을 주고받음
처리한 결과를 주고 받음
서로 다른 프로세스상에 있어도 처리 가능
서비스를 다른 컴포넌트가 호출 했을 시 수행되고, 이 메서드를 통해 서비스가 백그라운드에서 동작
리턴 해줘야 되는 3가지 값이 있음
서비스를 실행시켜 놓고 필요할 때마다 서비스의 메서드에 접근하여 통신할 수 있는 구조
서비스가 bindService() 함수로 시작되면
onCreate()-> onBind() 함수 호출
unbindService() 함수로 종료
종료하면서 onUnbind() -> onDestroy() 함수 실행
또한 startService로 실행한 서비스를 bindService()함수로 이에 바인딩 할 수도 있음.
이때는 바인딩을 해제해도 서비스가 소멸되지 않으므로 stopSelf() 혹은 stopService() 호출이 필요하다.
1. Local
2. Local X (다른 프로세스 실행, 즉 다른 apk)
Binder는 기본적으로 프로세스간 통신을 제공한다.
IPC(Inter Process Communication)
Service에서 Binder 만들기
class BoundService : Service() {
override fun onBind(intent: Intent?): IBinder {
return MyLocalBinder()
}
inner class MyLocalBinder : Binder(){
//Service의 레퍼런스를 반환
fun getService() : BoundService{
return this@BoundService
}
}
fun getCurrentTime(): String {
return Date().toString()
}
}
Activity에서 서비스와 연결
class BindActivity : AppCompatActivity() {
private lateinit var binding : ActivityBindBinding
lateinit var myService: BoundService
var isBound = false
val connection = object : ServiceConnection{
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as BoundService.MyLocalBinder
myService = binder.getService()
isBound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
isBound = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityBindBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener {
if(isBound){
val result = myService.getCurrentTime()
binding.textView.text = result
}
}
}
override fun onStart() {
super.onStart()
// binding 처리(연결)
val intent = Intent(this, BoundService::class.java)
bindService(intent, connection, BIND_AUTO_CREATE)
}
}
Messenger & Handler을 사용하하거나 AIDL을 사용하면 별도의 프로세스에서 서비스 부르기 가능
AIDL 파일을 만들고
서비스에서 넣고 싶은 메서드를 넣어주고 Make Project를 하면 자동으로 Interface 파일이 생성된다.
외부 서비스 파일
package com.ssafy.service.c_aidl_binder
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import java.text.SimpleDateFormat
import java.util.*
private const val TAG = "AIDLService_싸피"
class AIDLService : Service() {
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate: ")
}
// AIDL로 정의해서 생성한 Stub() 구현
private val myBinder = object : IMyAidlInterface.Stub(){
override fun getCurrentTime(): String {
Log.d(TAG, "getCurrentTime: ")
return Date().toString()
}
}
override fun onBind(p0: Intent?): IBinder {
Log.d(TAG, "onBind: ")
//위에서 생성한 binder 리턴.
return myBinder
}
}
서비스 호출하는 앱의 클래스
class AIDLActivity : AppCompatActivity() {
private var bound = false
private var timeService: IMyAidlInterface? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
Log.d(TAG, "onServiceConnected: $p1 ")
//binder 를 asInterface로 casting.
timeService = IMyAidlInterface.Stub.asInterface(p1)
bound = true
}
override fun onServiceDisconnected(p0: ComponentName?) {
Log.d(TAG, "onServiceDisconnected: ")
timeService = null
bound = false
}
}
override fun onStart() {
super.onStart()
if (!bound) {
Log.d(TAG, "onStart: ")
//Component Name 생성하고, bindService 호출.
val target = ComponentName("com.ssafy.service.c_aidl_binder" , "com.ssafy.service.c_aidl_binder.AIDLService")
val intent = Intent()
intent.setComponent(target)
bindService(intent, connection, BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
if (bound) {
unbindService(connection)
bound = false
}
}
private lateinit var binding: ActivityAidlactivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAidlactivityBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button2.setOnClickListener{
// val time = timeService?.getCurrentTime()?:"00:00"
val format = SimpleDateFormat("yyyy-MM-dd kk:mm:ss E", Locale.KOREAN) //format 설정
format.timeZone = TimeZone.getTimeZone("Asia/Seoul") //TimeZone 설정 (GMT +9)
binding.textView2.text = "AIDL Service 호출. \n한국시간은 ${format.format(Date().time)} 입니다."
}
}
}
항상 상태바 알림에 표시되는 서비스
활성화된 액티비티와 동급의 우선순위를 가져 메모리가 부족하더라도 시스템에 의해 종료될 확률이 적음.
일반 권한으로, manifest에 선언만 하면 자동 승인된다.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Foreground 서비스 선언
class ForegroundMusicService : Service() {
lateinit var mp: MediaPlayer
override fun onCreate() {
super.onCreate()
mp = MediaPlayer.create(this,R.raw.jazzbyrima)
Log.d(TAG, "onCreate()")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d( TAG, "Action Received = ${intent?.action}" )
when (intent?.action) {
Actions.START_FOREGROUND -> {
Log.d(TAG, "Start Foreground 인텐트를 받음")
if( !mp.isPlaying ){
mp.isLooping = true
mp.start()
startForegroundService()
}
}
Actions.STOP_FOREGROUND -> {
Log.d(TAG, "Stop Foreground 인텐트를 받음")
if( mp.isPlaying ) {
stopForegroundService()
mp.stop() //음악 중지
}
}
Actions.PLAY ->{
Log.d(TAG, "start music from notification : ${mp.isPlaying}")
if( !mp.isPlaying ){
Log.d(TAG, "start music from notification")
mp.start()
}
}
Actions.STOP ->{
Log.d(TAG, "stop music from notification")
if( mp.isPlaying ) mp.pause() //음악 중지
}
}; return START_STICKY
}
private fun startForegroundService() {
val notification = MusicNotification.createNotification(this)
startForeground(NOTIFICATION_ID, notification)
}
private fun stopForegroundService() {
stopForeground(true)
stopSelf()
}
override fun onBind(intent: Intent?): IBinder? {
// bindservice가 아니므로 null
return null
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy()")
}
companion object {
const val NOTIFICATION_ID = 20
}
}
상태바 알림기능
Notification Manager을 통해 채널 생성
// Oreo 부터는 Notification Channel을 만들어야 함
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID, //임의로 만든 채널 ID 값
"Music Player Channel", // 채널표시명
NotificationManager.IMPORTANCE_HIGH
)
val manager = context.getSystemService(NotificationManager::class.java)
manager?.createNotificationChannel(serviceChannel)
}
Notification 생성하여 notification manger로 전송
val intent = Intent(applicationContext, SimpleNotification::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
val pendingIntent = PendingIntent.getActivity(applicationContext, 0,
intent, PendingIntent.FLAG_IMMUTABLE)
val builder = NotificationCompat.Builder(this, channelId)
builder.setSmallIcon(R.drawable.ic_baseline_add_comment_24)
builder.setContentTitle(title)
builder.setContentText(content)
builder.priority = NotificationCompat.PRIORITY_DEFAULT
builder.setAutoCancel(true)
builder.setContentIntent(pendingIntent)
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.notify( 100, builder.build())