이 과정에서는 네트워크 리소스 사용을 세밀하게 제어하는 애플리케이션 작성 방법을 설명합니다. 애플리케이션에서 많은 네트워크 작업을 실행한다면 앱의 데이터 사용 패턴(예: 앱의 데이터 동기화 빈도, Wi-Fi에 연결되었을 때만 업로드/다운로드 실행 여부, 로밍 중 데이터 사용 여부)을 사용자가 제어할 수 있도록 사용자 설정을 제공해야 합니다. 이러한 제어 기능을 제공하면 사용량 한도에 가까워질 때 사용자가 앱의 데이터 사용량을 정밀하게 제어할 수 있으므로 앱의 백그라운드 데이터 액세스를 사용 중지할 가능성이 크게 줄어듭니다.
일정 기간의 네트워크 연결 횟수 및 유형을 포함한 앱의 네트워크 사용에 관한 자세한 내용은 웹 앱 및 네트워크 프로파일러로 네트워크 트래픽 검사를 읽어보세요. 다운로드 및 네트워크 연결이 배터리 수명에 미치는 영향을 최소화하는 앱 작성 방법에 관한 일반 가이드라인은 배터리 수명 최적화 및 배터리 소모 없이 데이터 전송을 참고하세요.
NetworkConnect 샘플도 확인할 수 있습니다.
기기의 네트워크 연결 확인
기기에는 다양한 유형의 네트워크 연결이 있을 수 있습니다. 이 과정에서는 Wi-Fi 또는 모바일 네트워크 연결을 사용하는 것에 중점을 둡니다. 사용 가능한 네트워크 유형의 전체 목록은 ConnectivityManager
를 참고하세요.
일반적으로 Wi-Fi 속도가 더 빠릅니다. 또한, 모바일 데이터는 데이터 전송량 제한이 있는 경우가 많아서 비용이 많이 들 수 있습니다. 앱에서는 일반적으로 Wi-Fi 네트워크를 사용할 수 있을 때만 대용량 데이터를 가져옵니다.
네트워크 작업을 실행하기 전에 네트워크 연결 상태를 확인하는 것이 좋습니다. 무엇보다도 이렇게 하면 앱에서 실수로 잘못된 네트워크를 사용하는 것을 방지할 수 있습니다. 네트워크 연결을 사용할 수 없다면 애플리케이션이 적절하게 응답해야 합니다. 네트워크 연결을 확인하려면 일반적으로 다음 클래스를 사용합니다.
ConnectivityManager
: 네트워크 연결 상태에 관한 쿼리에 답합니다. 또한, 네트워크 연결이 변경될 때 애플리케이션에 알립니다.NetworkInfo
: 특정 유형(현재는 모바일 또는 Wi-Fi)의 네트워크 인터페이스 상태를 설명합니다.
이 코드 스니펫은 Wi-Fi 및 모바일 네트워크 연결을 테스트합니다. 이러한 네트워크 인터페이스가 사용 가능한지(즉, 네트워크 연결이 가능한지)와 연결되어 있는지(즉, 네트워크 연결 여부 및 소켓 설정과 데이터 전달 여부)를 확인합니다.
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
을 반환합니다.
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를 사용할 수 있는 경우에만 다운로드할지 설정
그림 1. 환경설정 활동
다음은 SettingsActivity
입니다. 이는 OnSharedPreferenceChangeListener
를 구현합니다.
사용자가 환경설정을 변경하면 onSharedPreferenceChanged()
를 실행하여 refreshDisplay
를 참으로 설정합니다. 따라서, 사용자가 기본 활동으로 되돌아가면 화면이 새로고침됩니다.
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 연결이 있는 경우) 앱에서 피드를 다운로드하고 화면을 새로고침합니다.
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
입니다.
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()
}
}
}
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();
}
}
}