개발일지

Android in A..Z - Service 본문

Android (안드로이드)

Android in A..Z - Service

강태종 2021. 10. 7. 17:07

Service

어플리케이션이 종료되도 작업을 유지할 수 있는 요소(Android 4대 Component 중 하나이다.)이다. 크게 Background와 Foreground, Bind로 나눌 수 있습니다. Background는 UI가 제공되지 않고 Foreground는 Notification으로 UI가 제공되며 Bind는 Binder로 Service와 통신할 수 있습니다.

 

* Oreo 이상에서 베터리에 대한 규제가 강화되면서 Background Service를 사용하기 어렵습니다.

https://developer.android.com/about/versions/oreo/background?hl=ko#services 

-> 앱 종료시 Background 종료됩니다.

 

* Service를 생성해도 내부적으로 새로운 Thread를 만드는 것은 아닙니다. Service에서 무거운 작업을 진행한다면 내부에서 새로운 Thread를 만들어야 합니다.

 

* Android Developer에서는 Action으로 Service를 실행하는 방법을 추천하지 않습니다. 그 이유는 원치않는 Service가 호출되어 Resource를 낭비할 수 있기 때문입니다.

-> Class로 명시적으로 실행하면 됩니다.


onCreate

서비스가 처음 생성될 때 수행된다. 초기화 작업을 진행하면 된다.

* 서비스가 이미 진행중일 때 startService를 호출하면 onCreate는 호출되지 않습니다.

 

onBind

bindService로 호출할 때 진행됩니다. IBinder를 반환하여 Service를 호출한 컴포넌트와 통신할 수 있습니다.

* bindService를 사용하지 않는 경우 null을 반환하면 됩니다. onBind를 호출한 컴포넌트가 종료되면 서비스도 같이 종료됩니다.

 

onStartCommand

실제 작업이 이루어지는 공간입니다. startService를 호출하면 진행하며, Service가 만들어진 상태에서는 onCreate가 호출되지 않고, onStartCommand가 바로 호출됩니다.

 

반환값에 따라 Service가 종료 후 재실행 될 때 상태를 정합니다. (Android에서 리소스가 부족하면 Service를 종료시키고 리소스가 확보될 때 다시 실행시키는 경우가 있다.)

  • START_NOT_STICKY : 전달해야 하는 Intent가 없는 경우 서비스를 재생성하지 않습니다. (불필요한 서비스 재생성을 막을 수 있습니다.)
  • START_STICKY : Intent를 null로 지정하여 서비스를 재생성합니다.
  • START_REDELIVER_INTENT : 마지막으로 전달받은 Intent를 통해 재실행 합니다.

 

onDestroy

서비스가 끝날 때 호출됩니다. Thread 종료 등 자원을 반납하는 코드를 작성합니다.

 

stopSelf

서비스가 생성되면 생명주기는 개발자가 직접 관리해야 합니다. 작업이 종료되면 stopSelf를 통해 서비스를 종료 시킬 수 있습니다.


Git (예제코드)

https://github.com/KangTaeJong98/Example/tree/main/Android/AndroidComponent

 

GitHub - KangTaeJong98/Example: My Example Code

My Example Code. Contribute to KangTaeJong98/Example development by creating an account on GitHub.

github.com

open class ScreenStateService : Service() {
    private var screenStateReceiver: ScreenStateReceiver? = null

    override fun onCreate() {
        super.onCreate()
        CoroutineScope(Dispatchers.Main).launch {
            while (true) {
                delay(1500L)
                Toast.makeText(this@ScreenStateService, "Running", Toast.LENGTH_SHORT).show()
                Log.d("PASS", "Running")
            }
        }
    }

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

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        ScreenStateReceiver().also { receiver ->
            screenStateReceiver = receiver
            registerReceiver(receiver, IntentFilter(Intent.ACTION_SCREEN_ON))
        }

        return START_REDELIVER_INTENT
    }

    override fun onDestroy() {
        super.onDestroy()
        screenStateReceiver?.let { receiver ->
            screenStateReceiver = null
            unregisterReceiver(receiver)
        }
    }
}
class MyBindService : Service() {
    private var value = 0

    override fun onCreate() {
        super.onCreate()
        CoroutineScope(Dispatchers.IO).launch {
            while (true) {
                delay(1500L)
                value++
            }
        }
    }

    override fun onBind(intent: Intent): IBinder {
        return object : Binder(), MyBindInterface {
            override fun getValue(): Int {
                return value
            }
        }
    }

    private interface MyBindInterface {
        fun getValue(): Int
    }

    open class MyBindConnection : ServiceConnection {
        private var binder: MyBindInterface? = null

        override fun onServiceConnected(name: ComponentName, binder: IBinder) {
            if (binder is MyBindInterface) {
                this.binder = binder
            }
        }

        override fun onServiceDisconnected(name: ComponentName) {

        }

        fun getValue(): Int {
            return binder!!.getValue()
        }
    }
}
class MainActivity : AppCompatActivity() {
    private val binding: ActivityMainBinding by lazy { DataBindingUtil.setContentView(this, R.layout.activity_main) }
    private var connection: MyBindService.MyBindConnection? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding.lifecycleOwner = this
        binding.setOnBind {
            if (connection == null) {
                val conn = object : MyBindService.MyBindConnection() {
                    override fun onServiceConnected(name: ComponentName, binder: IBinder) {
                        super.onServiceConnected(name, binder)
                        connection = this
                        Toast.makeText(this@MainActivity, getValue().toString(), Toast.LENGTH_SHORT).show()
                    }

                    override fun onServiceDisconnected(name: ComponentName) {
                        super.onServiceDisconnected(name)
                        connection = null
                    }
                }

                bindService(Intent(this, MyBindService::class.java), conn, BIND_AUTO_CREATE)
            } else {
                Toast.makeText(this, connection!!.getValue().toString(), Toast.LENGTH_SHORT).show()
            }
        }
        binding.setOnOff {
            connection?.let { unbindService(it) }
            stopService(Intent(this, MyBackgroundService::class.java))
            stopService(Intent(this, MyForegroundService::class.java))
        }
        binding.setOnBackground {
            startService(Intent(this, MyBackgroundService::class.java))
            stopService(Intent(this, MyForegroundService::class.java))
        }
        binding.setOnForeground {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(Intent(this, MyForegroundService::class.java))
            } else {
                startService(Intent(this, MyForegroundService::class.java))
            }
            stopService(Intent(this, MyBackgroundService::class.java))
        }
    }

    override fun onStart() {
        super.onStart()
        val manager = getSystemService(PowerManager::class.java)
        if (!manager.isIgnoringBatteryOptimizations(packageName)) {
            AlertDialog.Builder(this)
                .setTitle("Request Permission")
                .setMessage("We need permission to running app")
                .setCancelable(false)
                .setPositiveButton("OK") { _, _ ->
                    Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS).also {
                        startActivity(it)
                    }
                }
                .show()
        }
    }
}

'Android (안드로이드)' 카테고리의 다른 글

Android in A..Z - Manifest  (0) 2021.10.12
Android in A..Z - Broadcast Receiver  (0) 2021.10.07
Android in A..Z - Fragment  (0) 2021.10.04
Android in A..Z - Glide Advanced  (0) 2021.10.01
Android in A..Z - Span  (0) 2021.09.16
Comments