[안드로이드] 네트워크 연결 LiveData로 받아보자

1


최근에 면접을 보고오느라 마음이 심란하기도 하고, 일에 대한 집중이 안됩니다.
하지만 블로깅을 하면서 마음을 다잡으려고 노력해야죠!
오늘은 네트워크 연결 LiveData로 콜백 받기를 주제로 하겠습니다.


어떤 로직에서 주로 쓰이는가?

WEBAPP에서 인터넷이 통신할때, Dialog를 호출해야할때가 있습니다.
또는 API 와 통신을 할때에도 미리 체크를 해야하는 로직이 필요한 경우가 있습니다.
오늘 다뤄볼 내용은 네트워크 콜백값을 받는 registerDefaultNetworkCallback입니다.

registerDefaultNetworkCallback이 나오기 전

꽤나 예전에 ConnectivityManager.CONNECTIVITY_ACTION 을통해 broadcast 방식으로 네트워크 상태값을 전달 받았었습니다.
하지만 SDK 24 이후부터 static receiver로 받는것이 금지 되었으니, 사실상 24 이후부터 deprecated된거나 마찬가지입니다.

static receiver

Note: If your app targets API level 26 or higher, you cannot use the manifest to declare a receiver for implicit broadcasts (broadcasts that do not target your app specifically), except for a few implicit broadcasts that are exempted from that restriction. In most cases, you can use scheduled jobs instead


26레벨부터 manifast에서의 receiver등록을 몇개를 제외하고 금지하고 있습니다.
만약 static receiver를 사용하려면 타겟팅을 25로 낮춰야하는데,
그만큼의 side-effect를 감수하고 사용하는 기술같지는 않습니다.
앱이 manifast에 reciever를 등록하게되면 많은 리소스를 낭비하기때문에 다른 앱까지 영향을 미칠 우려가 있다고 합니다.

하지만 기존방식이 deprecated된 만큼 새로운 기술이 우릴 기다리고 있으니, 콜백방식의 registerDefaultNetworkCallback이 되겠습니다.

이제 코딩을 시작해보자

public void registerDefaultNetworkCallback (ConnectivityManager.NetworkCallback networkCallback)

해당 함수를 보면 ConnectivityManager.NetworkCallback을 메소드로 갖습니다.

해당 클래스를 자세히 들어가보면

 public static class NetworkCallback {
        public static final int FLAG_INCLUDE_LOCATION_INFO = 1;

        public NetworkCallback() {
            throw new RuntimeException("Stub!");
        }

        public NetworkCallback(int flags) {
            throw new RuntimeException("Stub!");
        }

        public void onAvailable(@NonNull Network network) {
            throw new RuntimeException("Stub!");
        }

        public void onLosing(@NonNull Network network, int maxMsToLive) {
            throw new RuntimeException("Stub!");
        }

        public void onLost(@NonNull Network network) {
            throw new RuntimeException("Stub!");
        }
  ...
 }

해당 Callback Class를 객체로 만들어 파라미터에 넣으면 네트워크가 변경될때마다
override된 Method로 들어오게 됩니다.

networkCallback = object : ConnectivityManager.NetworkCallback() {


  //불가능으로 변경되었을때 이곳으로 들어온다.
	override fun onUnavailable() {
		super.onUnavailable()
		Timber.d("onUnavailable.. false")
	}
	//Wifi나 셀룰러 데이터가 이용가능하도록 변경될경우 이곳으로 들어온다.
	override fun onAvailable(network: Network) {
		super.onAvailable(network)
		Timber.d("onAvailable.. true")
    }
   
 return networkCallback

하지만 이걸 Activity단에서 적용하게 된다면,몇가지 문제가 있습니다.

  • Activity의 라이프사이클 주기를 따르지 않기때문에 해당 Activity와 종속될경우, 메모리 누수가 일어난다.
  • 해당 NetworkCallbackunregister해주어야 하는데 해제포인트를 찾기가 어렵다.

하지만 이런 문제를 해결하기위해 저희는 LiveData를 이용하기로 했습니다.

class NetworkConnection (context : Context): LiveData<Boolean>() {
private var connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private lateinit var networkCallback: ConnectivityManager.NetworkCallback

    //라이브 데이터에서 오버라이드된 함수. 
    //observe가 실행했을때 들어온다.
    override fun onActive() {
        super.onActive()
        Timber.d("onActive..")
        updateConnection()
        connectivityManager.registerDefaultNetworkCallback(connectivityManagerCallback())
    }

    //라이브 데이터 현재 관측중이지 않을경우..
    //생명주기를 알기때문에 일반적으로 onstop에 들어오게된다.
    override fun onInactive() {
        super.onInactive()
        Timber.d("onInactive..")    connectivityManager.unregisterNetworkCallback(networkCallback)
    }




private fun connectivityManagerCallback(): ConnectivityManager.NetworkCallback {
	Timber.d("connect callback..")
	networkCallback = object : ConnectivityManager.NetworkCallback() {
                override fun onUnavailable() {
                    super.onUnavailable()
                    Timber.d("onUnavailable.. false")
                    postValue(false)
                }

                override fun onAvailable(network: Network) {
                    super.onAvailable(network)
                    Timber.d("onAvailable.. true")
                    postValue(true)
                }
            }
            return networkCallback
    }

잘하셨습니다!
이제 해당 클래스를 activity 에서 observe해서 호출하면 네트워크 감지 기능이 완성되었습니다!
반토막만요..🤣🤣🤣🤣🤣

네 바로 그 반토막요..

문득 적용하면 이상하다는것을 느끼실껍니다.

"어 왜... 와이파이를 키고 앱을켯는데 왜 아무값도 안들어오지?"

이 문제때문에 LiveData를 확장한 클래스를 만들었습니다.

ConnectivityManager.NetworkCallback은 변경시에만 콜백이 호출되기 때문입니다.(엄근진)

그렇다면 onActive() 단에서 현재 네트워크 상태를 알게 해주는 함수를 사용하며 초기값을 알려주면 문제는 완벽히 해결됩니다.


override fun onActive() {
        super.onActive()
        Timber.d("onActive..")
        //해당 초기값을 livedata에 emit해준다.
        postValue(updateConnection())
        connectivityManager.registerDefaultNetworkCallback(connectivityManagerCallback())
}

//해당 함수를 통해 더 세분화해서 초기값을 얻을수 있다.
private fun updateConnection(): Boolean {
        val networkCapabilities = connectivityManager.activeNetwork ?: return false
        val actNw = connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
       val  result = when {
           actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
           actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
           actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
           else -> false
       }
        return result
    }

오늘은 꽤나 코드를 많이 다뤘던 것 같습니다.
비록 회사에서 아무도 알려주지 않는 암담한 앞날일지라도
저의 글 하나하나가 그분들의 앞길을 조금이라도 밝힐수있기를...

저는 이만 물러가겠습니다.

profile
쉽게 가르칠수 있도록 노력하자

1개의 댓글

comment-user-thumbnail
2022년 2월 14일

잘 읽었습니다

답글 달기