네트워크 사용 관리

이 과정에서는 네트워크 리소스 사용을 세밀하게 제어하는 애플리케이션 작성 방법을 설명합니다. 애플리케이션에서 많은 네트워크 작업을 실행한다면 앱의 데이터 사용 패턴(예: 앱의 데이터 동기화 빈도, Wi-Fi에 연결되었을 때만 업로드/다운로드 실행 여부, 로밍 중 데이터 사용 여부)을 사용자가 제어할 수 있도록 사용자 설정을 제공해야 합니다. 이러한 제어 기능을 제공하면 사용량 한도에 가까워질 때 사용자가 앱의 데이터 사용량을 정밀하게 제어할 수 있으므로 앱의 백그라운드 데이터 액세스를 사용 중지할 가능성이 크게 줄어듭니다.

일정 기간의 네트워크 연결 횟수 및 유형을 포함한 앱의 네트워크 사용에 관한 자세한 내용은 웹 앱네트워크 프로파일러로 네트워크 트래픽 검사를 읽어보세요. 다운로드 및 네트워크 연결이 배터리 수명에 미치는 영향을 최소화하는 앱 작성 방법에 관한 일반 가이드라인은 배터리 수명 최적화배터리 소모 없이 데이터 전송을 참고하세요.

NetworkConnect 샘플도 확인할 수 있습니다.

기기의 네트워크 연결 확인

기기에는 다양한 유형의 네트워크 연결이 있을 수 있습니다. 이 과정에서는 Wi-Fi 또는 모바일 네트워크 연결을 사용하는 것에 중점을 둡니다. 사용 가능한 네트워크 유형의 전체 목록은 ConnectivityManager를 참고하세요.

일반적으로 Wi-Fi 속도가 더 빠릅니다. 또한, 모바일 데이터는 데이터 전송량 제한이 있는 경우가 많아서 비용이 많이 들 수 있습니다. 앱에서는 일반적으로 Wi-Fi 네트워크를 사용할 수 있을 때만 대용량 데이터를 가져옵니다.

네트워크 작업을 실행하기 전에 네트워크 연결 상태를 확인하는 것이 좋습니다. 무엇보다도 이렇게 하면 앱에서 실수로 잘못된 네트워크를 사용하는 것을 방지할 수 있습니다. 네트워크 연결을 사용할 수 없다면 애플리케이션이 적절하게 응답해야 합니다. 네트워크 연결을 확인하려면 일반적으로 다음 클래스를 사용합니다.

  • ConnectivityManager: 네트워크 연결 상태에 관한 쿼리에 답합니다. 또한, 네트워크 연결이 변경될 때 애플리케이션에 알립니다.
  • NetworkInfo: 특정 유형(현재는 모바일 또는 Wi-Fi)의 네트워크 인터페이스 상태를 설명합니다.

이 코드 스니펫은 Wi-Fi 및 모바일 네트워크 연결을 테스트합니다. 이러한 네트워크 인터페이스가 사용 가능한지(즉, 네트워크 연결이 가능한지)와 연결되어 있는지(즉, 네트워크 연결 여부 및 소켓 설정과 데이터 전달 여부)를 확인합니다.

Kotlin

private const val DEBUG_TAG = "NetworkStatusExample"
...
val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
var isWifiConn: Boolean = false
var isMobileConn: Boolean = false
connMgr.allNetworks.forEach { network ->
    connMgr.getNetworkInfo(network).apply {
        if (type == ConnectivityManager.TYPE_WIFI) {
            isWifiConn = isWifiConn or isConnected
        }
        if (type == ConnectivityManager.TYPE_MOBILE) {
            isMobileConn = isMobileConn or isConnected
        }
    }
}
Log.d(DEBUG_TAG, "Wifi connected: $isWifiConn")
Log.d(DEBUG_TAG, "Mobile connected: $isMobileConn")

자바

private static final String DEBUG_TAG = "NetworkStatusExample";
...
ConnectivityManager connMgr =
        (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
boolean isWifiConn = false;
boolean isMobileConn = false;
for (Network network : connMgr.getAllNetworks()) {
    NetworkInfo networkInfo = connMgr.getNetworkInfo(network);
    if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        isWifiConn |= networkInfo.isConnected();
    }
    if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
        isMobileConn |= networkInfo.isConnected();
    }
}
Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn);
Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);

네트워크의 '사용 가능 여부'에 따라 결정을 내려서는 안 됩니다. 네트워크 작업을 실행하기 전에는 항상 isConnected()를 확인해야 합니다. isConnected()에서 비정상적인 모바일 네트워크, 비행기 모드, 제한된 백그라운드 데이터와 같은 사례를 처리하기 때문입니다.

네트워크 인터페이스가 사용 가능한지 확인하는 더 간단한 방법은 다음과 같습니다. getActiveNetworkInfo() 메서드는 처음 연결된 네트워크 인터페이스를 찾아서 이를 나타내는 NetworkInfo 인스턴스를 반환하거나 연결된 인터페이스가 없는 경우(즉, 인터넷 연결 사용 불가) null을 반환합니다.

Kotlin

fun isOnline(): Boolean {
    val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val networkInfo: NetworkInfo? = connMgr.activeNetworkInfo
    return networkInfo?.isConnected == true
}

자바

public boolean isOnline() {
    ConnectivityManager connMgr = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    return (networkInfo != null && networkInfo.isConnected());
}

더 자세한 상태를 쿼리하려면 NetworkInfo.DetailedState를 사용할 수 있으나 이런 경우는 흔하지 않습니다.

네트워크 사용 관리

사용자가 앱의 네트워크 리소스 사용량을 명시적으로 제어하도록 하는 환경설정 활동을 구현할 수 있습니다. 예:

  • 기기가 Wi-Fi 네트워크에 연결된 경우에만 사용자가 동영상을 업로드하도록 할 수 있습니다.
  • 네트워크 가용성, 시간 간격 등 특정 기준에 따라 동기화하거나 동기화하지 않을 수 있습니다.

네트워크 액세스 및 네트워크 사용 관리를 지원하는 앱을 작성하려면 매니페스트에 올바른 권한과 인텐트 필터가 있어야 합니다.

  • 이 섹션 뒷부분에 나오는 발췌된 매니페스트에는 다음 권한이 포함됩니다.
  • ACTION_MANAGE_NETWORK_USAGE 작업을 위한 인텐트 필터를 선언하여 데이터 사용량 제어 옵션을 제공하는 활동을 애플리케이션에서 정의한다는 것을 나타낼 수 있습니다. ACTION_MANAGE_NETWORK_USAGE는 특정 애플리케이션의 네트워크 데이터 사용량 관리용 설정을 보여줍니다. 앱에 사용자가 네트워크 사용량을 제어할 수 있는 설정 활동이 있다면 그 활동에 대해 이 인텐트 필터를 선언해야 합니다.

샘플 애플리케이션에서 이 작업은 SettingsActivity 클래스가 처리하며, 이 클래스는 사용자가 피드를 다운로드하는 시점을 선택할 수 있는 환경설정 UI를 표시합니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.networkusage"
    ...>

    <uses-sdk android:minSdkVersion="4"
           android:targetSdkVersion="14" />

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

    <application
        ...>
        ...
        <activity android:label="SettingsActivity" android:name=".SettingsActivity">
             <intent-filter>
                <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
                <category android:name="android.intent.category.DEFAULT" />
          </intent-filter>
        </activity>
    </application>
</manifest>

민감한 사용자 데이터를 처리하는 앱 및 Android 11 이상을 타겟팅하는 앱은 프로세스별로 네트워크 액세스를 부여할 수 있습니다. 네트워크 액세스가 허용되는 프로세스를 명시적으로 지정하면 데이터를 업로드할 필요가 없는 모든 코드를 분리할 수 있습니다.

이렇게 하면 앱이 실수로 데이터 업로드하는 것을 막는다는 보장은 없지만, 앱의 버그로 인한 데이터 유출 가능성을 줄일 수 있습니다.

다음은 프로세스별 기능을 사용하는 매니페스트 파일의 샘플입니다.

<processes>
    <process />
    <deny-permission android:name="android.permission.INTERNET" />
    <process android:process=":withoutnet1" />
    <process android:process="com.android.cts.useprocess.withnet1">
        <allow-permission android:name="android.permission.INTERNET" />
    </process>
    <allow-permission android:name="android.permission.INTERNET" />
    <process android:process=":withoutnet2">
        <deny-permission android:name="android.permission.INTERNET" />
    </process>
    <process android:process="com.android.cts.useprocess.withnet2" />
</processes>

환경설정 활동 구현

이 주제의 앞부분에 발췌된 매니페스트에서 확인할 수 있듯이 샘플 앱의 SettingsActivity 활동에는 ACTION_MANAGE_NETWORK_USAGE 작업용 인텐트 필터가 있습니다. SettingsActivityPreferenceActivity의 서브클래스입니다. 이 클래스는 사용자가 다음 사항을 지정할 수 있는 환경설정 화면(그림 1 참고)을 표시합니다.

  • 각 XML 피드 항목에 관한 요약을 표시할지 또는 각 항목의 링크만 표시할지 설정
  • 네트워크 연결이 가능한 경우 XML 피드를 다운로드할지 또는 Wi-Fi를 사용할 수 있는 경우에만 다운로드할지 설정

환경설정 패널 네트워크 환경설정

그림 1. 환경설정 활동

다음은 SettingsActivity입니다. 이는 OnSharedPreferenceChangeListener를 구현합니다. 사용자가 환경설정을 변경하면 onSharedPreferenceChanged()를 실행하여 refreshDisplay를 참으로 설정합니다. 따라서, 사용자가 기본 활동으로 되돌아가면 화면이 새로고침됩니다.

Kotlin

class SettingsActivity : PreferenceActivity(), OnSharedPreferenceChangeListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Loads the XML preferences file
        addPreferencesFromResource(R.xml.preferences)
    }

    override fun onResume() {
        super.onResume()

        // Registers a listener whenever a key changes
        preferenceScreen?.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
    }

    override fun onPause() {
        super.onPause()

        // Unregisters the listener set in onResume().
        // It's best practice to unregister listeners when your app isn't using them to cut down on
        // unnecessary system overhead. You do this in onPause().
        preferenceScreen?.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
    }

    // When the user changes the preferences selection,
    // onSharedPreferenceChanged() restarts the main activity as a new
    // task. Sets the refreshDisplay flag to "true" to indicate that
    // the main activity should update its display.
    // The main activity queries the PreferenceManager to get the latest settings.

    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
        // Sets refreshDisplay to true so that when the user returns to the main
        // activity, the display refreshes to reflect the new settings.
        NetworkActivity.refreshDisplay = true
    }
}

자바

public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Loads the XML preferences file
        addPreferencesFromResource(R.xml.preferences);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Registers a listener whenever a key changes
        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    protected void onPause() {
        super.onPause();

       // Unregisters the listener set in onResume().
       // It's best practice to unregister listeners when your app isn't using them to cut down on
       // unnecessary system overhead. You do this in onPause().
       getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
    }

    // When the user changes the preferences selection,
    // onSharedPreferenceChanged() restarts the main activity as a new
    // task. Sets the refreshDisplay flag to "true" to indicate that
    // the main activity should update its display.
    // The main activity queries the PreferenceManager to get the latest settings.

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        // Sets refreshDisplay to true so that when the user returns to the main
        // activity, the display refreshes to reflect the new settings.
        NetworkActivity.refreshDisplay = true;
    }
}

환경설정 변경에 응답

사용자가 설정 화면에서 환경설정을 변경하면 일반적으로 앱의 동작에 영향을 미칩니다. 이 스니펫에서 앱은 onStart()의 환경설정을 확인합니다. 설정과 기기의 네트워크 연결이 일치하는 경우(예: 설정이 "Wi-Fi"이고 기기에 Wi-Fi 연결이 있는 경우) 앱에서 피드를 다운로드하고 화면을 새로고침합니다.

Kotlin

class NetworkActivity : Activity() {

    // The BroadcastReceiver that tracks network connectivity changes.
    private lateinit var receiver: NetworkReceiver

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Registers BroadcastReceiver to track network connection changes.
        val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
        receiver = NetworkReceiver()
        this.registerReceiver(receiver, filter)
    }

    public override fun onDestroy() {
        super.onDestroy()
        // Unregisters BroadcastReceiver when app is destroyed.
        this.unregisterReceiver(receiver)
    }

    // Refreshes the display if the network connection and the
    // pref settings allow it.

    public override fun onStart() {
        super.onStart()

        // Gets the user's network preference settings
        val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)

        // Retrieves a string value for the preferences. The second parameter
        // is the default value to use if a preference value is not found.
        sPref = sharedPrefs.getString("listPref", "Wi-Fi")

        updateConnectedFlags()

        if (refreshDisplay) {
            loadPage()
        }
    }

    // Checks the network connection and sets the wifiConnected and mobileConnected
    // variables accordingly.
    fun updateConnectedFlags() {
        val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

        val activeInfo: NetworkInfo? = connMgr.activeNetworkInfo
        if (activeInfo?.isConnected == true) {
            wifiConnected = activeInfo.type == ConnectivityManager.TYPE_WIFI
            mobileConnected = activeInfo.type == ConnectivityManager.TYPE_MOBILE
        } else {
            wifiConnected = false
            mobileConnected = false
        }
    }

    // Uses AsyncTask subclass to download the XML feed from stackoverflow.com.
    fun loadPage() {
        if (sPref == ANY && (wifiConnected || mobileConnected) || sPref == WIFI && wifiConnected) {
            // AsyncTask subclass
            DownloadXmlTask().execute(URL)
        } else {
            showErrorPage()
        }
    }

    companion object {

        const val WIFI = "Wi-Fi"
        const val ANY = "Any"
        const val SO_URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort;=newest"

        // Whether there is a Wi-Fi connection.
        private var wifiConnected = false
        // Whether there is a mobile connection.
        private var mobileConnected = false
        // Whether the display should be refreshed.
        var refreshDisplay = true

        // The user's current network preference setting.
        var sPref: String? = null
    }
...

}

자바

public class NetworkActivity extends Activity {
    public static final String WIFI = "Wi-Fi";
    public static final String ANY = "Any";
    private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort;=newest";

    // Whether there is a Wi-Fi connection.
    private static boolean wifiConnected = false;
    // Whether there is a mobile connection.
    private static boolean mobileConnected = false;
    // Whether the display should be refreshed.
    public static boolean refreshDisplay = true;

    // The user's current network preference setting.
    public static String sPref = null;

    // The BroadcastReceiver that tracks network connectivity changes.
    private NetworkReceiver receiver = new NetworkReceiver();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Registers BroadcastReceiver to track network connection changes.
        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        receiver = new NetworkReceiver();
        this.registerReceiver(receiver, filter);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // Unregisters BroadcastReceiver when app is destroyed.
        if (receiver != null) {
            this.unregisterReceiver(receiver);
        }
    }

    // Refreshes the display if the network connection and the
    // pref settings allow it.

    @Override
    public void onStart () {
        super.onStart();

        // Gets the user's network preference settings
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);

        // Retrieves a string value for the preferences. The second parameter
        // is the default value to use if a preference value is not found.
        sPref = sharedPrefs.getString("listPref", "Wi-Fi");

        updateConnectedFlags();

        if(refreshDisplay){
            loadPage();
        }
    }

    // Checks the network connection and sets the wifiConnected and mobileConnected
    // variables accordingly.
    public void updateConnectedFlags() {
        ConnectivityManager connMgr = (ConnectivityManager)
                getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
        if (activeInfo != null && activeInfo.isConnected()) {
            wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI;
            mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE;
        } else {
            wifiConnected = false;
            mobileConnected = false;
        }
    }

    // Uses AsyncTask subclass to download the XML feed from stackoverflow.com.
    public void loadPage() {
        if (((sPref.equals(ANY)) && (wifiConnected || mobileConnected))
                || ((sPref.equals(WIFI)) && (wifiConnected))) {
            // AsyncTask subclass
            new DownloadXmlTask().execute(URL);
        } else {
            showErrorPage();
        }
    }
...

}

연결 변경 감지

마지막으로 필요한 부분은 BroadcastReceiver의 서브클래스인 NetworkReceiver입니다. 기기의 네트워크 연결이 변경되면 NetworkReceiver에서 CONNECTIVITY_ACTION 작업을 가로채고 네트워크 연결 상태가 무엇인지 확인한 다음, wifiConnectedmobileConnected 플래그를 참/거짓으로 적절하게 설정합니다. 그러면 다음에 사용자가 앱으로 돌아올 때 앱이 최신 피드만 다운로드하고 NetworkActivity.refreshDisplaytrue로 설정된 경우 화면을 업데이트합니다.

불필요하게 호출되는 BroadcastReceiver를 설정하면 시스템 리소스가 소모될 수 있습니다. 샘플 애플리케이션은 onCreate()에서 BroadcastReceiver NetworkReceiver를 등록하고 onDestroy()에서 등록을 취소합니다. 이 방법은 매니페스트에서 <receiver>를 선언하는 것보다 가볍습니다. 매니페스트에서 <receiver>를 선언하면 몇 주간 앱을 실행하지 않았어도 언제든지 대기 상태를 해제할 수 있습니다. 기본 활동 내에서 NetworkReceiver를 등록하고 등록 취소하면 사용자가 앱을 닫은 후 앱이 대기 상태에서 전환되지 않습니다. 매니페스트에서 <receiver>를 선언하고 필요한 위치를 정확하게 알고 있는 경우 setComponentEnabledSetting()을 사용하여 적절하게 사용 설정하거나 사용 중지할 수 있습니다.

다음은 NetworkReceiver입니다.

Kotlin

class NetworkReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        val conn = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val networkInfo: NetworkInfo? = conn.activeNetworkInfo

        // Checks the user prefs and the network connection. Based on the result, decides whether
        // to refresh the display or keep the current display.
        // If the userpref is Wi-Fi only, checks to see if the device has a Wi-Fi connection.
        if (WIFI == sPref && networkInfo?.type == ConnectivityManager.TYPE_WIFI) {
            // If device has its Wi-Fi connection, sets refreshDisplay
            // to true. This causes the display to be refreshed when the user
            // returns to the app.
            refreshDisplay = true
            Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show()

            // If the setting is ANY network and there is a network connection
            // (which by process of elimination would be mobile), sets refreshDisplay to true.
        } else if (ANY == sPref && networkInfo != null) {
            refreshDisplay = true

            // Otherwise, the app can't download content--either because there is no network
            // connection (mobile or Wi-Fi), or because the pref setting is WIFI, and there
            // is no Wi-Fi connection.
            // Sets refreshDisplay to false.
        } else {
            refreshDisplay = false
            Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show()
        }
    }
}

Java

public class NetworkReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        ConnectivityManager conn =  (ConnectivityManager)
            context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = conn.getActiveNetworkInfo();

        // Checks the user prefs and the network connection. Based on the result, decides whether
        // to refresh the display or keep the current display.
        // If the userpref is Wi-Fi only, checks to see if the device has a Wi-Fi connection.
        if (WIFI.equals(sPref) && networkInfo != null
            && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
            // If device has its Wi-Fi connection, sets refreshDisplay
            // to true. This causes the display to be refreshed when the user
            // returns to the app.
            refreshDisplay = true;
            Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show();

        // If the setting is ANY network and there is a network connection
        // (which by process of elimination would be mobile), sets refreshDisplay to true.
        } else if (ANY.equals(sPref) && networkInfo != null) {
            refreshDisplay = true;

        // Otherwise, the app can't download content--either because there is no network
        // connection (mobile or Wi-Fi), or because the pref setting is WIFI, and there
        // is no Wi-Fi connection.
        // Sets refreshDisplay to false.
        } else {
            refreshDisplay = false;
            Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show();
        }
    }
}