오늘날 디지털 시대에 WiFi는 기술 발전의 증거로서, 우리 일상에 자연스럽게 통합되어 있다. WiFi의 여정은 1997년 802.11 프로토콜로 시작되었지만, 1999년 802.11b 표준이 도입된 후에야 WiFi 기술이 진정으로 발전하기 시작했다. 이 기술은 기존 로컬 영역 네트워크를 무선으로 확장할 수 있는 간단하고 저렴한 수단을 제공한다.

WiFi 표준의 진화

WiFi는 Ethernet에 무선을 고려하며 만들어져서 아래 그림과 같이 L1, L2의 표준화를 통해서 이루어 졌다. 이로 인해 Ethernet의 Carrier Sense Multiple Access (CSMA), 즉 공유 채널을 사용하는지 확인하는 것은 동일하게 사용한다. Ethernet의 경우는 Collision Detection 즉, CSMA를 하지만 보낼 수 있으면 바로 보내고 다른 쪽과 충돌이 발생하면 Random한 Interval을 가지고 다시 시도하는 반면, 무선은 CSMA를 하지만 Collision Avoidance를 하기 위해서 채널이 비어 있는 상황에서만 시도하는 방식을 사용한다.

 

Ehternet과 WiFi

 

WiFi 기술은 누구나 접근할 수 있고 비용 효율적인 비허가 ISM 스펙트럼에서 운영된다. 802.11b에서 802.11ac와 같은 더 정교한 프로토콜로의 진화는 지속적으로 높은 처리량과 고급 변조 기술을 추구함으로써 WiFi 성능을 크게 향상시켰다.

 

802.11 표준 가족은 802.11b부터 802.11n, 802.11ac에 이르기까지 다양한 기능을 제공하며, 이는 우리의 증가하는 데이터 전송 요구를 충족하기 위해 설계되었다. 이 표준들은 점진적으로 20MHz에서 최대 160MHz까지 대역폭을 확장하고 MIMO 기술을 도입하여 WiFi를 단순한 연결 옵션에서 강력한 네트워크의 중추로 전환시켰다.

WiFi 문제 극복

그럼에도 불구하고, WiFi는 특히 여러 네트워크가 겹치는 밀집된 도심 환경에서 많은 도전에 직면해 있으며, 이는 혼잡과 성능 저하로 이어진다. WiFi를 쉽게 배포할 수 있는 매우 단순함이 오히려 취약점을 초래하기도 하며, 간섭 및 예측 불가능한 네트워크 부하와 같은 문제를 포함한다.

 

WiFi는 대역폭이나 지연 시간을 보장하지 않으며, 그 성능은 주변 환경에 크게 좌우된다. 802.11n 표준의 프레임 집약과 802.11ac의 5GHz 대역 적응과 같은 혁신이 일부 문제를 완화했지만, 공유 채널 접근과 간섭의 근본적인 도전은 여전히 남아 있다.

 

WiFi의 채널 사용 특성

WiFi 사용 최적화

WiFi 배포를 이해하고 최적화하는 것은 애플리케이션 성능을 크게 향상시킬 수 있다. 예를 들어, **혼잡하지 않은 대역에서 다른 사용이 확인되지 않은 대역폭**을 활용하거나 **가변 대역폭**을 사용하는 것은 서비스 중단을 방지하고 사용자 경험을 향상시킬 수 있다.

 

데이터 집약적 애플리케이션에 대해 WiFi 오프로딩과 같은 전략을 사용하면 네트워크 리소스를 효율적으로 사용하면서 성능을 유지하고 셀룰러 네트워크를 통한 데이터 전송과 관련된 비용을 최소화할 수 있다.

미래 방향

앞으로 WiFi 표준과 지속적인 개선이 필수적일 것이다. 서비스 품질을 애플리케이션 수준에서 향상시키는 혁신과 공유 네트워크 리소스의 관리 방법이 WiFi의 다음 단계를 정의할 것이다.

 

결론적으로, WiFi가 우리 네트워크 인프라의 핵심 요소가 되었지만, 그 복잡성을 이해하고 전략적으로 관리하는 것이 그 잠재력을 진정으로 활용하기 위해 필요하다. 더 연결된 미래를 향해 나아가면서, WiFi의 역할은 의심할 여지 없이 확장될 것이며, 우리가 서로 연결된 세계의 증가하는 요구를 충족하기 위해 더욱 정교한 솔루션을 요구할 것이다.

참고자료

[1] 일리아 그리고릭, "구글 엔지니어에게 듣는 네트워킹과 웹 성능 최적화 기법", 정해권, 오현주 공역, 인사이트(insight)

[2] High Performance Browser Networking Ch5 Ch6, https://docs.google.com/presentation/d/1WCDXTVjy9hoou3Ehjz-meoZQUJoPEjc-hGLCPGOkh3M/edit?usp=sharing

들어가며..

최근에 Vibe Coding [1,2] 이라는 이야기가 많이 회자되고 있다. 개발자는 구조나 Product에 집중하고 실재 코딩 작업은 LLM에 맞기는 것이라고 한다. VSCode에 통합되어 있는 AI도구인 Cursor에서는 Agent[3]라는 제품도 나와서 이러한 트렌드를 더 가속화 하는 것으로 보인다. Cursor가 이미 IDE와 결함되어 있고, Agent 그리고 Ask 기능을 이미 제공하고 있기 때문에 여기서 설명하는 내용이 이미 필요 없을 수 있다. 하지만, Cursor를 사용하지 않고도 가능하다고 하면 Cursor를 사용하지 못하기 때문에 AI 도구를 이용해서 효과를 내지 못하는 것은 아니라는 것의 반증이기도 할 것 같다.

나의 사례

여기서 다루려는 내용은 아래 내용으로 나누어 볼 수 있다.

  1. 기존에 존재하는 코드를 이해하고 
  2. 우리가 원하는 기능을 어떻게 구현할지 결정하고
  3. 구현하는 코드를 만들어 적용

 

1번은 우리가 이미 구현하고 있는 Android App기준으로 이 부분은 다른 사례에 적용한다면 개발 환경에 따라 다를 수 있을 것 같다. 이 Android App에 대해 분석하는 예는 따로 설명한다[4]. 2번 부터 3번까지의 경우, Android App 기준으로 하나의 예를 가지고 따라가 보자. 물론 상황에 따라 다를 수 있음을 이해하자.

원하는 기능의 구현

이 부분은 필자의 사례이므로 상황에 따라 많이 다를 수 있다. 우리는 사용자가 특정 기간 동안 다른 동작을 해서 주의가 산만해 지는 것을 확인하기 위해 휴대폰에서 특정 앱을 사용하는 것을 모니터링 하는 것을 구현하려고 했다. 이와 관련해서 ChatGPT에 Prompting해 본 결과 중 일부를 살펴 보자.

Prompt: 너는 pytorch 및 tensorflow의 전문가이고, 동영상 처리에 대한 전문가야. Android에 대해서도 잘 알고 있는 전문가야. 휴대폰의 사용자가 휴대폰 사용하는 동안 어떤 어플리케이션을 사용하고 있는지 알아 내는 모듈을 개발하려고해. 이를 알아내는 방법을 여러가지 제안해줘

ChatGPT said:  (7가지를 이야기 했지만, 아래 3가지만 추출)
휴대폰에서 사용자가 어떤 애플리케이션을 사용하는지 알아내는 모듈을 개발하려면 아래와 같은 여러 가지 접근 방법을 고려할 수 있습니다. 각 접근법은 기술적인 제약, 성능, 그리고 보안 문제를 고려해야 합니다.

 

1. Accessibility Services 활용
- 방법: Android의 접근성 서비스(Accessibility Service)를 이용하면 사용자가 현재 활성화된 앱과 상호작용하는 내용을 감지할 수 있습니다.
- 장점: 사용자가 상호작용하는 앱에 대한 정보를 비교적 간단하게 가져올 수 있습니다.
- 단점: 사용자 동의가 필요하며, 민감한 정보를 처리하기 때문에 오용 시 보안 문제가 발생할 수 있습니다.
- 구현:
    1. `AccessibilityService` 클래스 구현.
    2. `onAccessibilityEvent` 메서드에서 앱 사용 데이터를 수집.

 

2. UsageStatsManager 활용
- 방법: Android의 `UsageStatsManager` API를 사용하여 최근에 사용된 애플리케이션 목록 및 사용 시간을 가져올 수 있습니다.
- 장점: 시스템 API를 사용하므로 안정적이고 공식적인 접근 방식.
- 단점: 사용자에게 Usage Access 권한을 별도로 요청해야 합니다.
- 구현:
    1. `UsageStatsManager` 인스턴스 생성.
    2. `queryUsageStats` 메서드로 주어진 기간 동안 사용된 앱의 데이터를 수집.

 

3. Foreground Service 및 Activity Recognition API
- 방법: 포어그라운드 서비스와 Activity Recognition API를 조합하여 현재 활성화된 앱 상태를 모니터링.
- 장점: 백그라운드에서 안정적으로 작동.
- 단점: 높은 배터리 소모 가능성과 앱 승인 절차.
- 구현:
    1. `ActivityManager`로 현재 실행 중인 태스크 정보 확인.
    2. Activity Recognition API로 사용자 동작(예: 걷기, 정지)도 함께 분석.

여기서 도출된 것들 중 유사한 기능을 다른 유관 부서에서 구현한 것을 보아서 문의 시, UsageStatsManager를 사용하는 것을 알았다. 여기서도 동일한 방법으로 접근해 보기로 결정하였다.

구현하는 코드를 만들어 적용

이를 기반으로 Prompting을 하고 관련된 코드를 입수 하여 독립된 Android App 으로 구현 테스트를 진행한다. 다음과 같은 Prompt를 사용하였다.

너는 android application 개발자야. 다음 요구 사항을 만족하는 application을 만들어줘. 추가적으로 필요한 정보가 있다면, 확인하고 제안해줘. 프로그램을 작성하는 순서를 상세히 설명해줘. 
1. MainActivity를 가지고 있어 
2. MainActivity에는 Start 버튼을 가지고 있어. 이 버튼을 클릭하면, 동작 시간을 측정 시작해. 그리고, Start 버튼이 Stop 버튼으로 변경돼. 
3. MainActivity의 Stop 버턴을 클릭하면, 휴대폰에서 사용자가 다른 어플리케이션을 사용한 내용을 정리해서 알려주는 기능을 가지고 있어. 이 부분은 UsageStatsManager에서 사용 이력을 확인할 수 있어.

이 것 기반으로 Android에서 동작하는 Kotlin 코드를 제공한다. 실제로 Android Studio에 코드를 적용하고, 테스트를 한다. 여기서 획득한 Helper 코드의 기본적인 테스트가 완료되어서, 실재 우리 App과 연동해 보고 동작하는 것을 확인하였다. 

마무리하며

간단한 사례이지만, 여기서 의미 있는 부분은 나의 문제를 명확하게 하고 어떻게 해결할지 고민하면서 필요한 요구 사항을 LLM에게 제공하여 동작 가능한 코드를 제공받았다. 개발자의 학습면에서도 좋다고 생각되는 부분은 제공 받은 코드를 통해서 Kotlin에 대해서 학습할 기회를 얻게 된 부분도 부수적인 장점으로 생각된다.


참고 문헌

[1] https://youtu.be/mzomjCHEPpg?si=TJRR-4Er7arf5m-j
[2] https://youtu.be/IACHfKmZMr8?si=rGTS_SlI96HS16no
[3] https://youtu.be/gKCklJOWB6o?si=N95cuv_Nf23Ar8S8
[4] Modern Anroid Application의 구조 이해, https://technical-leader.tistory.com/174

ViewModel을 사용할 때 Factory를 사용하는 경우와 사용하지 않는 경우에는 큰 차이가 있다. 이 차이를 이해하면, ViewModel의 인스턴스 생성 방식의존성 주입(DI) 활용 방법을 더욱 효과적으로 설계할 수 있다.

1. 기본적인 ViewModel 사용 (Factory 없이)

특징

  • ViewModel이 기본 생성자만 가진 경우 Factory 없이 사용 가능.
  • ViewModelProvider를 사용하여 Activity 또는 Fragment에 간단히 연결 가능.
  • 인수가 필요 없는 ViewModel에 적합.

예제 코드

class MainViewModel : ViewModel() {
    val message = MutableLiveData<String>()
    fun updateMessage(newMessage: String) {
        message.value = newMessage
    }
}

 

class MainFragment : Fragment() {
    private val viewModel: MainViewModel by viewModels()
}

장점

  • 간단하고 구현이 쉬움 (by viewModels() 또는 ViewModelProvider 사용 가능).
  • 생명주기를 고려하여 자동으로 관리됨.

단점

  • 생성자에서 파라미터를 받을 수 없음 (Repository, UseCase 같은 객체를 직접 주입할 수 없음).
  • 의존성 주입(DI)이 어렵고, ViewModel 내부에서 객체를 직접 생성해야 함 (유지보수 어려움).

2. Factory를 사용하는 ViewModel

특징

  • ViewModel 생성자에서 파라미터를 받는 경우 반드시 Factory가 필요함.
  • 보통 Repository, UseCase, API 서비스 등의 객체를 ViewModel에 주입할 때 사용.
  • DI(Dependency Injection) 환경 (예: Hilt, Dagger)에서 활용하기 좋음.

예제 코드

① ViewModel이 생성자를 가지는 경우

class MainViewModel(private val repository: DataRepository) : ViewModel() {
    val data: LiveData<String> = repository.getData()
}

② Custom ViewModelFactory 구현

class MainViewModelFactory(private val repository: DataRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return MainViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

③ Factory를 이용한 ViewModel 생성

class MainFragment : Fragment() {
    private val repository = DataRepository()
    private val viewModel: MainViewModel by viewModels {
        MainViewModelFactory(repository)
    }
}

장점

  • ViewModel의 생성자를 통해 의존성을 주입할 수 있음 (Repository, UseCase 등).
  • DI를 활용하여 테스트가 용이하고 유지보수가 쉬움.

단점

  • Factory 클래스를 따로 구현해야 하므로 코드가 길어질 수 있음.
  • Factory를 생성할 때 ViewModel을 명시적으로 매핑해야 하는 과정이 필요함.

3. Factory를 사용하지 않는 경우 vs Factory를 사용하는 경우 비교

항목Factory 사용 X (by viewModels())Factory 사용 O (Factory 필요)

사용 가능 조건ViewModel에 생성자 파라미터 없음ViewModel에 생성자 파라미터 필요
ViewModel 생성 방식by viewModels() 또는 ViewModelProviderViewModelProvider.Factory를 통해 생성
의존성 주입(DI)불가능 (ViewModel 내부에서 직접 객체 생성)가능 (Repository, UseCase 등 주입 가능)
테스트 용이성어려움 (ViewModel 내부에서 직접 객체 생성)쉬움 (의존성을 모킹(Mock)할 수 있음)
코드의 복잡성단순하고 짧음Factory 클래스를 추가해야 함

4. ViewModelFactory를 활용하는 고급 예제

(1) ViewModel에 여러 개의 의존성을 주입하는 경우

class MainViewModel(
    private val repository: DataRepository,
    private val analyticsTracker: AnalyticsTracker
) : ViewModel() {
    fun logEvent(event: String) {
        analyticsTracker.trackEvent(event)
    }
}

(2) Factory에서 여러 개의 파라미터를 전달

class MainViewModelFactory(
    private val repository: DataRepository,
    private val analyticsTracker: AnalyticsTracker
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return MainViewModel(repository, analyticsTracker) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

(3) Fragment에서 Factory를 활용한 ViewModel 생성

class MainFragment : Fragment() {
    private val repository = DataRepository()
    private val analyticsTracker = AnalyticsTracker()

    private val viewModel: MainViewModel by viewModels {
        MainViewModelFactory(repository, analyticsTracker)
    }
}

5. Hilt를 이용한 ViewModel Factory 대체 방법

최근에는 DI 라이브러리(Hilt, Dagger)를 사용하면 Factory를 직접 구현할 필요 없음.

(1) Hilt를 활용한 ViewModel 생성

① ViewModel에 @HiltViewModel 추가

@HiltViewModel
class MainViewModel @Inject constructor(private val repository: DataRepository) : ViewModel() {
    val data: LiveData<String> = repository.getData()
}

② Repository에 @Inject 추가

class DataRepository @Inject constructor() {
    fun getData(): LiveData<String> {
        return MutableLiveData("Hello from Repository")
    }
}

③ Fragment에서 간단하게 ViewModel 생성

@AndroidEntryPoint
class MainFragment : Fragment() {
    private val viewModel: MainViewModel by viewModels()
}
  • Hilt를 사용하면 Factory 클래스를 직접 만들 필요 없이, 의존성이 자동으로 주입됨.
  • 이 방식이 현재 Android 개발의 표준적인 접근법.

6. 결론

  • ViewModel에 생성자 파라미터가 필요 없다면 Factory 없이 by viewModels() 사용.
  • ViewModel에 생성자 파라미터가 필요하면 반드시 Factory를 사용해야 함.
  • DI 라이브러리(Hilt, Dagger)를 활용하면 Factory 없이도 자동으로 의존성 주입 가능.
  • 테스트 가능성과 유지보수를 고려하면 Factory 또는 Hilt를 사용하는 것이 권장됨.

📌 정리:

간단한 ViewModel → by viewModels()
의존성이 있는 ViewModel → Factory 사용 or Hilt 활용

+ Recent posts