안드로이드

네트워크 상태 읽기

jay20033 2025. 2. 20. 06:11

네트워크 권한 설정

네트워크 작업을 실행하기 위해 매니패스트 파일에 다음 두개의 권한을 설정해야 한다.

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

순간적인 상태 가져오기

1. ConnectivityManager

현재 네트워크 상태에 관한 정보 (시스템의 연결 상태)를 알려줌

val connectivityManager = getSystemService(ConnectivityManager::class.java)

 

2. Network

현재 활성화된 네트워크의 객체

  • Network 객체를 키로 사용하여 네트워크 정보를 수집하거나 네트워크에서 소켓을 결합할 수 있음. 만약 네트워크 연결이 끊어지면 Network 객체의 사용이 중지되고 다시 연결되더라도 새 Network 객체는 새 네트워크를 나타냄
val currentNetwork = connectivityManager.getActiveNetwork()

 

3. LinkProperties

네트워크에 설치된 DNS 서버, 로컬 IP 주소, 네트워크 경로 목록 등의 네트워크 연결 정보가 포함됨

val linkProperties = connectivityManager.getLinkProperties(currentNetwork)

 

4. NetworkCapabilities

전송(Wi-Fi, 모바일, 블루투스) 및 네트워크에서 사용할 수 있는 기능과 같은 네트워크 속성 정보가 포함됨

val caps = connectivityManager.getNetworkCapabilities(currentNetwork)

 

동기식 ConnectivityManager는 호출 후 발생하는 일에 관해 앱에 알리지 않으므로 ConnectivityManager에 콜백을 등록하여 변경사항을 알림 받아 즉시 반응할 수 있다.


NetworkCapabilities 및 LinkProperties

LinkProperties

경로, 링크 주소, 인터페이스 이름, 프록시 정보(있는 경우), DNS 서버에 관한 정보를 얻을 수 있다.

NetworkCapabilities

네트워크 전송과 기능에 관한 정보를 캡슐화한다.

전송

네트워크 특정 전송이 있는지 확인하려면 NetworkCapabilities.TRANSPORT_* 상수 중 하나와 NetworkCapabilities.hasTransport(int) 메서드를 사용하면 된다.




 

기능

네트워크마다 가지고 있는 기능이 다르다. MMS 기능이 있는 네트워크는 멀티미디어 메시지 서비스의 메시지를 송수신 할 수 있지만 이런 기능이 없는 네트워크도 있다.

이런 기능을 확인하기 위해서는 NetworkCapabilities.NET_CAPABILITY_* 상수 중 하나와 함께 NetworkCapabilities.hasCapability(int) 메서드를 사용하면 된다.

if (caps?.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS) == true) {
    // MMS 가능
}

if (caps?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) {
    // Wifi 연결됨
}

네트워크 이벤트 콜백 처리

NetworkCallback을 등록해보자.

시스템은 일반적으로 데이터 전송량 제한이 있는 네트워크 보다는 Wifi를 선호하고 빠른 네트워크를 선호한다.

네트워크 요청이 발생하면 시스템은 기본 네트워크를 사용하여 요청을 처리하는데 새로운 네트워크가 기본값이 되면 앱이 여는 새로운 연결은 이 새로운 네트워크를 사용하게 되고 이전의 네트워크에 남아있는 모든 연결이 강제 종료된다. 그래서 기본 네트워크가 변경되는 시점을 알기 위해 네트워크 콜백을 등록하는 것이다.

connectivityManager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network : Network) {
        // 새로운 네트워크가 연결되었을 때 호출
    }

    override fun onLost(network : Network) {
        // 네트워크 연결이 끊겼을 때 호출
    }

    override fun onCapabilitiesChanged(network : Network, networkCapabilities : NetworkCapabilities) {
        // 네트워크 특성이 변경될 때 호출
    }

    override fun onLinkPropertiesChanged(network : Network, linkProperties : LinkProperties) {
        // 네트워크 링크 속성이 변경될 때 호출
        // 네트워크 속도 측정, 대역폭 측정
    }
})

 

더 이상 콜백을 사용하지 않는 경우 ConnectivityManager.unregisterNetworkCallback(NetworkCallback)함수를 호출하여 콜백 해제하면 된다.


네트워크 정보 요청

NetworkRequest

네트워크 연결 요청시 원하는 조건을 설정해서 직접 만든 객체로 네트워크 필터 역할을 한다.

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

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

이 코드에서는 모바일 데이터와 WiFi 전송이 가능한 네트워크를 찾기 위해 NetworkRequest를 생성하고 위와 같이 콜백을 등록하였다.


연결된 네트워크가 없는 경우 다이얼로그 띄우기

sealed class NetworkState {
    object None

    object Connected

    object NotConnected
}

먼저 실시간 연결 정보를 flow로 전달 받기 위해 NetworkState를 정의하였다.

class NetworkChecker(appContext: Context) {
    private val _networkState = MutableStateFlow<NetworkState>(NetworkState.None)
    val networkState: StateFlow<NetworkState> = _networkState

    private val validTransportTypes = listOf(NetworkCapabilities.TRANSPORT_WIFI, NetworkCapabilities.TRANSPORT_CELLULAR)
    private val connectivityManager: ConnectivityManager = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    init {
        initiateNetworkState()
        registerNetworkCallback()
    }

    fun isNetworkAvailable(): Boolean {
        val caps = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
        if (caps != null) {
            return validTransportTypes.any { caps.hasTransport(it) }
        }
        return false
    }

    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
                }
            }
        }
    }

    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)
        }
    }

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

 

위와 같이 NetworkState 초기값을 설정하고, 원하는 조건을 가진 네트워크를 찾기 위한 NetworkRequest를 생성한 뒤 콜백으로 등록하였다. 이로써 네트워크가 변경됨에 따라 콜백이 등록되어 있으므로 현재 네트워크 상태를 flow로 받아 실시간으로 감지할 수 있게 되었다.

 

이후 networkState를 Collect해서 값이 NotConnected이면 다이얼로그를 출력하도록 설계하였다.

@Composable
fun NetworkDialog(
    networkState: NetworkState,
) {
    var showDialog by remember { mutableStateOf(false) }
    val coroutineScope = rememberCoroutineScope()

    LaunchedEffect(networkState) {
        showDialog = networkState is NetworkState.NotConnected
    }
    if (showDialog) {
        WeQuizBaseDialog()
    }
        ...
 }

 

결과 화면