Netzwerknutzung verwalten

In dieser Lektion wird beschrieben, wie Sie Anwendungen schreiben, mit denen Sie die Nutzung von Netzwerkressourcen genau steuern können. Wenn Ihre Anwendung viele Netzwerkvorgänge ausführt, sollten Sie Nutzereinstellungen bereitstellen, mit denen Nutzer die Datengewohnheiten Ihrer Anwendung steuern können, z. B. wie oft Ihre Anwendung Daten synchronisiert, ob Uploads/Downloads nur über WLAN durchgeführt werden sollen oder ob Daten beim Roaming verwendet werden sollen usw. Mit diesen Steuerelementen ist es viel weniger wahrscheinlich, dass Nutzer den Zugriff Ihrer App auf Hintergrunddaten deaktivieren, wenn sie sich ihrer Limits nähern, da sie stattdessen genau steuern können, wie viele Daten von der App verwendet werden.

Weitere Informationen zur Netzwerknutzung Ihrer Anwendung, einschließlich der Anzahl und Art der Netzwerkverbindungen über einen bestimmten Zeitraum, finden Sie unter Webanwendungen und Netzwerktraffic mit Netzwerk-Profiler prüfen. Allgemeine Richtlinien zum Schreiben von Apps, die die Akkulaufzeit von Downloads und Netzwerkverbindungen minimieren, finden Sie unter Akkulaufzeit optimieren und Daten ohne Entladung des Akkus übertragen.

Sie können sich auch das NetworkConnect-Beispiel ansehen.

Netzwerkverbindung eines Geräts prüfen

Ein Gerät kann über verschiedene Arten von Netzwerkverbindungen verfügen. In dieser Lektion geht es um die Nutzung einer WLAN- oder Mobilfunkverbindung. Eine vollständige Liste der möglichen Netzwerktypen finden Sie unter ConnectivityManager.

Das WLAN ist normalerweise schneller. Außerdem werden mobile Daten häufig in Metern abgerechnet, was teuer werden kann. Eine gängige Strategie für Apps besteht darin, große Daten nur dann abzurufen, wenn ein WLAN-Netzwerk verfügbar ist.

Bevor Sie Netzwerkvorgänge ausführen, empfiehlt es sich, den Status der Netzwerkverbindung zu überprüfen. Dies kann unter anderem verhindern, dass Ihre Anwendung versehentlich die falsche Funkschnittstelle verwendet. Wenn keine Netzwerkverbindung verfügbar ist, sollte Ihre Anwendung ordnungsgemäß reagieren. Zur Prüfung der Netzwerkverbindung verwenden Sie in der Regel die folgenden Klassen:

  • ConnectivityManager: beantwortet Abfragen zum Status der Netzwerkverbindung. Außerdem werden Anwendungen benachrichtigt, wenn sich die Netzwerkverbindung ändert.
  • NetworkInfo: Beschreibt den Status einer Netzwerkschnittstelle eines bestimmten Typs (derzeit entweder „Mobilfunk“ oder „WLAN“).

Mit diesem Code-Snippet wird die Netzwerkverbindung für WLAN und Mobilgeräte getestet. Es ermittelt, ob diese Netzwerkschnittstellen verfügbar (d. h. ob eine Netzwerkverbindung möglich ist) und/oder verbunden sind (d. h. ob eine Netzwerkverbindung besteht und ob es möglich ist, Sockets einzurichten und Daten zu übergeben):

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

Beachten Sie, dass Sie Entscheidungen nicht darauf stützen sollten, ob ein Netzwerk "verfügbar" ist. Sie sollten vor dem Netzwerkbetrieb immer isConnected() prüfen, da isConnected() bei unzuverlässigen Mobilfunknetzen, im Flugmodus und eingeschränkten Hintergrunddaten geschützt ist.

Im Folgenden wird genauer beschrieben, wie Sie prüfen können, ob eine Netzwerkschnittstelle verfügbar ist. Die Methode getActiveNetworkInfo() gibt eine NetworkInfo-Instanz zurück, die die erste verbundene Netzwerkschnittstelle darstellt, die sie finden kann, oder null, wenn keine der Schnittstellen verbunden ist, d. h. keine Internetverbindung verfügbar ist:

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

Wenn Sie einen detaillierteren Status abfragen möchten, können Sie NetworkInfo.DetailedState verwenden. Dies sollte jedoch selten erforderlich sein.

Netzwerknutzung verwalten

Sie können eine Einstellungsaktivität implementieren, mit der Nutzer die Nutzung von Netzwerkressourcen durch Ihre Anwendung explizit steuern können. Beispiele:

  • Möglicherweise können Nutzer Videos nur dann hochladen, wenn das Gerät mit einem WLAN-Netzwerk verbunden ist.
  • Je nach Kriterien wie Netzwerkverfügbarkeit, Zeitintervall usw. können Sie eine Synchronisierung durchführen (oder auch nicht).

Wenn Sie eine App schreiben möchten, die den Netzwerkzugriff unterstützt und die Netzwerknutzung verwaltet, muss Ihr Manifest die entsprechenden Berechtigungen und Intent-Filter haben.

  • Der Manifestauszug weiter unten in diesem Abschnitt enthält die folgenden Berechtigungen:
  • Sie können den Intent-Filter für die Aktion ACTION_MANAGE_NETWORK_USAGE deklarieren, um anzugeben, dass Ihre Anwendung eine Aktivität definiert, die Optionen zur Steuerung der Datennutzung bietet. ACTION_MANAGE_NETWORK_USAGE zeigt Einstellungen zum Verwalten der Netzwerkdatennutzung einer bestimmten Anwendung. Wenn es in Ihrer App eine Aktivität gibt, mit der Nutzer die Netzwerknutzung steuern können, sollten Sie diesen Intent-Filter für diese Aktivität deklarieren.

In der Beispielanwendung wird diese Aktion von der Klasse SettingsActivity durchgeführt, die eine Einstellungs-UI anzeigt, über die Nutzer entscheiden können, wann sie einen Feed herunterladen möchten.

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

Apps, die mit sensiblen Nutzerdaten umgehen und auf Android 11 und höher ausgerichtet sind, können Netzwerkzugriff pro Prozess gewähren. Indem Sie explizit angeben, welche Prozesse Zugriff auf das Netzwerk haben, isolieren Sie den gesamten Code, der keine Daten hochladen muss.

Es ist zwar nicht garantiert, dass Ihre Anwendung versehentlich Daten hochlädt, bietet Ihnen aber eine Möglichkeit, die Wahrscheinlichkeit von Fehlern in Ihrer Anwendung zu verringern, die ein Datenleck verursachen.

Im Folgenden sehen Sie ein Beispiel für eine Manifestdatei, die die Pro-Prozess-Funktionalität verwendet:

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

Bevorzugte Aktivität implementieren

Wie Sie im Manifestauszug weiter oben in diesem Thema sehen können, hat die Aktivität SettingsActivity der Beispiel-App einen Intent-Filter für die Aktion ACTION_MANAGE_NETWORK_USAGE. SettingsActivity ist eine abgeleitete Klasse von PreferenceActivity. Hier wird ein Bildschirm mit Einstellungen angezeigt (siehe Abbildung 1), auf dem Nutzer Folgendes angeben können:

  • Gibt an, ob Zusammenfassungen für jeden XML-Feed-Eintrag oder nur ein Link für jeden Eintrag angezeigt werden sollen.
  • Gibt an, ob der XML-Feed heruntergeladen werden soll, wenn eine Netzwerkverbindung verfügbar ist, oder nur, wenn WLAN verfügbar ist.

Steuerfeld „Einstellungen“ Netzwerkeinstellung festlegen

Abbildung 1: „Einstellungen“-Aktivität.

Hier ist SettingsActivity. Dabei wird OnSharedPreferenceChangeListener implementiert. Wenn ein Nutzer eine Einstellung ändert, wird onSharedPreferenceChanged() ausgelöst, wodurch refreshDisplay auf „true“ gesetzt wird. Dadurch wird die Anzeige aktualisiert, wenn der Nutzer zur Hauptaktivität zurückkehrt:

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

Auf Änderungen der Einstellungen reagieren

Wenn Nutzer Einstellungen auf dem Einstellungsbildschirm ändern, hat dies in der Regel Auswirkungen auf das Verhalten der App. In diesem Snippet prüft die Anwendung die Einstellungen in onStart(). Bei einer Übereinstimmung zwischen der Einstellung und der Netzwerkverbindung des Geräts (z. B. wenn die Einstellung "Wi-Fi" ist und das Gerät eine WLAN-Verbindung hat), lädt die App den Feed herunter und aktualisiert das Display.

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

}

Verbindungsänderungen erkennen

Das letzte Teil des Puzzles ist die abgeleitete BroadcastReceiver-Klasse NetworkReceiver. Wenn sich die Netzwerkverbindung des Geräts ändert, fängt NetworkReceiver die Aktion CONNECTIVITY_ACTION ab, ermittelt den Status der Netzwerkverbindung und setzt die Flags wifiConnected und mobileConnected entsprechend auf „true“ oder „false“. Das Ergebnis ist, dass die App bei der nächsten Rückkehr zur App nur dann den neuesten Feed herunterlädt und die Anzeige aktualisiert, wenn NetworkActivity.refreshDisplay auf true gesetzt ist.

Das Einrichten einer BroadcastReceiver, die unnötigerweise aufgerufen wird, kann die Systemressourcen belasten. Die Beispielanwendung registriert BroadcastReceiver NetworkReceiver in onCreate() und hebt die Registrierung in onDestroy() auf. Dies ist einfacher als das Deklarieren von <receiver> im Manifest. Wenn Sie ein <receiver> im Manifest deklarieren, kann dadurch Ihre Anwendung jederzeit aktiviert werden, auch wenn Sie sie seit Wochen nicht mehr ausgeführt haben. Wenn Sie NetworkReceiver in der Hauptaktivität registrieren und die Registrierung aufheben, wird die App nicht mehr aktiviert, nachdem der Nutzer die App verlassen hat. Wenn Sie eine <receiver> im Manifest deklarieren und genau wissen, wo Sie sie benötigen, können Sie sie mit setComponentEnabledSetting() nach Bedarf aktivieren bzw. deaktivieren.

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