서비스

서비스

서비스란?

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

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

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

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 서비스

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

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

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

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

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 } }

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 서비스의 메서드 호출하기

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

MyService.kt

fun serviceMessage() : String { return "Hello Activity! I am Service!" }

MainActivity.kt

// serviceMessage() 호출하는 함수 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

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) } } }

from http://tmdfyd0807.tistory.com/33 by ccl(A) rewrite - 2021-09-22 21:27:59