Управление использованием сети

Этот урок описывает, как писать приложения, которые имеют точный контроль над их использованием сетевых ресурсов. Если приложение выполняет много сетевых операций, вы должны предоставить пользовательские настройки, которые позволят пользователям контролировать данные вашего приложения, например, как часто ваше приложение синхронизирует данные, следует ли выполнять загрузку только тогда, когда используется Wi-Fi, использовать ли данные в роуминге, и так далее. С помощью этих элементов управления, доступных для них, пользователи гораздо менее вероятно отключат доступ вашего приложения к фоновым данным, когда они достигают пределов, потому что они могут вместо этого точно контролировать сколько данных использует ваше приложение.

Общие руководящие принципы о том, как создавать приложения, которые минимизируют воздействия загрузок и сетевых операций на работу от батареи, см. Оптимизация работы батареи и Передача данных без разрядки аккумулятора.

Проверка сетевого подключения устройства

Устройство может иметь различные типы сетевых подключений. Это урок посвящен использованию либо Wi-Fi либо мобильного подключения к сети. Для получения полного списка возможных типов сетевых соединений см. ConnectivityManager.

Wi-Fi, как правило, быстрее. Также, мобильные данные часто дозируются, потому что могут дорого обойтись. Общей стратегией приложений является загрузка больших данных только когда доступна Wi-Fi сеть.

Перед выполнением сетевых операций, хорошая практика проверять состояние подключения к сети. Среди прочего, это может привести к невозможности приложение по неосторожности использовать неправильного типа сети. Если сетевое подключение недоступно, приложение должно реагировать корректно. Чтобы проверить подключение к сети, вы, как правило, можете использовать следующие классы:

  • ConnectivityManager: Отвечает на запросы о состоянии сетевого подключения. Он также уведомляет приложения при изменении сетевого подключения.
  • NetworkInfo: Описывает статус сетевого интерфейса заданного типа (в настоящее время либо Mobile либо Wi-Fi).

Этот фрагмент кода проверяет подключение к сети Wi-Fi и мобильному интернету. Он определяет, доступно ли (то есть, возможно ли подключение к сети) и/или установлено соединение (т.е. существует ли подключение к сети и возможно ли установить соединение через сокеты и передавать данные):

private static final String DEBUG_TAG = "NetworkStatusExample";
...      
ConnectivityManager connMgr = (ConnectivityManager) 
        getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 
boolean isWifiConn = networkInfo.isConnected();
networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
boolean isMobileConn = networkInfo.isConnected();
Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn);
Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);

Обратите внимание, что вы не должны строить предположения о том "доступна" ли сеть. Вы всегда должны проверить isConnected() перед выполнением сетевых операций, так как isConnected() обрабатывает такие случаи, как мобильные сети, режим "в самолёте" и ограничение на передачу фоновых данных.

Более краткий способ проверки доступен ли сетевой интерфейс состоит в следующем. Метод getActiveNetworkInfo() возвращаетNetworkInfo экземпляр, представляющий первый интерфейс сетевого подключения, которое он может найти, или nullесли ни один из интерфейсов не подключен (это означает, что подключение к интернету не доступно):

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 действия (введенном в Android 4.0), чтобы указать, что приложение определяет деятельность, которая предлагает опции для контроля использования данных. ACTION_MANAGE_NETWORK_USAGE показывает настройки для управления использования данных сети для конкретного приложения. Когда ваше приложение имеет деятельность с настройками, которые позволяют пользователям контролировать использование сети, вы должны объявить фильтр намерений для этой деятельности. В приложении примере, это действие обрабатывается классом SettingsActivity, который отображает пользовательский интерфейс настроек, позволяя пользователям решать, когда загружать данные.
<?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>

Реализация деятельности настроек

Как вы можете видеть во фрагменте манифеста выше, деятельность приложения SettingsActivity имеет фильтр намерений для ACTION_MANAGE_NETWORK_USAGE действия. SettingsActivity это подкласс PreferenceActivity. Он отображает экран настроек (показанный на рисунке 1), который позволяет пользователям указать следующее:

  • Надо ли отображать итоговую информацию для каждой записи XML канала, или просто ссылку для каждой записи.
  • Надо ли загружать XML канал, если имеется подключение к сети, или только тогда, когда доступен Wi-Fi.
Панель настроек Установка предпочтений сети

Рисунок 1. Деятельность настроек.

Вот SettingsActivity. Обратите внимание, что она реализует OnSharedPreferenceChangeListener. Когда пользователь меняет предпочтения, он запускает onSharedPreferenceChanged(), который устанавливает 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 подключение), приложение загружает канал и обновляет экран.

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 в true/false соответственно. В результате, когда в следующий раз пользователь возвращается в приложение, приложение будет загружать последний канал и обновит экран только, если NetworkActivity.refreshDisplay установлен в true.

Настройка BroadcastReceiver, который вызывается излишне может влиять на системные ресурсы. Пример приложения регистрирует BroadcastReceiver NetworkReceiver в onCreate(), и отменяет регистрацию в onDestroy(). Это более легкий вариант, чем объявление <receiver> в манифесте. При объявлении <receiver> в манифесте, он может заставить проснуться ваше приложение в любое время, даже если вы не запускали его в течение нескольких недель. При регистрации и удаления регистрации NetworkReceiver в основной деятельности, вы будете уверены, что приложение не будет разбужено после того как пользователь покинет приложение. Если вы объявите <receiver> в манифесте, и вы точно знаете, где это необходимо, вы можете использовать setComponentEnabledSetting() для включения и отключения его по мере необходимости.

Вот NetworkReceiver:

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();
    }
}