Gérer l'utilisation du réseau

Cette leçon explique comment écrire des applications qui contrôlent précisément l'utilisation des ressources réseau. Si votre application effectue de nombreuses opérations réseau, vous devez fournir des paramètres utilisateur permettant aux utilisateurs de contrôler leurs habitudes concernant les données, comme la fréquence de synchronisation des données, s'il faut effectuer des importations ou des téléchargements uniquement lorsque vous êtes connecté à un réseau Wi-Fi, s'il faut utiliser les données en itinérance, etc. Grâce à ces contrôles, les utilisateurs sont bien moins susceptibles de désactiver l'accès de votre application aux données en arrière-plan lorsqu'elles atteignent leurs limites, car ils peuvent contrôler avec précision la quantité de données utilisée par votre application.

Pour en savoir plus sur l'utilisation du réseau de votre application, y compris le nombre et les types de connexions réseau sur une période donnée, consultez Applications Web et Inspecter le trafic réseau avec le Profileur de réseau. Pour obtenir des consignes générales sur la rédaction d'applications qui réduisent l'impact des téléchargements et des connexions réseau sur l'autonomie de la batterie, consultez les sections Optimiser l'autonomie de la batterie et Transférer des données sans vider la batterie.

Vous pouvez également consulter l'exemple NetworkConnect.

Vérifier la connexion réseau d'un appareil

Un appareil peut avoir différents types de connexions réseau. Cette leçon porte sur l'utilisation d'une connexion Wi-Fi ou d'une connexion à un réseau mobile. Pour obtenir la liste complète des types de réseaux possibles, consultez la section ConnectivityManager.

Le Wi-Fi est généralement plus rapide. De plus, les données mobiles sont souvent mesurées, ce qui peut s'avérer coûteux. Une stratégie courante pour les applications consiste à récupérer les données volumineuses uniquement si un réseau Wi-Fi est disponible.

Avant d'effectuer des opérations réseau, il est recommandé de vérifier l'état de la connectivité réseau. Cela peut, entre autres, empêcher votre application d'utiliser par erreur le mauvais signal radio. Si une connexion réseau n'est pas disponible, votre application devrait répondre normalement. Pour vérifier la connexion réseau, vous utilisez généralement les classes suivantes :

  • ConnectivityManager : répond aux requêtes sur l'état de la connectivité réseau. Il informe également les applications lorsque la connectivité réseau change.
  • NetworkInfo : décrit l'état d'une interface réseau d'un type donné (actuellement, mobile ou Wi-Fi).

Cet extrait de code teste la connectivité réseau pour les réseaux Wi-Fi et mobiles. Il détermine si ces interfaces réseau sont disponibles (c'est-à-dire si la connectivité réseau est possible) et/ou connectées (c'est-à-dire si la connectivité réseau existe et s'il est possible d'établir des sockets et de transmettre des données) :

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")

Java

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

Notez que vous ne devez pas prendre vos décisions en supposant la disponibilité d'un réseau. Vous devez toujours vérifier isConnected() avant d'effectuer des opérations réseau, car isConnected() gère des cas tels que les réseaux mobiles irréguliers, le mode Avion et les restrictions de données en arrière-plan.

Vous trouverez ci-après une méthode plus rapide pour vérifier si une interface réseau est disponible. La méthode getActiveNetworkInfo() affiche une instance NetworkInfo représentant la première interface réseau connectée qu'elle peut trouver, ou null si aucune des interfaces n'est connectée (ce qui signifie qu'aucune connexion Internet n'est disponible) :

Kotlin

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

Java

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

Pour interroger des données plus détaillées, vous pouvez utiliser NetworkInfo.DetailedState, mais cette opération est rarement nécessaire.

Gérer l'utilisation du réseau

Vous pouvez mettre en œuvre une activité de préférences permettant aux utilisateurs de contrôler explicitement l'utilisation des ressources réseau par votre application. Par exemple :

  • Vous pouvez autoriser les utilisateurs à mettre en ligne des vidéos uniquement lorsque l'appareil est connecté à un réseau Wi-Fi.
  • Vous pouvez effectuer la synchronisation (ou non) en fonction de critères spécifiques tels que la disponibilité du réseau, l'intervalle de temps, etc.

Pour coder une application compatible avec l'accès au réseau et gérer l'utilisation du réseau, votre fichier manifeste doit disposer des autorisations et des filtres d'intent appropriés.

  • Le fichier manifeste présenté plus loin dans cette section inclut les autorisations suivantes :
  • Vous pouvez déclarer le filtre d'intent pour l'action ACTION_MANAGE_NETWORK_USAGE afin d'indiquer que votre application définit une activité qui propose des options pour contrôler la consommation des données. ACTION_MANAGE_NETWORK_USAGE affiche les paramètres permettant de gérer la consommation des données réseau pour une application spécifique. Lorsque votre application dispose d'une activité de paramètres permettant aux utilisateurs de contrôler l'utilisation du réseau, vous devez déclarer ce filtre d'intent pour cette activité.

Dans l'exemple d'application, cette action est gérée par la classe SettingsActivity, qui affiche un écran d'interface utilisateur de préférences pour permettre aux utilisateurs de décider quand télécharger un flux.

<?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>

Les applications qui traitent des données utilisateur sensibles et qui ciblent Android 11 ou version ultérieure peuvent accorder un accès réseau pour chaque processus. En spécifiant explicitement les processus autorisés à accéder au réseau, vous isolez tout le code qui n'a pas besoin d'importer des données.

Bien que cela ne garantisse pas d'empêcher votre application d'importer accidentellement des données, cela permet de réduire le risque de fuites de données dans votre application.

Voici un exemple de fichier manifeste qui utilise la fonctionnalité par processus :

<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>

Implémenter une activité relative aux préférences

Comme vous pouvez le voir dans l'extrait du fichier manifeste plus haut sur cette page, l'activité SettingsActivity de l'application exemple comporte un filtre d'intent pour l'action ACTION_MANAGE_NETWORK_USAGE. SettingsActivity est une sous-classe de PreferenceActivity. Elle affiche un écran de préférences (illustré à l'image 1) qui permet aux utilisateurs de spécifier les éléments suivants :

  • Permet d'afficher un récapitulatif pour chaque entrée de flux XML ou simplement un lien pour chaque entrée.
  • Indique s'il faut télécharger le flux XML si une connexion réseau est disponible ou si le Wi-Fi est disponible.

Panneau des préférences Définir une préférence réseau

Image 1 : Activité relative aux préférences.

Voici SettingsActivity. Notez qu'elle implémente OnSharedPreferenceChangeListener. Lorsqu'un utilisateur modifie une préférence, il déclenche onSharedPreferenceChanged(), ce qui définit refreshDisplay sur "true". L'écran est alors actualisé lorsque l'utilisateur revient à l'activité principale :

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
    }
}

Java

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

Répondre aux changements de préférences

Lorsque l'utilisateur modifie les préférences dans l'écran des paramètres, cela a généralement des conséquences sur le comportement de l'application. Dans cet extrait, l'application vérifie les paramètres des préférences dans onStart(). S'il existe une correspondance entre le paramètre et la connexion réseau de l'appareil (par exemple, si le paramètre est "Wi-Fi" et que l'appareil est connecté au Wi-Fi), l'application télécharge le flux et actualise l'écran.

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
    }
...

}

Java

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

}

Détecter les modifications de connexion

La dernière partie du puzzle est la sous-classe BroadcastReceiver, NetworkReceiver. Lorsque la connexion réseau de l'appareil change, NetworkReceiver intercepte l'action CONNECTIVITY_ACTION, détermine l'état de la connexion réseau et définit les indicateurs wifiConnected et mobileConnected sur "true"/"false" en conséquence. L'avantage est que la prochaine fois que l'utilisateur revient dans l'application, celle-ci ne télécharge le dernier flux et ne met à jour l'affichage que si NetworkActivity.refreshDisplay est défini sur true.

Configurer un BroadcastReceiver qui est appelé inutilement peut entraîner une surconsommation des ressources système. L'application exemple enregistre le NetworkReceiver BroadcastReceiver dans onCreate() et l'annule dans onDestroy(). Cette méthode est plus légère que la déclaration d'un <receiver> dans le fichier manifeste. Lorsque vous déclarez un <receiver> dans le fichier manifeste, il peut réactiver votre application à tout moment, même si vous ne l'exécutez pas pendant des semaines. En enregistrant et en désinscrivant NetworkReceiver dans l'activité principale, vous vous assurez que l'application ne sera pas activée après que l'utilisateur l'a quittée. Si vous déclarez un <receiver> dans le fichier manifeste et que vous savez exactement où vous en avez besoin, vous pouvez utiliser setComponentEnabledSetting() pour l'activer et le désactiver selon les besoins.

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