업무용 안드로이드 앱을 만들다 보면 "동기화할 때만 모바일 데이터를 켰다가 끝나면 끄게 해 달라"는 요구가 가끔 들어옵니다. 결론부터 말하면 일반 앱에서는 할 수 없습니다. 예전에는 ConnectivityManager 의 숨겨진 메서드를 리플렉션으로 불러 데이터 연결을 켜고 끄는 꼼수가 돌았지만, Android 4.4 무렵부터 이 방법이 막혔습니다. 사용자 모르게 앱이 통신을 켜서 요금을 발생시키거나, 반대로 끊어 버릴 수 있다는 보안 문제 때문입니다. 지금 이 권한은 시스템 앱이나 통신사 권한을 가진 앱에만 열려 있다고 보면 됩니다.

그래서 남는 방법은 사용자에게 직접 켜고 끄게 하는 것뿐입니다. 앱이 할 수 있는 일은 두 가지입니다. 왜 데이터 연결이 필요한지 메시지로 알려 주는 것, 그리고 버튼 하나로 해당 설정 화면까지 데려다주는 것이죠.

광고

설정 화면으로 보내기

모바일 데이터 스위치가 있는 화면은 제조사마다 메뉴 위치가 달라서, 표준으로 정의된 설정 인텐트를 쓰는 게 안전합니다. 무선·네트워크 설정 화면은 아래처럼 엽니다.

Intent intent = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
startActivity(intent);

Android 10 이상이라면 앱 위에 인터넷 연결 패널을 띄우는 Settings.Panel.ACTION_INTERNET_CONNECTIVITY 도 있습니다. 화면 전환 없이 작은 패널에서 Wi-Fi와 모바일 데이터를 바로 켤 수 있어서 사용자 입장에서 훨씬 덜 번거롭습니다.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    startActivity(new Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY));
} else {
    startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
}

돌아온 뒤에는 연결 상태를 다시 확인

사용자가 설정 화면에서 실제로 데이터를 켰는지는 앱이 알 수 없습니다. 설정에서 돌아오면 onResume 이 다시 불리니, 여기서 현재 연결을 다시 확인하고 다음 동작을 정하면 됩니다.

@Override
protected void onResume() {
    super.onResume();
    ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    Network network = cm.getActiveNetwork();
    NetworkCapabilities caps = (network == null) ? null : cm.getNetworkCapabilities(network);
    boolean online = caps != null && caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
    if (online) {
        // 동기화 등 다음 작업 진행
    } else {
        // 아직 연결이 없다는 안내 유지
    }
}

getActiveNetwork() 는 API 23 이상에서 쓸 수 있고, 매니페스트에 ACCESS_NETWORK_STATE 권한이 있어야 합니다. 모바일 데이터인지 Wi-Fi인지 구분해야 한다면 caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) 로 확인하면 됩니다.

실무에서는 "켜 달라"고 매번 묻기보다, 연결이 없을 때 작업을 큐에 쌓아 두고 연결이 생기면 자동으로 보내는 구조가 사용자 불만이 덜합니다. 데이터를 끄게 하는 요구라면 아예 앱 안에서 "Wi-Fi에서만 동기화" 옵션을 두고 앱이 스스로 통신을 멈추게 하는 편이 현실적인 대안입니다.

권한과 설정 화면 예외 처리

연결 상태를 읽으려면 매니페스트에 아래 권한 한 줄이 있어야 합니다. 일반 권한이라 런타임에 사용자에게 따로 묻지 않아도 됩니다.

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

설정 인텐트는 표준 상수라도 일부 기기나 맞춤 롬에서 받아 줄 화면이 없을 수 있습니다. 그러면 startActivity 가 ActivityNotFoundException 을 던지고 앱이 죽으니, try-catch 로 감싸 두고 실패하면 더 일반적인 Settings.ACTION_SETTINGS 로 한 번 더 시도하는 식으로 대비해 두면 안전합니다.

연결이 생기면 알아서 보내는 구조

앞에서 말한 "연결될 때까지 쌓아 두었다가 보내기"는 직접 짤 필요 없이 AndroidX WorkManager에 맡길 수 있습니다. 네트워크 조건을 걸어 작업을 넣어 두면, 연결이 생기는 시점에 시스템이 실행해 줍니다.

Constraints constraints = new Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build();
OneTimeWorkRequest req = new OneTimeWorkRequest.Builder(SyncWorker.class)
        .setConstraints(constraints)
        .build();
WorkManager.getInstance(context).enqueue(req);

SyncWorker 는 Worker 를 상속해 실제 동기화를 하는 클래스입니다. "Wi-Fi에서만 동기화" 옵션을 켠 사용자라면 NetworkType.CONNECTED 대신 NetworkType.UNMETERED 를 주면 데이터 요금이 붙지 않는 네트워크에서만 돕니다. 이렇게 하면 앱이 모바일 데이터를 끄고 켤 수 없다는 제약이 실제로는 크게 문제 되지 않습니다.