Notice
Recent Posts
Recent Comments
«   2025/05   »
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 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

성장기록지

안드로이드 네트워크 상태 관리 (네트워크 체커) 본문

안드로이드

안드로이드 네트워크 상태 관리 (네트워크 체커)

pengcon 2025. 2. 18. 23:02
부스트캠프에서 진행한 프로젝트를 리팩토링하며 네트워크 연결이 끊어졌을 시 체크를 하는 네트워크 체커를 적용하고자 하였다.
기능을 구현하면서 공식문서 및 자료들을 통해 학습한 내용을 정리하고자 한다.

 

ConnectivityManager

안드로이드에는 ConnectivityManager라는 클래스가 있다.

공식문서에는 다음과 같이 설명되어 있다.

 

네트워크 연결 상태에 대한 쿼리에 응답하는 클래스. 또한 네트워크 연결이 변경되면 애플리케이션에 알림.

이 클래스의 주요 임무는 다음과 같다.

  • 네트워크 연결(Wi-Fi, GPRS, UMTS 등) 모니터링
  • 네트워크 연결이 변경되면 브로드캐스트 인텐트 전송
  • 네트워크 연결이 끊어지면 다른 네트워크로 '장애 조치' 시도
  • 애플리케이션이 사용 가능한 네트워크의 거시적 또는 세분화된 상태를 쿼리할 수 있는 API 제공
  • 애플리케이션이 데이터 트래픽을 위해 네트워크를 요청하고 선택할 수 있는 API 제공

안드로이드에서 ConnectivityManager는 다음과 같이 불러올 수 있다.

private val connectivityManager: ConnectivityManager = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

네트워크 요청 구성 (NetworkRequest)

Wi-Fi 또는 모바일 데이터 연결과 같은 네트워크의 전송 유형과 현재 연결된 네트워크의 기능(예: 인터넷 연결)을 지정하려면 네트워크 요청을 구성해야 한다.

앱의 네트워크 연결 요구사항을 설명하는 NetworkRequest를 선언한다.

예시 코드는 인터넷에 연결되어 있고 전송 유형에 Wi-Fi 또는 모바일 네트워크 연결을 사용하는 네트워크에 대한 요청을 만든다.

val networkRequest = NetworkRequest.Builder()
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
        .build()

 

addCapability

  • 네트워크가 지원해야 하는 기능을 지정한다.

addTransportType

  • 네트워크의 물리적 전송 방식을 지정한다.

 

 

 

networkCallback

네트워크 변경을 감지하기 위한 NetworkCallback을 구현해야한다.

공식문서에도 아래와 같이 ConnectivityManager에 콜백을 등록하여 알림을 받을 수 있다고 한다.

예시 코드는 다음과 같다.

private val networkCallback = object : ConnectivityManager.NetworkCallback() {
    // network is available for use
    override fun onAvailable(network: Network) {
        super.onAvailable(network)
    }

    // Network capabilities have changed for the network
    override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities
    ) {
        super.onCapabilitiesChanged(network, networkCapabilities)
        val unmetered = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
    }

    // lost network connection
    override fun onLost(network: Network) {
        super.onLost(network)
    }
}

 

업데이트를 등록하자

위에서처럼 NetworkRequest  NetworkCallback을 선언한 후

requestNetwork()또는registerNetworkCallback() 함수를 사용하여 NetworkRequest를 충족하는 기기에서 연결할 네트워크를 검색한다. 그러면 상태가 NetworkCallback에 보고되는 과정을 거친다.

공식문서의 예시코드는 아래와 같다. 

val connectivityManager = getSystemService(ConnectivityManager::class.java) as ConnectivityManager
connectivityManager.requestNetwork(networkRequest, networkCallback) // registerNetworkCallback도 가능

 

실제 구현 과정

우선 networkStateStateFlow로 선언해둔다.

위에서 설명한 addTransportType을 통해 추가해줄 transportTypeList로 지정해둔다. (validTransportTypes)

이후 ConnectivityManager를 불러와 변수로 선언해둔다. 

class NetworkMonitorImpl @Inject constructor(
    appContext: Context,
) : NetworkMonitor {
    private val _networkState = MutableStateFlow<NetworkState>(NetworkState.None)
    override val networkState: StateFlow<NetworkState> = _networkState

    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    private val validTransportTypes = listOf(
        NetworkCapabilities.TRANSPORT_WIFI,
        NetworkCapabilities.TRANSPORT_CELLULAR,
    )

    private val connectivityManager: ConnectivityManager = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

 

 

그다음 활용할 networkCallback을 지정해둔다.

네트워크 연결이 끊어질 때 onLost가 호출된다.

하지만 와이파이에서 데이터로 넘어갈 때 잠깐동안도 onLost가 호출되므로,

delay(NETWORK_CHECK_DELAY) 를 설정해둬서 (1초 딜레이)

onLost 호출 1초 이후에도 사용가능한 네트워크가 없을 때 networkState를 바꿔주도록 하였다.

private val networkCallback = object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        super.onAvailable(network)
        _networkState.value = NetworkState.Connected
    }

    override fun onLost(network: Network) {
        super.onLost(network)
        scope.launch {
            delay(NETWORK_CHECK_DELAY)
            if (isNetworkAvailable().not()) {
                _networkState.value = NetworkState.NotConnected
            }
        }
    }
}

 

isNetworkAvailable() 의 구현은 다음과 같다.

 private fun isNetworkAvailable(): Boolean {
        val capabilities =
            connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)

        if (capabilities != null) {
            return validTransportTypes.any { capabilities.hasTransport(it) }
        }

        return false
    }

 

 

초기화

isNetworkAvailable()을 활용하여 처음에 초기화를 해주고,

위의 "업데이트를 등록하자"의 코드를 응용하여 TransportType 리스트를 add해준 후 

registerNetworkCallback을 통해 등록을 하였다.

이렇게하면 네트워크 상태에 따라 값이 갱신되어진다.

init {
    initiateNetworkState()
    registerNetworkCallback()
}

private fun initiateNetworkState() {
    _networkState.value = if (isNetworkAvailable()) {
        NetworkState.Connected
    } else {
        NetworkState.NotConnected
    }
}

private fun registerNetworkCallback() {
    NetworkRequest.Builder().apply {
        validTransportTypes.forEach { addTransportType(it) }
    }.let {
        connectivityManager.registerNetworkCallback(it.build(), networkCallback)
    }
}

 

전체 코드는 아래와 같다.

class NetworkMonitorImpl @Inject constructor(
    appContext: Context,
) : NetworkMonitor {
    private val _networkState = MutableStateFlow<NetworkState>(NetworkState.None)
    override val networkState: StateFlow<NetworkState> = _networkState

    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    private val validTransportTypes = listOf(
        NetworkCapabilities.TRANSPORT_WIFI,
        NetworkCapabilities.TRANSPORT_CELLULAR,
    )

    private val connectivityManager: ConnectivityManager = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            super.onAvailable(network)
            _networkState.value = NetworkState.Connected
        }

        override fun onLost(network: Network) {
            super.onLost(network)
            scope.launch {
                delay(NETWORK_CHECK_DELAY)
                if (isNetworkAvailable().not()) {
                    _networkState.value = NetworkState.NotConnected
                }
            }
        }
    }

    init {
        initiateNetworkState()
        registerNetworkCallback()
    }

    private fun initiateNetworkState() {
        _networkState.value = if (isNetworkAvailable()) {
            NetworkState.Connected
        } else {
            NetworkState.NotConnected
        }
    }

    private fun registerNetworkCallback() {
        NetworkRequest.Builder().apply {
            validTransportTypes.forEach { addTransportType(it) }
        }.let {
            connectivityManager.registerNetworkCallback(it.build(), networkCallback)
        }
    }

    override fun checkCurrentNetworkState() {
        initiateNetworkState()
    }

    private fun isNetworkAvailable(): Boolean {
        val capabilities =
            connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)

        if (capabilities != null) {
            return validTransportTypes.any { capabilities.hasTransport(it) }
        }

        return false
    }

    companion object {
        private const val NETWORK_CHECK_DELAY = 1000L
    }
}