λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ’» Programming/Android Developer AtoZ

[Android] App Components (3) - Service μ™„λ²½ κ°€μ΄λ“œ

by 기무정 2025. 3. 19.
728x90

 

 

 

Android의 4λŒ€ μ»΄ν¬λ„ŒνŠΈ 쀑 ν•˜λ‚˜μΈ ServiceλŠ” UI 없이 λ°±κ·ΈλΌμš΄λ“œμ—μ„œ μ‹€ν–‰λ˜λ©° μž₯μ‹œκ°„ μž‘μ—…μ„ μˆ˜ν–‰ν•˜λŠ” 핡심 μš”μ†Œμž…λ‹ˆλ‹€.

μŒμ•… μž¬μƒ, λ„€νŠΈμ›Œν¬ μž‘μ—…, GPS λ“± μ‚¬μš©μžμ™€ 직접 μƒν˜Έμž‘μš©ν•˜μ§€ μ•ŠλŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€.

 

이 κ°€μ΄λ“œμ—μ„œλŠ” Service의 κ°œλ…, μ‹€ν–‰ 방식, μ΅œμ‹  λ²„μ „μ˜ μ œμ•½μ‚¬ν•­κΉŒμ§€ μ•Œμ•„λ³΄λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.

 


 

Service

UI 없이 Backgroundμ—μ„œ μ‹€ν–‰λ˜λŠ” μ»΄ν¬λ„ŒνŠΈλ‘œ, μ•±μ˜ μ‹€ν–‰ 여뢀와 관계없이 μž₯μ‹œκ°„ μˆ˜ν–‰ν•΄μ•Ό ν•˜λŠ” μž‘μ—…μ„ μ²˜λ¦¬ν•˜λŠ”λ° μ‚¬μš©λ©λ‹ˆλ‹€.

ServiceλŠ” 앱이 μ‚¬μš©μžμ™€ 직접 μƒν˜Έμž‘μš©ν•˜μ§€ μ•Šμ•„λ„ μ‹€ν–‰λ˜λ©°, λ‹€λ₯Έ μ»΄ν¬λ„ŒνŠΈ(Activity, Broadcast Receiver λ“±)와 μƒν˜Έμž‘μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

ServiceλŠ” λ‹€μŒκ³Ό 같이 2개둜 λ‚˜λˆŒ 수 μžˆμŠ΅λ‹ˆλ‹€.

  • ν¬κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€(Foreground Service)
    • μ•Œλ¦Ό(Notification)κ³Ό 합계 λ°±κ·ΈλΌμš΄λ“œμ—μ„œ μ‹€ν–‰
    • 예) μŒμ•… ν”Œλ ˆμ΄μ–΄, λ„€λΉ„κ²Œμ΄μ…˜ λ“±
  • λ°±κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€(Background Service)
    • UI 없이 λ°±κ·ΈλΌμš΄λ“œμ—μ„œ μ‹€ν–‰
    • Android 8.0(API 26) μ΄μƒμ—μ„œλŠ” 싀행에 μ œν•œ
    • 예) 데이터 동기화, λ„€νŠΈμ›Œν¬ μš”μ²­ λ“±

 

그리고 κ΅¬ν˜„ 방식에 차이가 μžˆλŠ” BoundServiceκ°€ μžˆμŠ΅λ‹ˆλ‹€.

μˆœμ„œλŒ€λ‘œ ν•˜λ‚˜μ”© μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

 

Foreground Service

Foreground ServiceλŠ” μ‚¬μš©μžκ°€ ν˜„μž¬ μ‹€ν–‰ μ€‘μž„μ„ 인지할 수 μžˆλ„λ‘ μ•Œλ¦Ό(Notification)을 ν‘œμ‹œν•˜λ©΄μ„œ λ™μž‘ν•˜λŠ” μ„œλΉ„μŠ€μž…λ‹ˆλ‹€.

보톡 였래 μ‹€ν–‰λ˜κ±°λ‚˜ μ€‘μš”ν•œ μž‘μ—…(μŒμ•… μž¬μƒ, GPS λ“±)을 μˆ˜ν–‰ν•  λ•Œ μ‚¬μš©λ˜λ©°, μ‹œμŠ€ν…œμ˜ λ©”λͺ¨λ¦¬κ°€ 뢀쑱해도 μ‰½κ²Œ μ’…λ£Œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

 

λ‹€μŒμ€ κ°„λ‹¨ν•œ Foreground Service κ΅¬ν˜„ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

class MyForegroundService: Service() {
	
    override fun onCreate() {
        super.onCreate()
        startForeground(1, createNotification()) // Foreground μƒνƒœλ‘œ λ³€κ²½
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // μ‹€ν–‰ν•  μž‘μ—… μˆ˜ν–‰
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        stopForeground(true) // Foreground Service μ’…λ£Œ
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    private fun createNotification(): Notification {
        val channelId = "foreground_service_channel"
        val channelName = "Foreground Service Channel"
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId, channelName, NotificationManager.IMPORTANCE_LOW
            )
            val manager = getSystemService(NotificationManager::class.java)
            manager.createNotificationChannel(channel)
        }

        return NotificationCompat.Builder(this, channelId)
            .setContentTitle("Foreground Service")
            .setSmallIcon(R.drawable.ic_service_icon)
            .build()
    }
}

 

AndroidManifest.xml λ‚΄μ— μ„œλΉ„μŠ€λ₯Ό μ„ μ–Έν•΄μ•Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

<manifest>
  <application>
      <service android:name=".MyForegroundService" />
  </application>
</manifest>

 

μ„œλΉ„μŠ€λ₯Ό μ‹€ν–‰ν•˜κ³ μž ν•˜λŠ” μœ„μΉ˜μ—μ„œ Intentλ₯Ό μ‚¬μš©ν•΄ Foreground Serviceλ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€.

val serviceIntent = Intent(context, MyForegroundService::class.java)
startForegroundService(serviceIntent)

 

 

Foreground Serviceλ₯Ό μ‹€ν–‰ν•  λ•ŒλŠ” λ°˜λ“œμ‹œ startForeground() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•΄μ•Ό ν•©λ‹ˆλ‹€.

μ‹œμŠ€ν…œμ΄ μ„œλΉ„μŠ€ μ’…λ£Œλ₯Ό λ°©μ§€ν•˜κ³ , κ°•μ œ μ’…λ£Œλ  κ°€λŠ₯성을 μ€„μ—¬μ€λ‹ˆλ‹€.

 

참고둜 이 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ§€ μ•ŠμœΌλ©΄ Android 8.0(API 26) μ΄μƒμ˜ ν™˜κ²½μ—μ„œλŠ” μ„œλΉ„μŠ€κ°€ μ‹€ν–‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

startForeground(NOTIFICATION_ID, createNotification())

 

 

λ˜ν•œ, Android 8.0λΆ€ν„° μ„œλΉ„μŠ€μ—μ„œ μ•Œλ¦Όμ„ λ„μš°κΈ° μœ„ν•΄ Notification Channel을 μ„€μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    val channel = NotificationChannel(
        "foreground_service_channel",
        "Foreground Service Channel",
        NotificationManager.IMPORTANCE_LOW
    )
    val manager = getSystemService(NotificationManager::class.java)
    manager.createNotificationChannel(channel)
}

 

 

μ„œλΉ„μŠ€λ₯Ό μ’…λ£Œν•  λ•ŒλŠ” μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

첫 번째둜 μ„œλΉ„μŠ€ λ‚΄μ—μ„œ μ•„λž˜ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜κ±°λ‚˜,

stopSelf()

 

두 번째둜 μ™ΈλΆ€μ—μ„œ Intentλ₯Ό μ‚¬μš©ν•΄ μ’…λ£Œν•©λ‹ˆλ‹€.

val serviceIntent = Intent(context, MyForegroundService::class.java)
stopService(serviceIntent)

 

 

Android 버전에 λ”°λ₯Έ λ³€κ²½ 사항

Android 13 μ΄ν›„λ‘œ 2κ°€μ§€μ˜ μœ ν˜•μ΄ μΆ”κ°€λ˜μ–΄, Android 14λΆ€ν„° Foreground Serviceλ₯Ό 8κ°€μ§€ μœ ν˜•μœΌλ‘œ λΆ„λ₯˜ν•΄μ•Ό ν•©λ‹ˆλ‹€.

이제 앱이 Foreground Serviceλ₯Ό μ‹œμž‘ν•  λ•Œ μ μ ˆν•œ μœ ν˜•μ„ μ„ μ–Έν•΄μ•Ό ν•˜λ©°, λΆ€μ μ ˆν•œ μœ ν˜•μ„ μ‚¬μš©ν•˜λ©΄ 싀행이 μ œν•œλ©λ‹ˆλ‹€.

 

Foreground Service μœ ν˜• μ„€λͺ…
mediaPlayback μŒμ•…, λ™μ˜μƒ λ“±μ˜ λ―Έλ””μ–΄ μž¬μƒμ„ μœ„ν•œ μ„œλΉ„μŠ€
mediaProjection ν™”λ©΄ λ…Ήν™”, 캑처λ₯Ό μœ„ν•œ μ„œλΉ„μŠ€
phoneCall μ „ν™” 및 VolP 톡화λ₯Ό μœ„ν•œ μ„œλΉ„μŠ€
location GPS와 같은 μœ„μΉ˜ 기반 μ„œλΉ„μŠ€
connectedDevice λΈ”λ£¨νˆ¬μŠ€, USB λ“±μ˜ 기기와 μ—°κ²°λœ μ„œλΉ„μŠ€
health ν”ΌνŠΈλ‹ˆμŠ€ 및 건강 κ΄€λ ¨ μ„œλΉ„μŠ€
remoteMessaging ν‘Έμ‹œ μ•Œλ¦Όμ„ ν¬ν•¨ν•œ 원격 λ©”μ‹œμ§• μ„œλΉ„μŠ€
systemExempted μ‹œμŠ€ν…œμ—μ„œ νŠΉλ³„νžˆ ν—ˆμš©ν•˜λŠ” μ„œλΉ„μŠ€ (일반 μ•±μ—μ„œ μ‚¬μš© λΆˆκ°€)

 

 

μ•„λž˜μ™€ 같이 AndroidManifest.xml에 Foreground Service μœ ν˜•μ„ μ„ μ–Έν•΄μ•Ό ν•©λ‹ˆλ‹€.

<manifest>
  <application>
    <service
        android:name=".MyForegroundService"
        android:foregroundServiceType="mediaPlayback"/>
  </application>
</manifest>

 

그리고 μ„ μ–Έν•œ μœ ν˜•μ— λ§žλŠ” κΆŒν•œμ„ μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€.

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

 

 

Background Service

Background ServiceλŠ” μ‚¬μš©μžκ°€ 직접 μΈν„°νŽ˜μ΄μŠ€μ™€ μƒν˜Έμž‘μš©ν•˜μ§€ μ•ŠλŠ” μƒνƒœμ—μ„œ μ‹€ν–‰λ˜λŠ” μ„œλΉ„μŠ€λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€. 즉, 화면에 UIλ₯Ό λ„μš°μ§€ μ•Šκ³  λ°±κ·ΈλΌμš΄λ“œμ—μ„œ 쑰용히 λ™μž‘ν•˜λŠ” μ„œλΉ„μŠ€μž…λ‹ˆλ‹€. Foreground Service와 달리, μ•Œλ¦Όμ„ λ”°λ‘œ ν‘œμ‹œν•˜μ§€ μ•Šμ•„λ„ λ©λ‹ˆλ‹€.

 

ν•˜μ§€λ§Œ Android 8.0 이상뢀터 κ°•λ ₯ν•œ μ œν•œμ΄ λ”°λ₯΄λ©°, Android 13-14λΆ€ν„°λŠ” Foreground Service의 μœ ν˜• λΆ„λ₯˜κΉŒμ§€ λ„μž…λ˜λ©° λ”μš± μ‚¬μš©μ΄ μ–΄λ €μ›Œμ‘ŒμŠ΅λ‹ˆλ‹€.

 

 

λ‹€μŒμ€ κ°„λ‹¨ν•œ Background Service μ˜ˆμ‹œμž…λ‹ˆλ‹€.

class MyBackgroundService: Service() {

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) {
        Thread {
            for (i in 1..10) {
                Log.d("Background Service", "Running: $i")
                Thread.sleep(1000)
            }
            stopSelf()
        }.start()
        return START_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
}
<manifest>
  <application>
      <service android:name=".MyBackgroundService" />
  </application>
</manifest>
val serviceIntent = Intent(this, MyBackgroundService::class.java)
startService(serviceIntent)

 

 

Background Service μ‹€ν–‰ μ œν•œ

μ•žμ—μ„œ μ–ΈκΈ‰ν–ˆλ‹€μ‹œν”Ό, Android 8.0 이상뢀터 λ°±κ·ΈλΌμš΄λ“œ 싀행이 μ œν•œλ©λ‹ˆλ‹€. 즉, 앱이 λ°±κ·ΈλΌμš΄λ“œμ— 있으면 startService() ν•¨μˆ˜ 호좜이 μ‹€νŒ¨ν•˜κ³ , λͺ‡ 초 내에 μžλ™μœΌλ‘œ μ’…λ£Œλ©λ‹ˆλ‹€.

 

λ”°λΌμ„œ Background Service의 λŒ€μ•ˆμœΌλ‘œ, μ•„λž˜ 두 κ°€μ§€ 방법이 μžˆμŠ΅λ‹ˆλ‹€.

  1. Foreground Service둜 λŒ€μ²΄ν•˜κΈ°
  2. WorkManager μ‚¬μš©ν•˜κΈ°

μ•žμ—μ„œ Foreground Service에 λŒ€ν•΄ μ•Œμ•„λ³΄μ•˜μœΌλ‹ˆ, WorkManager에 λŒ€ν•΄μ„œλ§Œ κ°„λž΅νžˆ μ„€λͺ…ν•˜κ² μŠ΅λ‹ˆλ‹€.

 

 

WorkManager

WorkManagerλŠ” λ°±κ·ΈλΌμš΄λ“œ μž‘μ—…μ„ μ•ˆμ „ν•˜κ³  효율적으둜 μ‹€ν–‰ν•˜κΈ° μœ„ν•œ Jetpack λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€.

앱이 μ’…λ£Œλ˜κ±°λ‚˜ κΈ°κΈ°κ°€ μž¬λΆ€νŒ…λ˜μ–΄λ„ μž‘μ—…μ„ μœ μ§€ν•  수 있고, λ„€νŠΈμ›Œν¬λ‚˜ 배터리 μƒνƒœ 등을 κ³ λ €ν•˜μ—¬ μž‘μ—…μ„ μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
    override fun doWork(): Result {
        // λ°±κ·ΈλΌμš΄λ“œμ—μ„œ μ‹€ν–‰ν•  μž‘μ—…
        return Result.success()
    }
}

// Work μš”μ²­ 생성 및 μ‹€ν–‰
val workRequest = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(context).enqueue(workRequest)

 

μž‘μ—…μ„ ν•œ 번 λ˜λŠ”, 주기적으둜 μˆ˜ν–‰ν•˜λ„λ‘ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ‹€μ œλ‘œ Foreground Serviceλ₯Ό 톡해 μˆ˜μ‹œλ‘œ 얻은 μ‚¬μš©μž 데이터λ₯Ό ν•˜λ£¨μ— ν•œ 번 μ„œλ²„μ— μ €μž₯ν•  λ•Œ WorkManagerλ₯Ό μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€. μ‹€ν–‰ 간격을 12μ‹œκ°„μœΌλ‘œ 두긴 ν–ˆλŠ”λ°, μ‹€μ œλ‘œλŠ” 더 λΉ λ₯΄κ±°λ‚˜ λŠλ €μ§€λŠ” κ²½μš°κ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
μ •ν™•νžˆ μ„€μ •ν•œ μ£ΌκΈ°λŒ€λ‘œ λ™μž‘ν•˜μ§€λŠ” μ•Šκ³ , μ–΄λŠ μ •λ„μ˜ μ˜€μ°¨λŠ” μžˆμŠ΅λ‹ˆλ‹€.

 

728x90

 

 

Bound Service

Bound ServiceλŠ” ν΄λΌμ΄μ–ΈνŠΈ(Activity, Fragment λ“±)κ°€ 바인딩(Binding)ν•˜μ—¬ 직접 μƒν˜Έμž‘μš©ν•  수 μžˆλŠ” μ„œλΉ„μŠ€μž…λ‹ˆλ‹€. 즉, ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„κ°€ μ—°κ²°λœ ν˜•νƒœλ‘œ λ™μž‘ν•˜λŠ” μ„œλΉ„μŠ€μž…λ‹ˆλ‹€.

 

Bound ServiceλŠ” ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„(μ„œλΉ„μŠ€) κ°„μ˜ μ–‘λ°©ν–₯ 톡신이 κ°€λŠ₯ν•˜λ©°, ν΄λΌμ΄μ–ΈνŠΈκ°€ 바인딩을 ν•΄μ œν•˜λ©΄ μ„œλΉ„μŠ€κ°€ μžλ™μœΌλ‘œ μ’…λ£Œλ©λ‹ˆλ‹€. Messager, AIDL, Binder λ“±μ˜ λ©”μ»€λ‹ˆμ¦˜μ„ μ‚¬μš©ν•˜μ—¬ 데이터λ₯Ό κ΅ν™˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

Bound Service 기본적인 λ™μž‘μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. ν΄λΌμ΄μ–ΈνŠΈκ°€ bindService()λ₯Ό ν˜ΈμΆœν•˜μ—¬ μ„œλΉ„μŠ€μ— 바인딩
  2. μ„œλΉ„μŠ€λŠ” onBind()λ₯Ό κ΅¬ν˜„ν•˜μ—¬ IBinder 객체λ₯Ό λ°˜ν™˜
  3. ν΄λΌμ΄μ–ΈνŠΈλŠ” IBinderλ₯Ό 톡해 μ„œλΉ„μŠ€μ˜ λ©”μ„œλ“œλ₯Ό 호좜
  4. ν΄λΌμ΄μ–ΈνŠΈκ°€ unbindService()λ₯Ό ν˜ΈμΆœν•˜κ±°λ‚˜ μ†Œλ©Έλ˜λ©΄ μ„œλΉ„μŠ€λ„ μ’…λ£Œ

 

Bound Serviceλ₯Ό κ΅¬ν˜„ν•˜λŠ” μ—¬λŸ¬ κ°€μ§€ 방법을 ν•˜λ‚˜μ”© μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

LocalBinder

ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλΉ„μŠ€κ°€ 같은 ν”„λ‘œμ„ΈμŠ€μ—μ„œ 싀행될 λ•Œ μ‚¬μš©ν•˜λŠ” κ°€μž₯ κ°„λ‹¨ν•œ λ°©λ²•μž…λ‹ˆλ‹€.

μ„œλΉ„μŠ€μ—μ„œ Binder 객체λ₯Ό λ°˜ν™˜ν•˜λ©΄, ν΄λΌμ΄μ–ΈνŠΈλŠ” 이λ₯Ό μ΄μš©ν•΄ μ„œλΉ„μŠ€μ˜ λ©”μ„œλ“œλ₯Ό 직접 ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

직접 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜κΈ° λ•Œλ¬Έμ— μ„±λŠ₯이 μ’‹κ³  μ—¬λŸ¬ 개의 ν΄λΌμ΄μ–ΈνŠΈλ₯Ό λ™μ‹œμ— μ—°κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ λ‹€λ₯Έ ν”„λ‘œμ„ΈμŠ€μ—μ„œλŠ” μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

 

기본적인 λ™μž‘μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. μ„œλΉ„μŠ€μ—μ„œ Binder 객체λ₯Ό μƒμ„±ν•˜κ³  λ°˜ν™˜
  2. ν΄λΌμ΄μ–ΈνŠΈκ°€ bindService()λ₯Ό ν˜ΈμΆœν•˜λ©΄, onBind()μ—μ„œ Binderλ₯Ό λ°˜ν™˜
  3. ν΄λΌμ΄μ–ΈνŠΈλŠ” Binderλ₯Ό μ΄μš©ν•΄ μ„œλΉ„μŠ€μ˜ λ©”μ„œλ“œλ₯Ό 직접 호좜

λ‹€μŒμ€ LocalBinderλ₯Ό μ‚¬μš©ν•˜μ—¬ κ΅¬ν˜„ν•œ μ½”λ“œμž…λ‹ˆλ‹€.

class MyLocalService : Service() {
    private val binder = LocalBinder()

    inner class LocalBinder : Binder() {
        fun getService(): MyBoundService = this@MyLocalService
    }

    override fun onBind(intent: Intent?): IBinder {
        return binder
    }

    fun getData(): String {
        return "Hello from Bound Service!"
    }
}
class MainActivity : AppCompatActivity() {
    private var service: MyLocalService? = null
    private var isBound = false
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            val localBinder = binder as MyLocalService.LocalBinder
            service = localBinder.getService()
            isBound = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            isBound = false
        }
    }

    override fun onStart() {
        super.onStart()
        val intent = Intent(this, MyLocalService::class.java)
        bindService(intent, connection, Context.BIND_AUTO_CREATE)
    }

    override fun onStop() {
        super.onStop()
        if (isBound) {
            unbindService(connection)
            isBound = false
        }
    }
}

 

μ—¬κΈ°μ„œ MyBoundServiceκ°€ μ„œλ²„, MainActivityκ°€ ν΄λΌμ΄μ–ΈνŠΈμ˜ μ—­ν• μž…λ‹ˆλ‹€.

 

μ •λ¦¬ν•˜μžλ©΄, Local BinderλŠ” 같은 μ•± λ‚΄μ—μ„œ μ„œλΉ„μŠ€μ˜ λ©”μ„œλ“œλ₯Ό 직접 ν˜ΈμΆœν•  λ•Œ, ν”„λ‘œμ„ΈμŠ€ κ°„ 톡신(IPC)이 ν•„μš” 없을 λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄ μŒμ•… ν”Œλ ˆμ΄μ–΄ μ•±μ—μ„œ ν˜„μž¬ μž¬μƒ 쀑인 곑의 정보λ₯Ό κ°€μ Έμ˜€κ±°λ‚˜, ν”ΌνŠΈλ‹ˆμŠ€ μ•±μ—μ„œ μ‹€μ‹œκ°„ μ„Όμ„œ 데이터λ₯Ό κ°€μ Έμ˜€λŠ” κ²½μš°μ— μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

Messenger

IPC(Inter-Process-Communication)λ₯Ό μ§€μ›ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€. ν•Έλ“€λŸ¬λ₯Ό μ΄μš©ν•˜μ—¬ μ„œλΉ„μŠ€μ™€ ν΄λΌμ΄μ–ΈνŠΈ 간에 λ©”μ‹œμ§€λ₯Ό μ£Όκ³ λ°›μœΌλ©° 비동기 μ²˜λ¦¬κ°€ κ°€λŠ₯ν•©λ‹ˆλ‹€. λ”°λΌμ„œ MessengerλŠ” 비동기 λ©”μ‹œμ§• 방식이기 λ•Œλ¬Έμ— 직접 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  수 μ—†μŠ΅λ‹ˆλ‹€.

이 방식은 ν΄λΌμ΄μ–ΈνŠΈκ°€ μ„œλΉ„μŠ€μ— 직접 μ ‘κ·Όν•  ν•„μš” 없이 μš”μ²­μ„ λ©”μ‹œμ§€ ν˜•νƒœλ‘œ μ „λ‹¬ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€. λ”°λΌμ„œ 데이터λ₯Ό Message 객체둜 전달해야 ν•˜λ©°, λŒ€μš©λŸ‰ 데이터 μ²˜λ¦¬μ—λŠ” μ ν•©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

 

기본적인 λ™μž‘μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. μ„œλΉ„μŠ€λŠ” Handlerλ₯Ό μƒμ„±ν•˜μ—¬ Messengerλ₯Ό 톡해 ν΄λΌμ΄μ–ΈνŠΈμ˜ λ©”μ‹œμ§€λ₯Ό 처리
  2. ν΄λΌμ΄μ–ΈνŠΈλŠ” Messenger 객체λ₯Ό μ‚¬μš©ν•˜μ—¬ λ©”μ‹œμ§€λ₯Ό 전솑
  3. μ„œλΉ„μŠ€λŠ” λ©”μ‹œμ§€λ₯Ό λ°›κ³  ν•„μš”ν•œ μž‘μ—…μ„ μˆ˜ν–‰ν•œ ν›„ 응닡을 보낼 수 있음

λ‹€μŒμ€ Messengerλ₯Ό μ‚¬μš©ν•˜μ—¬ κ΅¬ν˜„ν•œ μ½”λ“œμž…λ‹ˆλ‹€.

class MyMessengerService : Service() {
    private val messenger = Messenger(IncomingHandler())

    inner class IncomingHandler : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                1 -> Log.d("MessengerService", "Received Message from Client")
                else -> super.handleMessage(msg)
            }
        }
    }

    override fun onBind(intent: Intent?): IBinder {
        return messenger.binder
    }
}
class ClientActivity : AppCompatActivity() {
    private var messenger: Messenger? = null
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            messenger = Messenger(service)
            val message = Message.obtain(null, 1)
            messenger?.send(message)
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            messenger = null
        }
    }

    override fun onStart() {
        super.onStart()
        Intent(this, MyMessengerService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        unbindService(connection)
    }
}

 

MessengerλŠ” μ—¬λŸ¬ ν΄λΌμ΄μ–ΈνŠΈκ°€ ν•˜λ‚˜μ˜ μ„œλΉ„μŠ€μ™€ 톡신해야 ν•˜μ§€λ§Œ, ν•œ λ²ˆμ— ν•˜λ‚˜μ˜ μš”μ²­λ§Œ μ²˜λ¦¬ν•˜λ©΄ λ˜λŠ” κ²½μš°μ— μ‚¬μš©ν•©λ‹ˆλ‹€.

λ˜ν•œ ν”„λ‘œμ„ΈμŠ€ κ°„ 톡신(IPC)이 ν•„μš”ν•˜μ§€λ§Œ λ‹¨μˆœνžˆ λ©”μ‹œμ§€ 전달이 μ£Όλͺ©μ μΌ λ•Œ, 그리고 μ„œλΉ„μŠ€κ°€ μ—¬λŸ¬ ν΄λΌμ΄μ–ΈνŠΈμ™€ μ—°κ²°λ˜μ§€λ§Œ μš”μ²­μ„ 순차적으둜 μ²˜λ¦¬ν•  λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€.

 

예λ₯Ό λ“€μžλ©΄, 파일 λ‹€μš΄λ‘œλ“œ μƒνƒœλ₯Ό UI와 Notification에 λ™μ‹œμ— μ „λ‹¬ν•˜κ±°λ‚˜, κ²Œμž„ μ•±μ—μ„œ λ°±κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€κ°€ μ„œλ²„μ—μ„œ 데이터λ₯Ό 받아와 UIλ₯Ό μ—…λ°μ΄νŠΈν•˜λŠ” κ²½μš°μ— μ‚¬μš©λ  수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

AIDL(Android Interface Definition Language)

AIDL은 λ‹€λ₯Έ ν”„λ‘œμ„ΈμŠ€μ—μ„œλ„ μ„œλΉ„μŠ€μ˜ λ©”μ„œλ“œλ₯Ό 직접 ν˜ΈμΆœν•  수 μžˆλ„λ‘ μ§€μ›ν•˜λŠ” IPC λ°©μ‹μž…λ‹ˆλ‹€.

Messagenger보닀 더 κ°•λ ₯ν•œ κΈ°λŠ₯을 μ œκ³΅ν•˜λ©°, 닀쀑 ν΄λΌμ΄μ–ΈνŠΈκ°€ λ™μ‹œμ— μš”μ²­μ„ 보낼 수 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ 섀정이 λ³΅μž‘ν•˜κ³ , 개발 λΉ„μš©μ΄ λ†’λ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€.

 

기본적인 λ™μž‘μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. AIDL νŒŒμΌμ„ μƒμ„±ν•˜μ—¬ μΈν„°νŽ˜μ΄μŠ€ μ •μ˜ (.aidl 파일)
  2. μ»΄νŒŒμΌλŸ¬κ°€ μžλ™μœΌλ‘œ Stub 클래슀λ₯Ό 생성
  3. ν΄λΌμ΄μ–ΈνŠΈλŠ” binderService()λ₯Ό 톡해 μ„œλΉ„μŠ€μ— 바인딩
  4. ν΄λΌμ΄μ–ΈνŠΈλŠ” Stub 객체λ₯Ό μ‚¬μš©ν•˜μ—¬ 원격 λ©”μ„œλ“œλ₯Ό 직접 호좜

λ‹€μŒμ€ AIDLλ₯Ό μ‚¬μš©ν•˜μ—¬ κ΅¬ν˜„ν•œ μ½”λ“œμž…λ‹ˆλ‹€.

AIDL은 μ•žμ„  LocalBinder, Messenger와 달리 .aidl νŒŒμΌμ„ 생성해야 ν•©λ‹ˆλ‹€.

package com.example.aidl;
interface MyAidlInterface {
    String getMessage();
}
class MyAidlService : Service() {
    private val binder = object : MyAidlInterface.Stub() {
        override fun getMessage(): String {
            return "Hello from AIDL Service!"
        }
    }

    override fun onBind(intent: Intent?): IBinder {
        return binder
    }
}
class ClientActivity : AppCompatActivity() {
    private var myService: MyAidlInterface? = null
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            myService = MyAidlInterface.Stub.asInterface(service)
            Log.d("AIDLService", "Message: ${myService?.getMessage()}")
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            myService = null
        }
    }

    override fun onStart() {
        super.onStart()
        Intent(this, MyAidlService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        unbindService(connection)
    }
}

 

μ •λ¦¬ν•˜μžλ©΄ AIDL은 λ©€ν‹° ν”„λ‘œμ„ΈμŠ€ ν™˜κ²½μ—μ„œ ν΄λΌμ΄μ–ΈνŠΈκ°€ 원격 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜κ³ , λ³΅μž‘ν•œ 데이터(객체, 리슀트 λ“±)λ₯Ό μ²˜λ¦¬ν•  λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€. λ³‘λ ¬λ‘œ μ²˜λ¦¬ν•΄μ•Ό ν•˜λŠ” μš”μ²­μ΄ μžˆκ±°λ‚˜, μ—¬λŸ¬ ν΄λΌμ΄μ–ΈνŠΈκ°€ λ™μ‹œμ— μš”μ²­μ„ 보내고 응닡을 λ°›μ•„μ•Ό ν•˜λŠ” κ²½μš°μ—λ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

예λ₯Ό λ“€λ©΄ λ―Έλ””μ–΄ ν”Œλ ˆμ΄μ–΄ 앱이 Android System Media Session Service와 ν†΅μ‹ ν•˜κ±°λ‚˜, VPN μ„œλΉ„μŠ€μ—μ„œ λ„€νŠΈμ›Œν¬ νŠΈλž˜ν”½μ„ μ œμ–΄ν•˜λŠ” κ²½μš°μ— μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 


 

μ§€κΈˆκΉŒμ§€ μ„œλΉ„μŠ€μ— λŒ€ν•œ κ°œλ…κ³Ό λ™μž‘ 방식, λ‹€μ–‘ν•œ μœ ν˜•μ— λŒ€ν•΄ μ‚΄νŽ΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. μ•ˆλ“œλ‘œμ΄λ“œμ—μ„œ μ„œλΉ„μŠ€λŠ” λ°±κ·ΈλΌμš΄λ“œ μž‘μ—…μ„ μˆ˜ν–‰ν•˜λŠ” 핡심적인 μ»΄ν¬λ„ŒνŠΈμ΄μ§€λ§Œ, 배터리 μ΅œμ ν™” 및 μ‚¬μš©μž κ²½ν—˜μ„ κ³ λ €ν•΄μ•Ό ν•˜λ―€λ‘œ μ‚¬μš©μ— μ£Όμ˜κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

특히 μ΅œμ‹  μ•ˆλ“œλ‘œμ΄λ“œ λ²„μ „μ—μ„œλŠ” λ°±κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€μ— λŒ€ν•œ μ œμ•½μ΄ κ°•ν™”λ˜μ–΄ WorkManager와 같은 λŒ€μ²΄ λ°©μ•ˆμ„ ꢌμž₯ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

μ„œλΉ„μŠ€λŠ” μ•±μ˜ ꡬ쑰와 μ„±λŠ₯에 큰 영ν–₯을 λ―ΈμΉ  수 μžˆμœΌλ―€λ‘œ, 상황에 λ§žλŠ” 졜적의 방식을 μ„ νƒν•˜μ—¬ 효율적으둜 κ΅¬ν˜„ν•˜λŠ” 것이 κ°€μž₯ μ€‘μš”ν•©λ‹ˆλ‹€.

 



μ•ˆλ“œλ‘œμ΄λ“œ 개발자 λ‘œλ“œλ§΅μ„ 따라 μ •λ¦¬ν•œ λ‚΄μš©μž…λ‹ˆλ‹€.

 

 

 

728x90