Archives
Recent Posts
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Today
Total
관리 메뉴

안드로이드 개발자의 창고

[39일차 Android] Service 본문

Computer/Android

[39일차 Android] Service

Wise-99 2023. 6. 29. 18:17

 

 

 

출처 : 안드로이드 앱스쿨 2기 윤재성 강사님 수업 PPT

 

 

 

📖 Service

  • 안드로이드 4대 구성 요소 중 하나로 백그라운드 처리를 위해 제공되는 요소
  • Activity는 화면을 가지고 있어 화면이 보이는 동안 동작하지만 Service는 화면을 가지고 있지 않아 보이지 않는 동안에도 동작하는 것을 의미한다.

Forground Service

  • Service는 백그라운드에서 운영되는 실행 요소로써 메모리가 부족해지거나 절전 모드가 되는 등 다양한 상황에서 안드로이드 OS에 의해 제거 될 수 있다.
  • 이를 방지하고자 할 때는 Foreground Service로 만들어 사용하면 된다.
  • Foreground Service외의 서비스는 안드로이드 OS에 의해 모두 제거될 수 있다.
  • Foreground Service의 목적은 현재 단말기에서 Service를 통해 백그라운드에서 작업 중이라는 것을 사용자에게 알리는 목적으로 사용한다.

예제 코드

TestService.kt

val NOTIFICATION_CHANNEL1_ID = "Service"
val NOTIFICATION_CHANNEL1_NAME = "Service"

var isRunning = false
var value = 0

// Activity가 서비스에 접속하면 전달될 바인더 객체
val binder = LocalBinder()

// 외부에서 서비스에 접속하면 자동으로 호출되는 메서드
// 여기에서는 바인더 객체를 반환한다.
override fun onBind(intent: Intent): IBinder {
    return binder
}
// 서비스가 가동되면 자동으로 호출되는 메서드
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

    addNotificationChannel(NOTIFICATION_CHANNEL1_ID, NOTIFICATION_CHANNEL1_NAME)

    // 안드로이드 버전 확인
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        val builder = getNotificationBuilder(NOTIFICATION_CHANNEL1_ID)
        
        builder.setSmallIcon(android.R.drawable.ic_menu_search)
        builder.setContentTitle("서비스 가동")
        builder.setContentText("서비스가 가동 중입니다")
        
        val notification = builder.build()
        startForeground(10, notification)
    }

    isRunning = true
    thread {
        while(isRunning == true){
            SystemClock.sleep(500)
            val now = System.currentTimeMillis()
            Log.d("now", "now : $now")
            value++
        }
    }

    return super.onStartCommand(intent, flags, startId)
}
  • 안드로이드 버전 8.0 이상인 에뮬레이터에서 확인할 수 있도록 Notification을 발생시킨다.
  • Thread를 발생시켜 현재 시간을 계속 LogCat에 출력한다.

 

// 서비스가 중지되면 호출되는 메서드
override fun onDestroy() {
    super.onDestroy()
    // 쓰래드의 while문 중단을 위해
    isRunning = false
}
  • 위에서 발생시킨 Thread의 while문을 멈추기 위해 isRunning의 값을 false로 설정해준다.

 

// Notification Channel을 등록하는 메서드
fun addNotificationChannel(id:String, name:String){
    
    // 안드로이드 8.0 이상일 때만 동작하게 한다.
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        // 알림 메시지를 관리하는 객체를 추출한다.
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        // id를 통해 NotificationChannel 객체를 추출한다.
        // 채널이 등록된 적이 없다면 null을 반환한다.
        val channel = notificationManager.getNotificationChannel(id)
       
       // 채널이 등록된 적이 없다면...
        if(channel == null){
            // 채널 객체를 생성한다.
            val newChannel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH)
            // 단말기에 LED 램프가 있다면 램프를 사용하도록 설정한다.
            newChannel.enableLights(true)
            // LED 램프의 색상을 설정한다.
            newChannel.lightColor = Color.RED
            // 진동을 사용할 것인가?
            newChannel.enableVibration(true)
            // 채널을 등록한다.
            notificationManager.createNotificationChannel(newChannel)
        }
    }
}

// Notification 메시지 관리 객체를 생성하는 메서드
// Notification 채널 id를 받는다.
fun getNotificationBuilder(id:String) : NotificationCompat.Builder{
    // 안드로이드 8.0 이상이라면
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        val builder = NotificationCompat.Builder(this, id)
        return builder
    } else {
        val builder = NotificationCompat.Builder(this)
        return builder
    }
}

// 접속하는 Activity에서 서비스를 추출히기 위해 사용하는 객체
inner class LocalBinder : Binder(){
    public fun getService() : TestService{
        return this@TestService
    }
}

// 변수의 값을 반환하는 메서드
public fun getNumber():Int{
    return value
}

 

 

 

MainActivity.kt

lateinit var activityMainBinding: ActivityMainBinding

// 서비스를 가동시키기 위해 사용할 인텐트
lateinit var serviceIntent2:Intent

// 동작중인 서비스 객체를 담을 변수
var ipcService:TestService? = null

// 서비스 접속을 관리하는 객체
lateinit var serviceConnectionClass:ServiceConnectionClass
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(activityMainBinding.root)

    activityMainBinding.run{
        button.setOnClickListener {
            // 현재 서비스가 실행중인지 파악한다.
            val chk = isServiceRunning("com.test.android54_service.TestService")
            serviceIntent2 = Intent(this@MainActivity, TestService::class.java)

            // 서비스가 가동중이 아니라면 서비스를 가동한다.
            if(chk == false) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    startForegroundService(serviceIntent2)
                } else {
                    startService(serviceIntent2)
                }
            }

            // 서비스에 접속한다.
            serviceConnectionClass = ServiceConnectionClass()
            bindService(serviceIntent2, serviceConnectionClass, BIND_AUTO_CREATE)
        }

        button2.setOnClickListener {
            stopService(serviceIntent2)
        }

        button3.setOnClickListener {
            // 서비스에서 데이터를 가져온다.
            val value = ipcService?.getNumber()
            activityMainBinding.textView.text = "value : $value"
        }
    }
}
  • button
    • 서비스가 가동 중인지를 확인하여 중복으로 가동되지 않도록 한다.
    • 이를 확인하지 않는다면 서비스가 가동될 때마다 TestSerrvice의 onStartCommand() 메서드가 계속 실행되므로 Notification과 Thread가 추가로 실행된다.
    • 서비스가 가동 중인 것을 확인한 후 서비스에 접속한다.

 

  • button2
    • 서비스를 중지시킨다.
    • TestService의 onDestroy()가 실행되어 Thread와 서비스가 중지된다.

 

  • button3
    • 서비스에 접근하여 서비스에서 데이터를 가져온다.
    • TestService의 Thread에서 LogCat에 출력하고 있는 현재 시간의 개수(value)를 가져온다.

 

// 서비스가 가동중인지 확인하는 메서드
fun isServiceRunning(name:String) : Boolean{
    // 서비스 관리자를 추출한다.
    val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
    // 현재 실행중인 모든 서비스를 가져온다.
    val serviceList = activityManager.getRunningServices(Int.MAX_VALUE)
    // 가져온 서비스의 수 만큼 반복한다.
    for(serviceInfo in serviceList){
        // 현재의 서비스의 클래스 이름이 매개변수로 전달된 클래스 이름과 동일한지...
        if(serviceInfo.service.className == name){
            return true
        }
    }
    return false
}
  • button에서 현재 서비스가 실행 중인지 파악할 때 사용하는 메서드
  • 모든 서비스를 가져와 넘겨준 서비스의 이름과 같은게 있다면 true를 반환하고 없으면 false를 반환한다.
  • 즉 넘겨준 이름의 서비스가 실행 중이면 true, 실행 중이지 않으면 false를 반환한다.

 

// Activity의 서비스 접속을 관리하는 클래스
inner class ServiceConnectionClass : ServiceConnection{
    // 서비스 접속이 성공했을 경우
    // 두 번째 매개변수 : 서비스의 onBind 메서드가 반환하는 객체
    override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
        // 서비스를 추출한다.
        val binder = p1 as TestService.LocalBinder
        ipcService = binder?.getService()
    }

    // 서비스 접속이 해제 되었을 때
    override fun onServiceDisconnected(p0: ComponentName?) {
        ipcService = null
    }
}
  • button에서 서비스에 접속할 때 사용되는 클래스
  • 서비스 접속에 성공하면 TestService의 onBind() 메서드를 통해 binder 객체를 받아 서비스를 추출한다.
  • 서비스 접속이 해제되면 null을 반환한다.
  • 이 값은 button의 bindService에 담겨진다.

 

override fun onDestroy() {
    super.onDestroy()

    // 서비스가 접속 중이라면 접속을 해제한다.
    if(::serviceConnectionClass.isInitialized == true){
        unbindService(serviceConnectionClass)
    }
}

 

'Computer > Android' 카테고리의 다른 글

[41일차 Android] ActionBar  (0) 2023.07.01
[39일차 Android] Fargment  (0) 2023.07.01
[39일차 Android] Broadcast Receiver  (0) 2023.06.27
[39일차 Android] Thread, runOnUiThraad  (0) 2023.06.26
[38일차 Android] 다양한 Notification  (0) 2023.06.25