안드로이드 Foreground Service 등록하기

손현수·2024년 9월 9일

안드로이드 앱에서 외부 기기와 블루투스를 통해 통신을 하고 있다. 요구사항에 따르면 홈 버튼을 눌러서 앱을 나가더라도 블루투스 기기로부터 데이터를 받아올 수 있어야 한다. 또 다른 요구사항으로는 화면 전환을 하더라도 기존에 수행하고 있던 블루투스 통신이 끊어지면 안되고 계속 유지되어야 한다.
이를 위해 bleService 객체를 Foreground Service로 등록하여 앱 전역에서 객체를 공유하여 사용하고 앱을 나가더라도 블루투스 통신을 통해 데이터를 받아오려고 한다.

MainActivity

  • 모든 컴포저블 함수가 메인 액티비티 위에서 NavHost에 의해 화면 전환이 이루어지므로 블루투스 통신과 관련된 변수와 로직을 MainActivity에 구현하기로 했다.
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    private lateinit var bluetoothManager: BluetoothManager
    private lateinit var bluetoothAdapter: BluetoothAdapter
    private lateinit var scanner: BluetoothLeScanner
    private var bleService: BleService? = null
    private var isServiceBound = false
    private val serviceConnection = object: ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val binder = service as BleService.LocalBinder
            bleService = binder.getService()
            isServiceBound = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            isServiceBound = false
            bleService = null
        }
    }
    private val onClickBtnConnect = { macAddress: String ->
        connectToDevice(macAddress = macAddress)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        startAndBindBleService()

        bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        bluetoothAdapter = bluetoothManager.adapter
        scanner = bluetoothAdapter.bluetoothLeScanner

        setContent {
            // 생략
        }
    }

    private fun startAndBindBleService() {
        val serviceIntent = Intent(this, BleService::class.java)
        startService(serviceIntent)
        bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
    }

    private fun connectToDevice(macAddress: String) {
        if (isServiceBound) {
            bleService?.connect(address = macAddress, bluetoothManager = bluetoothManager)

        } else {
            Log.e("MainActivity", "BleService가 바인딩되지 않았습니다.")
        }
    }

    // 생략

    fun startBluetoothScan(
        addOrUpdateDevice: (String, String, Int) -> Unit = { s: String, s1: String, i: Int -> }
    ) {
        val scanCallback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult?) {
                super.onScanResult(callbackType, result)

                result?.let {
                    val advertisedUuids = it.scanRecord?.serviceUuids

                    if (advertisedUuids != null) {
                        for (uuid in advertisedUuids) {
                            if (uuid.uuid.toString() == Config.UUID) {
                                val rssi = result.rssi

                                if (it.device.name != null) addOrUpdateDevice(it.device.name, it.device.address, rssi)
                            }
                        }
                    }
                }
            }

            override fun onScanFailed(errorCode: Int) {
                super.onScanFailed(errorCode)
            }
        }

        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.BLUETOOTH_SCAN
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }

        scanner.startScan(scanCallback)
    }

    // 생략

    override fun onDestroy() {
        super.onDestroy()
        if (isServiceBound) {
            unbindService(serviceConnection)
            isServiceBound = false
        }
    }
}

ServiceConnection 객체

  • Service와 클라이언트(여기서는 컴포저블 함수)를 연결시켜주는 객체
  • LocalBinder의 경우에는 BleService 내에 정의되어 있는 바인더로 이를 통해 서비스 인스턴스에 접근하는 것이 가능함
  • isServiceBound 변수를 통해 바인딩 여부를 판단할 수 있음

getSystemService 함수

  • 안드로이드 시스템에서 특정 시스템 서비스를 요청하는 함수
  • 리턴값이 object 타입이기 때문에 적절한 서비스 클래스로 형변환해야 함
profile
안녕하세요.

0개의 댓글