이 과정에서는 네트워크 리소스 사용을 세밀하게 제어하는 애플리케이션 작성 방법을 설명합니다. 애플리케이션에서 많은 네트워크 작업을 실행한다면 앱의 데이터 사용 패턴(예: 앱의 데이터 동기화 빈도, 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 네트워크에 연결된 경우에만 사용자가 동영상을 업로드하도록 할 수 있습니다.
- 네트워크 가용성, 시간 간격 등 특정 기준에 따라 동기화하거나 동기화하지 않을 수 있습니다.
네트워크 액세스 및 네트워크 사용 관리를 지원하는 앱을 작성하려면 매니페스트에 올바른 권한과 인텐트 필터가 있어야 합니다.
- 이 섹션 뒷부분에 나오는 발췌된 매니페스트에는 다음 권한이 포함됩니다.
android.permission.INTERNET
: 애플리케이션에서 네트워크 소켓을 열 수 있습니다.android.permission.ACCESS_NETWORK_STATE
: 애플리케이션에서 네트워크에 관한 정보에 액세스할 수 있습니다.
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
작업용 인텐트 필터가 있습니다. SettingsActivity
는 PreferenceActivity
의 서브클래스입니다. 이 클래스는 사용자가 다음 사항을 지정할 수 있는 환경설정 화면(그림 1 참고)을 표시합니다.
- 각 XML 피드 항목에 관한 요약을 표시할지 또는 각 항목의 링크만 표시할지 설정
- 네트워크 연결이 가능한 경우 XML 피드를 다운로드할지 또는 Wi-Fi를 사용할 수 있는 경우에만 다운로드할지 설정
다음은 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
작업을 가로채고 네트워크 연결 상태가 무엇인지 확인한 다음, wifiConnected
와 mobileConnected
플래그를 참/거짓으로 적절하게 설정합니다. 그러면 다음에 사용자가 앱으로 돌아올 때 앱이 최신 피드만 다운로드하고 NetworkActivity.refreshDisplay
가 true
로 설정된 경우 화면을 업데이트합니다.
불필요하게 호출되는 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(); } } }