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 :
android.permission.INTERNET
: autorise les applications à ouvrir des sockets réseau.android.permission.ACCESS_NETWORK_STATE
: autorise les applications à accéder aux informations sur les réseaux.
- 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.
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(); } } }