Przegląd transmisji

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:

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

  2. Podklasa BroadcastReceiver i zaimplementuj onReceive(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:

  1. W pliku kompilacji na poziomie modułu dodaj bibliotekę AndroidX Core w wersji 1.9.0 lub nowszej:

    Odlotowy

    dependencies {
        def core_version = "1.12.0"
    
        // 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-rc01"
        // To test the Animator APIs
        androidTestImplementation "androidx.core:core-animation-testing:1.0.0-rc01"
    
        // 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-alpha01"
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation "androidx.core:core-splashscreen:1.1.0-alpha02"
    }
    

    Kotlin

    dependencies {
        val core_version = "1.12.0"
    
        // 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-rc01")
        // To test the Animator APIs
        androidTestImplementation("androidx.core:core-animation-testing:1.0.0-rc01")
    
        // 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-alpha01")
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation("androidx.core:core-splashscreen:1.1.0-alpha02")
    }
    
  2. Utwórz instancję BroadcastReceiver:

    Kotlin

    val br: BroadcastReceiver = MyBroadcastReceiver()
    

    Java

    BroadcastReceiver br = new MyBroadcastReceiver();
    
  3. Utwórz instancję IntentFilter:

    Kotlin

    val filter = IntentFilter(APP_SPECIFIC_BROADCAST)
    

    Java

    IntentFilter filter = new IntentFilter(APP_SPECIFIC_BROADCAST);
    
  4. 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 flagi RECEIVER_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;
    }
    
  5. Zarejestruj odbiorcę, dzwoniąc do registerReceiver():

    Kotlin

    ContextCompat.registerReceiver(context, br, filter, receiverFlags)
    

    Java

    ContextCompat.registerReceiver(context, br, filter, receiverFlags);
    
  6. 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 w onDestroy(), aby zapobiec wyciekowi odbiorcy poza kontekst aktywności. Jeśli rejestrujesz odbiornik w onResume(), wyrejestruj go w onPause(), aby uniknąć wielokrotnego rejestrowania (jeśli nie chcesz odbierać transmisji po wstrzymaniu, co może ograniczyć niepotrzebne koszty systemu). Nie wyrejestruj się w onSaveInstanceState(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 w pliku manifestu (a następnie otrzymali je, jeśli jest to niebezpieczne). Na przykład ten kod wysyła komunikat:

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 funkcji onReceive(). Więcej informacji znajdziesz w artykule Wpływ na stan procesu. Zalecamy:

    • Wywołuję goAsync() w metodzie onReceive() 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ń.
  • 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.