성장기록지
안드로이드 네트워크 상태 관리 (네트워크 체커) 본문
부스트캠프에서 진행한 프로젝트를 리팩토링하며 네트워크 연결이 끊어졌을 시 체크를 하는 네트워크 체커를 적용하고자 하였다.
기능을 구현하면서 공식문서 및 자료들을 통해 학습한 내용을 정리하고자 한다.
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도 가능
실제 구현 과정
우선 networkState를 StateFlow로 선언해둔다.
위에서 설명한 addTransportType을 통해 추가해줄 transportType을 List로 지정해둔다. (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
}
}
'안드로이드' 카테고리의 다른 글
MVP 패턴이란? (with Android) (0) | 2025.03.06 |
---|---|
MVC 패턴이란? (with Android) (0) | 2025.03.03 |
Glide에 대해 알아보기 - 기본 사용법, 다운 샘플링, Target (0) | 2025.02.11 |
android:exported 는 무엇일까? (0) | 2025.02.10 |
DiffUtil 알아보기 (0) | 2025.01.30 |