Gestisci l'utilizzo della rete

Questa lezione descrive come scrivere applicazioni con un controllo granulare sull'utilizzo delle risorse di rete. Se la tua applicazione esegue molte operazioni di rete, devi fornire impostazioni utente che consentano agli utenti di controllare le abitudini di utilizzo dei dati dell'app, ad esempio la frequenza di sincronizzazione dei dati nell'app, se eseguire caricamenti/download solo quando la connessione Wi-Fi è attiva, se utilizzare i dati in roaming e così via. Con questi controlli a loro disposizione, è molto meno probabile che gli utenti disattivino l'accesso della tua app ai dati in background quando si avvicinano ai limiti perché possono controllare con precisione la quantità di dati utilizzata dall'app.

Per scoprire di più sull'utilizzo della rete della tua app, inclusi il numero e i tipi di connessioni di rete durante un periodo di tempo, leggi App web e Esaminare il traffico di rete con il profiler di rete. Per le linee guida generali su come scrivere app che riducono al minimo l'impatto sui download e sulle connessioni di rete sulla durata della batteria, vedi Ottimizzare la durata della batteria e Trasferire dati senza scaricare la batteria.

Puoi anche consultare l'esempio di NetworkConnect.

Controllare la connessione di rete di un dispositivo

Un dispositivo può avere vari tipi di connessioni di rete. Questa lezione è incentrata sull'utilizzo di una connessione di rete mobile o Wi-Fi. Per l'elenco completo dei possibili tipi di rete, consulta ConnectivityManager.

Il Wi-Fi è in genere più veloce. Inoltre, i dati mobili sono spesso a consumo, il che può diventare costoso. Una strategia comune per le app consiste nel recuperare dati di grandi dimensioni solo se è disponibile una rete Wi-Fi.

Prima di eseguire operazioni di rete, è buona norma controllare lo stato della connettività di rete. Tra le altre cose, questo potrebbe impedire all'app di usare inavvertitamente la radio sbagliata. Se non è disponibile una connessione di rete, l'applicazione dovrebbe rispondere correttamente. Per verificare la connessione di rete, in genere utilizzi le seguenti classi:

  • ConnectivityManager: risponde a query sullo stato della connettività di rete. Inoltre, avvisa le applicazioni quando la connettività di rete cambia.
  • NetworkInfo: descrive lo stato di un'interfaccia di rete di un determinato tipo (attualmente mobile o Wi-Fi).

Questo snippet di codice verifica la connettività di rete per Wi-Fi e dispositivi mobili. Determina se queste interfacce di rete sono disponibili (ovvero se è possibile la connettività di rete) e/o se sono connesse (ossia se esiste una connettività di rete e se è possibile stabilire socket e trasmettere dati):

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

Tieni presente che non devi basare le decisioni sulla disponibilità di una rete. Devi sempre selezionare isConnected() prima di eseguire le operazioni di rete, poiché isConnected() gestisce casi come reti mobili instabili, modalità aereo e dati in background limitati.

Un modo più conciso per verificare se è disponibile un'interfaccia di rete è il seguente. Il metodo getActiveNetworkInfo() restituisce un'istanza NetworkInfo che rappresenta la prima interfaccia di rete connessa che riesce a trovare oppure null se nessuna delle interfacce è connessa (ovvero che non è disponibile una connessione a internet):

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

Per eseguire query su uno stato più granulare, puoi utilizzare NetworkInfo.DetailedState, ma raramente ciò dovrebbe essere necessario.

Gestire l'utilizzo della rete

Puoi implementare un'attività basata sulle preferenze che offra agli utenti un controllo esplicito sull'utilizzo delle risorse di rete da parte della tua app. Ecco alcuni esempi:

  • Puoi consentire agli utenti di caricare video solo quando il dispositivo è connesso a una rete Wi-Fi.
  • Puoi eseguire o meno la sincronizzazione a seconda di criteri specifici come la disponibilità della rete, l'intervallo di tempo e così via.

Per scrivere un'app che supporti l'accesso alla rete e la gestione dell'utilizzo della rete, il file manifest deve disporre delle autorizzazioni e dei filtri per intent corretti.

  • Il manifest estratto più avanti in questa sezione include le seguenti autorizzazioni:
  • Puoi dichiarare il filtro per intent per l'azione ACTION_MANAGE_NETWORK_USAGE per indicare che l'applicazione definisce un'attività che offre opzioni per controllare l'utilizzo dei dati. ACTION_MANAGE_NETWORK_USAGE mostra le impostazioni per la gestione dell'utilizzo dei dati di rete di un'applicazione specifica. Quando la tua app ha un'attività relativa alle impostazioni che consente agli utenti di controllare l'utilizzo della rete, devi dichiarare questo filtro per intent per l'attività in questione.

Nell'applicazione di esempio, questa azione è gestita dalla classe SettingsActivity, che mostra una UI delle preferenze per consentire agli utenti di decidere quando scaricare un feed.

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

Le app che gestiscono dati utente sensibili e che hanno come target Android 11 e versioni successive possono concedere l'accesso alla rete per singolo processo. Specificando esplicitamente quali processi sono autorizzati ad accedere alla rete, isoli tutto il codice che non ha bisogno di caricare dati.

Anche se non è garantito che la tua app impedisca il caricamento accidentale dei dati, offre un modo per ridurre le probabilità che l'app causi una fuga di dati.

Di seguito è riportato un esempio di file manifest che utilizza la funzionalità per processo:

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

Implementare un'attività relativa alle preferenze

Come puoi vedere nell'estratto del file manifest in precedenza in questo argomento, l'attività SettingsActivity dell'app di esempio ha un filtro per intent per l'azione ACTION_MANAGE_NETWORK_USAGE. SettingsActivity è una sottoclasse di PreferenceActivity. Visualizza una schermata delle preferenze (mostrata nella figura 1) che consente agli utenti di specificare quanto segue:

  • Indica se visualizzare riepiloghi per ogni voce del feed XML o solo un link per ogni voce.
  • Se scaricare il feed XML, se è disponibile una connessione di rete o solo se è disponibile una rete Wi-Fi.

Riquadro delle preferenze Impostazione di una preferenza di rete

Figura 1. Preferenze.

Ecco SettingsActivity. Tieni presente che implementa OnSharedPreferenceChangeListener. Quando un utente modifica una preferenza, viene attivato onSharedPreferenceChanged(), impostando refreshDisplay su true. In questo modo la visualizzazione si aggiorna quando l'utente torna all'attività 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;
    }
}

Rispondere ai cambiamenti delle preferenze

Quando l'utente modifica le preferenze nella schermata delle impostazioni, in genere ciò ha conseguenze sul comportamento dell'app. In questo snippet, l'app controlla le impostazioni delle preferenze in onStart(). se esiste una corrispondenza tra l'impostazione e la connessione di rete del dispositivo (ad esempio, se l'impostazione è "Wi-Fi" e il dispositivo ha una connessione Wi-Fi), l'app scarica il feed e aggiorna la visualizzazione.

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

}

Rileva modifiche alla connessione

L'ultimo pezzo del puzzle è la sottoclasse BroadcastReceiver, NetworkReceiver. Quando la connessione di rete del dispositivo cambia, NetworkReceiver intercetta l'azione CONNECTIVITY_ACTION, determina lo stato della connessione di rete e imposta i flag wifiConnected e mobileConnected su true/false di conseguenza. Il risultato è che, la volta successiva che l'utente torna all'app, l'app scarica solo il feed più recente e aggiorna la visualizzazione se NetworkActivity.refreshDisplay è impostato su true.

La configurazione di un BroadcastReceiver che viene chiamato inutilmente può comportare il consumo eccessivo di risorse di sistema. L'applicazione di esempio registra BroadcastReceiver NetworkReceiver in onCreate() e ne annulla la registrazione in onDestroy(). Questa operazione è più leggera della dichiarazione di un <receiver> nel manifest. Quando dichiari un <receiver> nel file manifest, l'app può riattivare la tua app in qualsiasi momento, anche se non la esegui da settimane. Registrando e annullando la registrazione di NetworkReceiver nell'attività principale, ti assicuri che l'app non venga riattivata dopo che l'utente è uscito dall'app. Se dichiari una <receiver> nel file manifest e sai esattamente dove ne hai bisogno, puoi utilizzare setComponentEnabledSetting() per attivarla e disattivarla in base alle tue esigenze.

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