Z tej lekcji dowiesz się, jak pisać aplikacje, które mają precyzyjną kontrolę nad wykorzystaniem zasobów sieciowych. Jeśli Twoja aplikacja wykonuje wiele operacji sieciowych, musisz podać ustawienia użytkownika, które pozwolą im kontrolować nawyki użytkownika dotyczące danych. Mogą na przykład określić, jak często aplikacja ma synchronizować dane, czy przesyłać/pobierać tylko przez Wi-Fi, czy używać transmisji danych w roamingu itd. Dzięki takim ustawieniom użytkownicy mogą znacznie rzadziej wyłączyć dostęp aplikacji do danych w tle, gdy zbliżą się do swojego limitu, ponieważ mogą precyzyjnie kontrolować ilość danych zużywanych przez aplikację.
Więcej informacji o wykorzystaniu sieci przez aplikację, w tym o liczbie i typach połączeń sieciowych w danym okresie, znajdziesz w artykułach Aplikacje internetowe i Sprawdzanie ruchu w sieci za pomocą profilu sieci. Ogólne wskazówki dotyczące pisania aplikacji, które minimalizują wpływ pobierania i połączeń sieciowych na żywotność baterii, znajdziesz w artykułach Optymalizowanie czasu pracy na baterii i Przenoszenie danych bez obciążania baterii.
Możesz też zapoznać się z przykładem NetworkConnect.
Sprawdzanie połączenia sieciowego urządzenia
Urządzenie może mieć różne typy połączeń sieciowych. Ta lekcja dotyczy korzystania
z sieci Wi-Fi i sieci komórkowej. Pełną listę możliwych typów sieci znajdziesz w sekcji ConnectivityManager
.
Wi-Fi jest zwykle szybsze. Poza tym mobilna transmisja danych jest często zmierzona, co może być drogie. Typową strategią w przypadku aplikacji jest pobieranie dużych ilości danych tylko wtedy, gdy dostępna jest sieć Wi-Fi.
Przed wykonaniem operacji sieciowych warto sprawdzić stan połączenia sieciowego. Może to między innymi uniemożliwić aplikacji wykorzystanie niewłaściwego radia. Jeśli połączenie sieciowe jest niedostępne, aplikacja powinna odpowiedzieć płynnie. Do sprawdzania połączenia sieciowego zazwyczaj używane są te klasy:
ConnectivityManager
: odpowiada na pytania o stan połączenia sieciowego. Informuje też aplikacje o zmianach połączenia sieciowego.NetworkInfo
: opisuje stan interfejsu sieciowego danego typu (obecnie Komórka lub Wi-Fi).
Ten fragment kodu testuje połączenia z siecią Wi-Fi i komórkową. Określa ono, czy te interfejsy sieciowe są dostępne (to znaczy, czy można nawiązać połączenie sieciowe) i czy połączenie jest możliwe (tj. czy można nawiązać połączenie sieciowe oraz czy można ustanowić gniazda i przekazywać dane):
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);
Pamiętaj, że nie możesz podejmować decyzji o dostępności sieci. Zawsze sprawdzaj parametr isConnected()
przed wykonywaniem operacji sieciowych, ponieważ isConnected()
obsługuje przypadki niestabilnego połączenia z sieciami komórkowymi, trybu samolotowego i ograniczonych danych w tle.
Poniżej znajdziesz bardziej zwięzły sposób na sprawdzenie dostępności interfejsu sieci. Metoda getActiveNetworkInfo()
zwraca instancję NetworkInfo
reprezentującą pierwszy połączony interfejs sieci, jaki uda się znaleźć, lub null
, jeśli żaden z interfejsów nie jest połączony (co oznacza, że połączenie z internetem jest niedostępne):
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()); }
Aby uzyskać bardziej szczegółowe zapytania, możesz użyć metody NetworkInfo.DetailedState
, ale rzadko jest to konieczne.
Zarządzanie wykorzystaniem sieci
Możesz wdrożyć działanie związane z preferencjami, które daje użytkownikom wyraźną kontrolę nad wykorzystaniem zasobów sieciowych w aplikacji. Na przykład:
- Możesz zezwolić użytkownikom na przesyłanie filmów tylko wtedy, gdy urządzenie jest połączone z siecią Wi-Fi.
- Dane możesz synchronizować (lub nie) w zależności od określonych kryteriów, takich jak dostępność sieci czy przedział czasu.
Aby można było napisać aplikację, która obsługuje dostęp do sieci i zarządza jej wykorzystaniem, plik manifestu musi mieć odpowiednie uprawnienia i filtry intencji.
- Plik manifestu wyodrębniony w dalszej części tej sekcji obejmuje te uprawnienia:
android.permission.INTERNET
– umożliwia aplikacjom otwieranie gniazd sieciowych.android.permission.ACCESS_NETWORK_STATE
– umożliwia aplikacjom dostęp do informacji o sieciach.
- Możesz zadeklarować filtr intencji dla działania
ACTION_MANAGE_NETWORK_USAGE
, aby wskazać, że aplikacja definiuje działanie, które udostępnia opcje kontrolowania użycia danych.ACTION_MANAGE_NETWORK_USAGE
pokazuje ustawienia zarządzania siecią danych przez określoną aplikację. Jeśli w aplikacji występuje działanie związane z ustawieniami, które umożliwia użytkownikom kontrolowanie wykorzystania sieci, zadeklaruj ten filtr intencji dla tej aktywności.
W przykładowej aplikacji działanie to jest obsługiwane przez klasę SettingsActivity
, która wyświetla interfejs ustawień, aby użytkownicy mogli zdecydować, kiedy pobrać plik danych.
<?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>
Aplikacje, które mają dostęp do poufnych danych użytkownika i są kierowane na Androida 11 lub nowszego, mogą przyznawać dostęp do sieci poszczególnym procesom. Jasno określając, które procesy mogą mieć dostęp do sieci, izolujesz cały kod, który nie musi przesyłać danych.
Chociaż nie chronimy aplikacji przed przypadkowym przesyłaniem danych, to jednak pozwala zmniejszyć ryzyko wystąpienia błędów w aplikacji powodujących wyciek danych.
Poniżej znajdziesz przykład pliku manifestu, który korzysta z funkcji poszczególnych procesów:
<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>
Wdrażanie działania opartego na preferencjach
Jak widać we wcześniejszym fragmencie pliku manifestu w tym temacie, aktywność SettingsActivity
w przykładowej aplikacji zawiera filtr intencji dla działania ACTION_MANAGE_NETWORK_USAGE
. SettingsActivity
jest podklasą grupy PreferenceActivity
. Wyświetla on ekran ustawień (jak widać na Rysunku 1), na którym użytkownicy mogą określić:
- Określa, czy mają być wyświetlane podsumowania dla każdego wpisu w kanale XML, czy tylko dla linku dla każdego wpisu.
- Określa, czy plik XML ma zostać pobrany, gdy dostępne jest połączenie sieciowe, czy tylko wtedy, gdy dostępna jest sieć Wi-Fi.
Oto SettingsActivity
. Pamiętaj, że implementuje ona OnSharedPreferenceChangeListener
.
Gdy użytkownik zmieni ustawienie, uruchamia się tag onSharedPreferenceChanged()
, który ustawia refreshDisplay
na wartość prawda. Spowoduje to odświeżenie ekranu, gdy użytkownik wróci do głównej aktywności:
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; } }
Reagowanie na zmiany preferencji
Gdy użytkownik zmieni swoje preferencje na ekranie ustawień, zwykle ma to związek z działaniem aplikacji. Ten fragment kodu sprawdza ustawienia
preferencji w onStart()
. jeśli ustawienie jest zgodne z połączeniem sieciowym urządzenia (np. jeśli ustawienie to "Wi-Fi"
, a urządzenie ma połączenie z Wi-Fi), aplikacja pobiera plik danych i odświeża wyświetlacz.
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(); } } ... }
Wykrywanie zmian połączeń
Ostatnim elementem łamigłówki jest podklasa BroadcastReceiver
(NetworkReceiver
). Gdy połączenie sieciowe urządzenia się zmieni, NetworkReceiver
przechwytuje działanie CONNECTIVITY_ACTION
, określa stan połączenia sieciowego i odpowiednio ustawia flagi wifiConnected
oraz mobileConnected
na prawda/fałsz. W efekcie przy następnym powrocie użytkownika do aplikacji aplikacja pobierze tylko najnowszy kanał i zaktualizuje wyświetlacz, jeśli NetworkActivity.refreshDisplay
ma wartość true
.
Niepotrzebnie skonfigurowane BroadcastReceiver
może spowodować zużycie zasobów systemowych. Przykładowa aplikacja rejestruje BroadcastReceiver
NetworkReceiver
w onCreate()
i wyrejestrowana jest w onDestroy()
. To łatwiejsze niż zadeklarowanie <receiver>
w pliku manifestu. Jeśli zadeklarujesz w pliku manifestu funkcję <receiver>
, może ona ją wybudzić w dowolnym momencie, nawet jeśli nie była używana od tygodni. Rejestrując i wyrejestrowując usługę NetworkReceiver
w głównej aktywności, masz pewność, że aplikacja nie zostanie wybudzona po opuszczeniu aplikacji przez użytkownika. Jeśli zadeklarujesz w pliku manifestu właściwość <receiver>
i będziesz dokładnie wiedzieć, gdzie jest potrzebna, możesz ją włączyć i wyłączyć za pomocą setComponentEnabledSetting()
.
Oto 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(); } } }