Service
는 보이는 화면 없이 백그라운드에서 오래 실행되는 작업을 수행해요.Service
의 Foreground
유형은 알림이 필수라 백그라운드에서 사용자의 위치를 추적하고 있다는 것을 사용자에게 알릴 수 있어요.android:foregroundServiceType="location"
은 포그라운드 서비스임을 지정하고 앱이 기기의 현재 위치를 가져올 수 있다는 것을 의미해요.<manifest>
//...
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
//...
<service android:name=".services.TrackingService"
android:foregroundServiceType="location">
</service>
//...
</<application>
</manifest>
startService()
로 서비스를 시작하도록 요청해요.Intent
를 startService()
에 전달하여 Service
에 저장할 데이터를 전달할 수 있어요.Intent
)는 onStartCommand()
에 수신돼요.이 앱에서는 서비스의 동작들을 수신했어요.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//...
binding.btnStart.setOnClickListener {
sendCommandToService(ACTION_START_OR_RESUME_SERVICE)
}
binding.btnStop.setOnClickListener {
sendCommandToService(ACTION_STOP_SERVICE)
}
//...
}
private fun sendCommandToService(action: String) {
Intent(requireContext(), TrackingService::class.java).also { Intent ->
Intent.action = action
requireContext().startService(Intent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.let {
when(it.action){
ACTION_START_OR_RESUME_SERVICE -> {
Log.d("test","서비스 시작")
Log.d("test","서비스중..")
}
ACTION_STOP_SERVICE -> {
Log.d("test","서비스 중지")
}
else -> Log.d("","else")
}
}
return super.onStartCommand(intent, flags, startId)
}
Foreground Service
는 상태 표시줄에 대한 알림을 필수로 제공해야 해요. Foreground
에서 제거하지 않은 이상 알림을 해제할 수 없어요.PRIORITY_LOW
이상이어야 해요.이 앱에서는 사용자가 산책을 하고 있다는 것을 알림으로 보여줬어요.
private fun startForegroundService() {
//...
val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setAutoCancel(false)
.setOngoing(true)
.setSmallIcon(R.drawable.ic_paw_theme)
.setContentTitle("어야가자")
.setContentText("어야가자 앱에서 산책이 진행 중입니다..")
.setContentIntent(getMainActivityPendingIntent())
//...
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(notificationManager : NotificationManager ) {
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(channel)
}
PendingIntent
객체로 정의된 콘텐츠 Intent
를 지정하여 setContentIntent()
에 전달해야해요.이 앱에서는 탭을 클릭했을 때
TrackingFragment
가 열리도록 설정했어요.
private fun getMainActivityPendingIntent() = PendingIntent.getActivity(
this,
0,
Intent(this, MainActivity::class.java).also { Intent ->
Intent.action = ACTION_SHOW_TRACKING_FRAGMENT
},
FLAG_UPDATE_CURRENT
)
<navigation ...>
<action android:id="@+id/action_global_trackingFragment"
app:destination="@+id/trackingFragment"
app:launchSingleTop="true"/>
<!--...-->
</navigation>
override fun onCreate(savedInstanceState: Bundle?) {
...
navigateToTrackingFragmentIfNeeded(intent)
//...
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
navigateToTrackingFragmentIfNeeded(intent)
}
private fun navigateToTrackingFragmentIfNeeded(intent: Intent?){
val navHostFragment = supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment
if(intent?.action == ACTION_SHOW_TRACKING_FRAGMENT) {
navHostFragment.findNavController().navigate(R.id.action_global_trackingFragment)
}
}
startForeground()
함수로 알림을 생성하면서 Foreground Service
를 실행해요. override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.let {
when(it.action){
ACTION_START_OR_RESUME_SERVICE -> {
startForegroundService()
Log.d("test","서비스 시작")
Log.d("test","서비스중..")
}
//...
}
}
return super.onStartCommand(intent, flags, startId)
}
private fun startForegroundService() {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(notificationManager)
}
//...
startForeground(NOTIFICATION_ID, notificationBuilder.build())
}
Service
는 생명 주기를 직접 관리해야 해요.stopForeground(true)
와 stopSelf()
로 서비스를 중단 할 수 있어요.이 앱에서는 사용자가 산책 끝내기 버튼을 눌렀을 때 서비스를 중단할 것이기 때문에 UI 업데이트를 위한 데이터를 관찰하여 서비스의 생명주기를 관리할거에요.
class TrackingService: LifecycleService() {
//...
companion object {
val isTracking = MutableLiveData<Boolean>()
}
private fun postInitialValues() {
isTracking.postValue(false)
}
override fun onCreate() {
super.onCreate()
postInitialValues()
}
//...
private fun stopService() {
isTracking.postValue(false)
postInitialValues()
stopForeground(true)
stopSelf()
}
private fun startForegroundService() {
isTracking.postValue(true)
//...
}
//...
}
private var isTracking = false
//...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//...
binding.btnStart.setOnClickListener {
sendCommandToService(ACTION_START_OR_RESUME_SERVICE)
}
binding.btnStop.setOnClickListener {
sendCommandToService(ACTION_STOP_SERVICE)
}
subscribeToObservers()
}
private fun subscribeToObservers() {
TrackingService.isTracking.observe(viewLifecycleOwner, { isTracking ->
TrackingService.isTracking.observe(viewLifecycleOwner,{
updateUI(it)
})
}
private fun updateUI(isTracking: Boolean) {
this.isTracking = isTracking
if(isTracking) {
binding.btnStart.visibility = View.GONE
binding.clWalkLayout.visibility = View.VISIBLE
} else {
binding.btnStart.visibility = View.VISIBLE
binding.clWalkLayout.visibility = View.GONE
}
}
//...
서비스 개요
서비스 매니페스트 파일
알림 만들기
Tracking Service Basic Setup - MVVM Running Tracker App - Part 11
Making Our Service a Foreground Service - MVVM Running Tracker App - Part 12