Aplikacje na Androida mogą wysyłać i odbierać komunikaty z systemu Android i innych aplikacji na Androida, podobnie jak w przypadku wzorca publish-subscribe. Komunikaty te są wysyłane, gdy wystąpi interesujące Cię zdarzenie. System Android wysyła na przykład komunikaty, gdy wystąpią różne zdarzenia systemowe, takie jak uruchomienie systemu lub rozpoczęcie ładowania urządzenia. Aplikacje mogą też wysyłać własne komunikaty, np. aby powiadamiać inne aplikacje o czymś, co może ich zainteresować (np. o pobraniu nowych danych).
System optymalizuje wyświetlanie transmisji w celu utrzymania optymalnej kondycji systemu. W związku z tym nie możemy zagwarantować czasu realizacji transmisji. Aplikacje, które wymagają komunikacji między procesami o krótkim czasie oczekiwania, powinny rozważyć usługi powiązane.
Aplikacje mogą się zarejestrować, aby odbierać określone komunikaty Po wysłaniu komunikatu system automatycznie kieruje go do aplikacji, które zasubskrybowały otrzymywanie tego konkretnego typu transmisji.
Ogólnie mówiąc, komunikaty mogą służyć jako system przesyłania wiadomości w aplikacjach i poza nim. Musisz jednak uważać, aby nie nadużywać możliwości reagowania na komunikaty i uruchamiania zadań w tle, które mogą powodować powolne działanie systemu.
Komunikaty systemowe
System automatycznie wysyła komunikaty po wystąpieniu różnych zdarzeń systemowych, np. włączania i wyłączania trybu samolotowego. Transmisje systemowe są wysyłane do wszystkich aplikacji, które je subskrybują.
Sama wiadomość jest zawarta w obiekcie Intent
, którego ciąg działania identyfikuje wystąpienie zdarzenia (np. android.intent.action.AIRPLANE_MODE
). Intencja może też zawierać dodatkowe informacje dołączone do dodatkowego pola. Na przykład intencja trybu samolotowego zawiera dodatkową wartość logiczną, która wskazuje, czy tryb samolotowy jest włączony.
Więcej informacji o odczytywaniu intencji i pobieraniu ciągu działania z intencji znajdziesz w artykule o intencjach i filtrach intencji.
Pełną listę komunikatów systemowych znajdziesz w pliku BROADCAST_ACTIONS.TXT
w pakiecie SDK do Androida. Z każdym działaniem związanym z transmisją
jest powiązane stałe pole. Na przykład wartość stałej ACTION_AIRPLANE_MODE_CHANGED
to android.intent.action.AIRPLANE_MODE
. Dokumentacja każdego działania związanego z transmisją jest dostępna w powiązanym z nim polu stałych.
Zmiany w komunikatach systemowych
Wraz z rozwojem platformy Androida okresowo zmienia się sposób działania komunikatów systemowych. Pamiętaj o tych zmianach, aby zapewnić obsługę wszystkich wersji Androida.
Android 14
Gdy aplikacje są w pamięci podręcznej, przesyłanie transmisji jest zoptymalizowane pod kątem stanu systemu. Na przykład mniej ważne komunikaty systemowe, takie jak ACTION_SCREEN_ON
, są wstrzymywane, gdy aplikacja znajduje się w pamięci podręcznej. Gdy aplikacja przejdzie ze stanu z pamięci podręcznej do cyklu życia aktywnego procesu, system dostarczy wszystkie odroczone transmisje.
Ważne komunikaty, które są zadeklarowane w pliku manifestu, tymczasowo usuwają aplikacje ze stanu z pamięci podręcznej w celu ich dostarczenia.
Android 9
Począwszy od Androida 9 (poziom interfejsu API 28) transmisja NETWORK_STATE_CHANGED_ACTION
nie otrzymuje informacji o lokalizacji użytkownika ani danych umożliwiających jego identyfikację.
Jeśli aplikacja jest zainstalowana na urządzeniu z Androidem 9 lub nowszym, komunikaty systemowe z Wi-Fi nie zawierają identyfikatorów SSID, identyfikatorów BSSID, informacji o połączeniach ani wyników skanowania. Aby uzyskać te informacje, wywołaj metodę getConnectionInfo()
.
Android 8.0
Począwszy od Androida 8.0 (poziom interfejsu API 26) system nakłada dodatkowe ograniczenia na odbiorców zadeklarowanych w pliku manifestu.
Jeśli Twoja aplikacja jest kierowana na Androida 8.0 lub nowszego, nie możesz za pomocą pliku manifestu zdefiniować odbiornika w przypadku większości niejawnych transmisji (transmisji, które nie są kierowane konkretnie na Twoją aplikację). Możesz nadal używać odbiornika zarejestrowanego na podstawie kontekstu, gdy użytkownik aktywnie korzysta z Twojej aplikacji.
Android 7.0
Android 7.0 (poziom interfejsu API 24) i nowsze wersje nie wysyłają tych komunikatów systemowych:
Poza tym aplikacje kierowane na Androida 7.0 lub nowszego muszą rejestrować transmisję CONNECTIVITY_ACTION
za pomocą registerReceiver(BroadcastReceiver, IntentFilter)
.
Zadeklarowanie odbiorcy w pliku manifestu nie zadziała.
Odbieranie komunikatów
Aplikacje mogą odbierać komunikaty na 2 sposoby: przez odbiorców zadeklarowanych w pliku manifestu lub przez odbiorców zarejestrowanych na podstawie kontekstu.
Adresaci zadeklarowani w pliku manifestu
Jeśli w pliku manifestu zadeklarujesz odbiornik, system uruchomi aplikację (jeśli nie jest jeszcze uruchomiona) podczas wysyłania komunikatu.
Aby zadeklarować odbiornik w pliku manifestu, wykonaj te czynności:
Określ element
<receiver>
w pliku manifestu aplikacji.<!-- If this receiver listens for broadcasts sent from the system or from other apps, even other apps that you own, set android:exported to "true". --> <receiver android:name=".MyBroadcastReceiver" android:exported="false"> <intent-filter> <action android:name="APP_SPECIFIC_BROADCAST" /> </intent-filter> </receiver>
Filtry intencji określają działania związane z transmisją, które subskrybuje Twój odbiorca.
Podklasa
BroadcastReceiver
i zaimplementujonReceive(Context, Intent)
. Odbiornik transmisji w poniższych przykładowych logach i wyświetla zawartość transmisji:Kotlin
private const val TAG = "MyBroadcastReceiver" class MyBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { StringBuilder().apply { append("Action: ${intent.action}\n") append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n") toString().also { log -> Log.d(TAG, log) val binding = ActivityNameBinding.inflate(layoutInflater) val view = binding.root setContentView(view) Snackbar.make(view, log, Snackbar.LENGTH_LONG).show() } } } }
Java
public class MyBroadcastReceiver extends BroadcastReceiver { private static final String TAG = "MyBroadcastReceiver"; @Override public void onReceive(Context context, Intent intent) { StringBuilder sb = new StringBuilder(); sb.append("Action: " + intent.getAction() + "\n"); sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n"); String log = sb.toString(); Log.d(TAG, log); ActivityNameBinding binding = ActivityNameBinding.inflate(layoutInflater); val view = binding.root; setContentView(view); Snackbar.make(view, log, Snackbar.LENGTH_LONG).show(); } }
Aby włączyć wiązanie widoku, skonfiguruj atrybut viewBinding w pliku build.gradle na poziomie modułu.
Menedżer pakietów systemowych rejestruje odbiorcę po zainstalowaniu aplikacji. Odbiornik staje się wówczas oddzielnym punktem wejścia do aplikacji, co oznacza, że system może uruchomić aplikację i przesłać transmisję, jeśli aplikacja nie jest obecnie uruchomiona.
System tworzy nowy obiekt komponentu BroadcastReceiver
do obsługi każdej otrzymanej transmisji. Ten obiekt jest prawidłowy tylko przez czas wywołania onReceive(Context, Intent)
. Gdy kod powróci z tej metody, system uzna, że komponent nie jest już aktywny.
Odbiorcy zarejestrowani w kontekście
Adresaci zarejestrowani w kontekście otrzymują komunikaty, jeśli ich kontekst rejestracji jest prawidłowy. Jeśli na przykład zarejestrujesz się w kontekście Activity
, będziesz otrzymywać komunikaty, o ile aktywność nie zostanie zniszczona. Jeśli zarejestrujesz się w kontekście aplikacji, będziesz otrzymywać komunikaty, dopóki aplikacja będzie uruchomiona.
Aby zarejestrować odbiorcę na podstawie kontekstu, wykonaj te czynności:
W pliku kompilacji na poziomie modułu dodaj bibliotekę AndroidX Core w wersji 1.9.0 lub nowszej:
Odlotowy
dependencies { def core_version = "1.13.1" // Java language implementation implementation "androidx.core:core:$core_version" // Kotlin implementation "androidx.core:core-ktx:$core_version" // To use RoleManagerCompat implementation "androidx.core:core-role:1.0.0" // To use the Animator APIs implementation "androidx.core:core-animation:1.0.0" // To test the Animator APIs androidTestImplementation "androidx.core:core-animation-testing:1.0.0" // Optional - To enable APIs that query the performance characteristics of GMS devices. implementation "androidx.core:core-performance:1.0.0" // Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google implementation "androidx.core:core-google-shortcuts:1.1.0" // Optional - to support backwards compatibility of RemoteViews implementation "androidx.core:core-remoteviews:1.1.0" // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12 implementation "androidx.core:core-splashscreen:1.2.0-alpha01" }
Kotlin
dependencies { val core_version = "1.13.1" // Java language implementation implementation("androidx.core:core:$core_version") // Kotlin implementation("androidx.core:core-ktx:$core_version") // To use RoleManagerCompat implementation("androidx.core:core-role:1.0.0") // To use the Animator APIs implementation("androidx.core:core-animation:1.0.0") // To test the Animator APIs androidTestImplementation("androidx.core:core-animation-testing:1.0.0") // Optional - To enable APIs that query the performance characteristics of GMS devices. implementation("androidx.core:core-performance:1.0.0") // Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google implementation("androidx.core:core-google-shortcuts:1.1.0") // Optional - to support backwards compatibility of RemoteViews implementation("androidx.core:core-remoteviews:1.1.0") // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12 implementation("androidx.core:core-splashscreen:1.2.0-alpha01") }
Utwórz instancję
BroadcastReceiver
:Kotlin
val br: BroadcastReceiver = MyBroadcastReceiver()
Java
BroadcastReceiver br = new MyBroadcastReceiver();
Utwórz instancję
IntentFilter
:Kotlin
val filter = IntentFilter(APP_SPECIFIC_BROADCAST)
Java
IntentFilter filter = new IntentFilter(APP_SPECIFIC_BROADCAST);
Wybierz, czy odbiornik ma zostać wyeksportowany i widoczny dla innych aplikacji na urządzeniu. Jeśli ten odbiornik nasłuchuje komunikatów wysyłanych z systemu lub innych aplikacji (nawet Twoich innych aplikacji), użyj flagi
RECEIVER_EXPORTED
. Jeśli zamiast tego ten odbiornik nasłuchuje tylko transmisji wysyłanych przez Twoją aplikację, użyj flagiRECEIVER_NOT_EXPORTED
.Kotlin
val listenToBroadcastsFromOtherApps = false val receiverFlags = if (listenToBroadcastsFromOtherApps) { ContextCompat.RECEIVER_EXPORTED } else { ContextCompat.RECEIVER_NOT_EXPORTED }
Java
boolean listenToBroadcastsFromOtherApps = false; if (listenToBroadcastsFromOtherApps) { receiverFlags = ContextCompat.RECEIVER_EXPORTED; } else { receiverFlags = ContextCompat.RECEIVER_NOT_EXPORTED; }
Zarejestruj odbiorcę, dzwoniąc do
registerReceiver()
:Kotlin
ContextCompat.registerReceiver(context, br, filter, receiverFlags)
Java
ContextCompat.registerReceiver(context, br, filter, receiverFlags);
Aby przestać odbierać komunikaty, zadzwoń pod numer
unregisterReceiver(android.content.BroadcastReceiver)
. Pamiętaj, aby wyrejestrować odbiorcę, gdy go nie potrzebujesz lub kontekst nie jest już prawidłowy.Zwróć uwagę na to, gdzie rejestrujesz i wyrejestrowujesz odbiorcę. Jeśli na przykład zarejestrujesz odbiorcę w
onCreate(Bundle)
na podstawie kontekstu aktywności, musisz wyrejestrować go wonDestroy()
, aby zapobiec wyciekowi odbiorcy poza kontekst aktywności. Jeśli rejestrujesz odbiornik wonResume()
, wyrejestruj go wonPause()
, aby uniknąć wielokrotnego rejestrowania (jeśli nie chcesz odbierać transmisji po wstrzymaniu, co może ograniczyć niepotrzebne koszty systemu). Nie wyrejestruj się wonSaveInstanceState(Bundle)
, bo ta czynność nie jest wywoływana, gdy użytkownik cofnie się do stosu historii.
Wpływ na stan procesu
Określa, czy BroadcastReceiver
działa, czy nie, wpływa na zawarty w nim proces, co może zmienić prawdopodobieństwo wyłączenia systemu. Proces na pierwszym planie wykonuje metodę onReceive()
odbiorcy. System uruchamia proces z wyjątkiem skrajnie wysokiego obciążenia pamięci.
Radio BroadcastReceivedr zostanie zdezaktywowane po onReceive()
. Proces hosta odbiorcy jest równie istotny jak komponenty aplikacji. Jeśli ten proces hostuje tylko odbiorcę zadeklarowanego w pliku manifestu (często zdarza się to w przypadku aplikacji, z którymi użytkownik nigdy nie korzystał lub z których ostatnio nie wchodził w interakcję), system może zamknąć go po onReceive()
, aby udostępnić zasoby dla innych, bardziej krytycznych procesów.
Z tego względu odbiorcy transmisji nie powinni inicjować długo trwających wątków w tle.
Po upływie onReceive()
system może zatrzymać ten proces w dowolnym momencie, aby odzyskać pamięć, co spowoduje zakończenie utworzonego wątku. Aby podtrzymać ten proces, zaplanuj JobService
z odbiornika za pomocą JobScheduler
, aby system wiedział, że proces działa.
Więcej informacji na ten temat znajdziesz w artykule Omówienie zadań w tle.
Wysyłam komunikaty
Android zapewnia 3 sposoby wysyłania komunikatów przez aplikacje:
- Metoda
sendOrderedBroadcast(Intent, String)
wysyła transmisje do 1 odbiorcy w danym momencie. W miarę wykonywania swoich zadań każdy odbiorca może przekazać wynik do następnego odbiorcy lub całkowicie przerwać transmisję, by nie został przekazany innym odbiorcom. Kolejność odbiorników można kontrolować za pomocą atrybutu android:Priority pasującego elementuIntent-filter. Odbiorniki o takim samym priorytecie będą uruchamiane w dowolnej kolejności. - Metoda
sendBroadcast(Intent)
wysyła transmisje do wszystkich odbiorców w nieokreślonej kolejności. Jest to tzw. normalna transmisja. Jest to bardziej wydajne, ale oznacza, że odbiorcy nie mogą odczytywać wyników z innych odbiorców, rozpowszechniać danych otrzymanych z transmisji ani jej przerywać.
Poniższy fragment kodu pokazuje, jak wysłać transmisję przez utworzenie intencji i wywołanie sendBroadcast(Intent)
.
Kotlin
Intent().also { intent -> intent.setAction("com.example.broadcast.MY_NOTIFICATION") intent.putExtra("data", "Nothing to see here, move along.") sendBroadcast(intent) }
Java
Intent intent = new Intent(); intent.setAction("com.example.broadcast.MY_NOTIFICATION"); intent.putExtra("data", "Nothing to see here, move along."); sendBroadcast(intent);
Komunikaty są zawarte w obiekcie Intent
.
Ciąg znaków działania intencji musi zawierać składnię nazwy pakietu Java aplikacji i jednoznacznie identyfikować transmitowane zdarzenie. Dodatkowe informacje możesz dołączyć do intencji za pomocą polecenia putExtra(String, Bundle)
.
Możesz też ograniczyć transmisję do zestawu aplikacji w tej samej organizacji, wywołując w intencji setPackage(String)
.
Ograniczanie transmisji przy użyciu uprawnień
Uprawnienia pozwalają ograniczyć możliwość transmisji do zbioru aplikacji, które mają określone uprawnienia. Możesz egzekwować ograniczenia dotyczące nadawcy lub odbiorcy transmisji.
Wysyłanie z uprawnieniami
Wywołując metodę sendBroadcast(Intent, String)
lub sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
, możesz określić parametr uprawnień. Transmisję mogą otrzymać tylko odbiorcy, którzy poprosili o ich przyznanie za pomocą tagu
Kotlin
sendBroadcast(Intent(BluetoothDevice.ACTION_FOUND), Manifest.permission.BLUETOOTH_CONNECT)
Java
sendBroadcast(new Intent(BluetoothDevice.ACTION_FOUND), Manifest.permission.BLUETOOTH_CONNECT)
Aby odebrać transmisję, aplikacja odbierająca musi poprosić o uprawnienia, jak pokazano poniżej:
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
Możesz określić istniejące uprawnienia systemowe, np. BLUETOOTH_CONNECT
, albo zdefiniować uprawnienie niestandardowe za pomocą elementu <permission>
. Informacje o uprawnieniach i zabezpieczeniach znajdziesz w artykule Uprawnienia systemu.
Odbieranie z uprawnieniami
Jeśli określisz parametr uprawnień podczas rejestrowania odbiornika (za pomocą tagu registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
lub <receiver>
w pliku manifestu), tylko nadawcy, którzy poprosili o pozwolenie za pomocą tagu <uses-permission>
w swoim manifeście (a następnie otrzymali je, jeśli są niebezpieczne), będą mogli wysłać intencję do odbiorcy.
Załóżmy na przykład, że aplikacja odbierająca ma zadeklarowany odbiornik, jak pokazano poniżej:
<receiver android:name=".MyBroadcastReceiver"
android:permission="android.permission.BLUETOOTH_CONNECT">
<intent-filter>
<action android:name="android.intent.action.ACTION_FOUND"/>
</intent-filter>
</receiver>
Lub aplikacja odbierająca ma odbiorcę zarejestrowanym w kontekście, jak pokazano poniżej:
Kotlin
var filter = IntentFilter(Intent.ACTION_FOUND) registerReceiver(receiver, filter, Manifest.permission.BLUETOOTH_CONNECT, null )
Java
IntentFilter filter = new IntentFilter(Intent.ACTION_FOUND); registerReceiver(receiver, filter, Manifest.permission.BLUETOOTH_CONNECT, null );
Następnie, aby móc wysyłać komunikaty do tych odbiorców, aplikacja wysyłająca musi poprosić o to uprawnienie w sposób pokazany poniżej:
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
Kwestie bezpieczeństwa i sprawdzone metody
Oto kilka kwestii dotyczących bezpieczeństwa i sprawdzonych metod związanych z wysyłaniem i odbieraniem transmisji:
Jeśli wiele aplikacji zarejestrowało się w celu otrzymywania tego samego komunikatu w pliku manifestu, system może uruchamiać wiele aplikacji, co znacząco wpływa zarówno na wydajność urządzenia, jak i wrażenia użytkowników. Aby tego uniknąć, korzystaj z rejestracji kontekstu, a nie deklaracji w pliku manifestu. Czasami system Android wymusza stosowanie odbiorców zarejestrowanych na podstawie kontekstu. Na przykład transmisja
CONNECTIVITY_ACTION
jest dostarczana tylko do odbiorców zarejestrowanych na podstawie kontekstu.Nie przekazuj informacji poufnych przy użyciu intencji niejawnych. Informacje te mogą zostać odczytane przez dowolną aplikację, która zarejestrowała się w celu odbierania transmisji. Istnieją trzy sposoby kontrolowania, kto może otrzymywać Twoje komunikaty:
- Podczas wysyłania transmisji możesz określić uprawnienia.
- W Androidzie 4.0 i nowszych możesz określić pakiet przy użyciu
setPackage(String)
podczas wysyłania transmisji. System ogranicza transmisję do zestawu aplikacji, które pasują do pakietu.
Po zarejestrowaniu odbiornika każda aplikacja może wysyłać do niego potencjalnie szkodliwe transmisje. Jest kilka sposobów na ograniczenie liczby emisji wysyłanych przez aplikację:
- Podczas rejestrowania odbiornika możesz określić odpowiednie uprawnienia.
- W przypadku odbiorców zadeklarowanych w pliku manifestu możesz ustawić w pliku manifestu wartość atrybutu android:exported na „false”. Odbiorca nie otrzymuje transmisji ze źródeł spoza aplikacji.
Przestrzeń nazw działań związanych z transmisją jest globalna. Pamiętaj, że nazwy działań i inne ciągi znaków muszą być zapisane w Twojej przestrzeni nazw. W przeciwnym razie może dojść do konfliktu z innymi aplikacjami.
Metoda
onReceive(Context, Intent)
odbiorcy działa w wątku głównym, dlatego powinna być szybko uruchamiana i zwracana. Jeśli musisz wykonywać długotrwałą pracę, uważaj na uruchamianie wątków lub uruchamianie usług w tle, ponieważ system może zakończyć cały proces po zwróceniu funkcjionReceive()
. Więcej informacji znajdziesz w artykule Wpływ na stan procesu. Zalecamy:- Wywołuję
goAsync()
w metodzieonReceive()
odbiorcy i przekazujęBroadcastReceiver.PendingResult
do wątku w tle. Transmisja pozostanie aktywna po powrocie z:onReceive()
. Jednak nawet przy takim podejściu system oczekuje, że zakończy się bardzo szybko (poniżej 10 sekund). Pozwala to przenieść pracę do innego wątku, co pozwoli uniknąć zakłóceń w wątku głównym. - Planuję zadanie za pomocą:
JobScheduler
. Więcej informacji znajdziesz w artykule o inteligentnym harmonogramie zadań.
- Wywołuję
Nie rozpoczynaj działań związanych z odbiornikami, ponieważ jest to po prostu uciążliwe, zwłaszcza jeśli masz więcej niż 1 odbiornik. Rozważ wyświetlenie powiadomienia.