Panoramica delle trasmissioni

Le app per Android possono inviare o ricevere annunci dal sistema Android e da altre app Android, in modo simile al modello di progettazione publish-subscribe. Queste trasmissioni vengono inviate quando si verifica un evento di interesse. Ad esempio, il sistema Android invia annunci quando si verificano vari eventi di sistema, come l'avvio del sistema o la ricarica del dispositivo. Le app possono anche inviare annunci personalizzati, ad esempio, per informare altre app di qualcosa a cui potrebbero essere interessati (ad esempio, che sono stati scaricati alcuni nuovi dati).

Il sistema ottimizza la distribuzione delle trasmissioni per mantenere l'integrità ottimale del sistema. Di conseguenza, i tempi di pubblicazione delle trasmissioni non sono garantiti. Le app che richiedono una comunicazione tra processi a bassa latenza dovrebbero considerare i servizi associati.

Le app possono registrarsi per ricevere annunci specifici. Quando viene inviato un broadcast, il sistema indirizza automaticamente gli annunci alle app che si sono iscritte a ricevere quel determinato tipo di trasmissione.

In generale, gli annunci possono essere utilizzati come sistema di messaggistica per più app e al di fuori del normale flusso utente. Tuttavia, devi fare attenzione a non abusare dell'opportunità di rispondere alle trasmissioni ed eseguire job in background che possono contribuire a rallentare le prestazioni del sistema.

Informazioni sulle trasmissioni di sistema

Il sistema invia automaticamente annunci quando si verificano diversi eventi di sistema, ad esempio quando il sistema attiva e disattiva la modalità aereo. Le trasmissioni di sistema vengono inviate a tutte le app iscritte per ricevere l'evento.

Il messaggio dell'annuncio viene aggregato in un oggetto Intent la cui stringa di azione identifica l'evento che si è verificato (ad esempio android.intent.action.AIRPLANE_MODE). L'intent può anche includere informazioni aggiuntive nel suo campo aggiuntivo. Ad esempio, l'intent della modalità aereo include un extra booleano che indica se la modalità è attiva o meno.

Per ulteriori informazioni su come leggere gli intent e ottenere la stringa di azione da un intent, consulta la sezione Filtri per intent e intent.

Per un elenco completo delle azioni di trasmissione di sistema, consulta il file BROADCAST_ACTIONS.TXT nell'SDK Android. A ogni azione di trasmissione è associato un campo costante. Ad esempio, il valore della costante ACTION_AIRPLANE_MODE_CHANGED è android.intent.action.AIRPLANE_MODE. La documentazione per ogni azione di trasmissione è disponibile nel relativo campo della costante associato.

Modifiche alle trasmissioni di sistema

La piattaforma Android si evolve, pertanto cambia periodicamente il comportamento delle trasmissioni di sistema. Per supportare tutte le versioni di Android, tieni presente le modifiche che seguono.

Android 14

Mentre le app sono in stato nella cache, la pubblicazione delle trasmissioni è ottimizzata per l'integrità del sistema. Ad esempio, le trasmissioni di sistema meno importanti, come ACTION_SCREEN_ON, vengono differite mentre l'app è in uno stato memorizzato nella cache. Quando l'app passa dallo stato memorizzato nella cache a un ciclo di vita del processo attivo, il sistema invia eventuali trasmissioni differite.

Le trasmissioni importanti dichiarate nel file manifest rimuovono temporaneamente le app dallo stato memorizzato nella cache per la distribuzione.

Android 9

A partire da Android 9 (livello API 28), la trasmissione di NETWORK_STATE_CHANGED_ACTION non riceve informazioni sulla posizione dell'utente o dati che consentono l'identificazione personale.

Inoltre, se l'app è installata su un dispositivo con Android 9 o versioni successive, le trasmissioni di sistema dalla rete Wi-Fi non contengono SSID, BSSID, informazioni sulla connessione o risultati dell'analisi. Per ottenere queste informazioni, chiama invece il numero getConnectionInfo().

Android 8.0

A partire da Android 8.0 (livello API 26), il sistema impone ulteriori limitazioni sui ricevitori dichiarati tramite manifest.

Se la tua app ha come target Android 8.0 o versioni successive, non puoi utilizzare il file manifest per dichiarare un ricevitore per la maggior parte delle trasmissioni implicite (trasmissioni che non hanno come target la tua app specifica). Puoi comunque utilizzare un ricevitore registrato nel contesto quando l'utente utilizza attivamente la tua app.

Android 7.0

Android 7.0 (livello API 24) e versioni successive non inviano le seguenti trasmissioni di sistema:

Inoltre, le app destinate ad Android 7.0 e versioni successive devono registrare la trasmissione CONNECTIVITY_ACTION utilizzando registerReceiver(BroadcastReceiver, IntentFilter). La dichiarazione di un ricevitore nel manifest non funziona.

Ricezione di annunci

Le app possono ricevere trasmissioni in due modi: tramite ricevitori dichiarati tramite manifest e ricevitori registrati in base al contesto.

Ricevitori dichiarati da manifest

Se dichiari un ricevitore di broadcast nel file manifest, il sistema avvia la tua app (se l'app non è già in esecuzione) quando viene inviato la trasmissione.

Per dichiarare un ricevitore di trasmissione nel file manifest, procedi nel seguente modo:

  1. Specifica l'elemento <receiver> nel manifest dell'app.

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

    I filtri per intent specificano le azioni di trasmissione a cui il ricevitore si abbona.

  2. Sottoclasse BroadcastReceiver e implementa onReceive(Context, Intent). Il ricevitore della trasmissione nell'esempio seguente registra e visualizza i contenuti della trasmissione:

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

    Per abilitare l'associazione di viste, configura viewBinding nel file build.gradle a livello di modulo.

Il gestore di pacchetti di sistema registra il destinatario quando l'app viene installata. Il destinatario diventa quindi un punto di ingresso separato nella tua app, il che significa che il sistema può avviare l'app e trasmettere la trasmissione se l'app non è attualmente in esecuzione.

Il sistema crea un nuovo oggetto componente BroadcastReceiver per gestire ogni trasmissione che riceve. Questo oggetto è valido solo per la durata della chiamata a onReceive(Context, Intent). Quando il codice viene restituito da questo metodo, il sistema considera il componente non più attivo.

Ricevitori registrati in base al contesto

I ricevitori registrati in base al contesto ricevono trasmissioni purché il loro contesto di registrazione sia valido. Ad esempio, se ti registri in un contesto Activity, ricevi trasmissioni a condizione che l'attività non venga eliminata. Se ti registri con il contesto dell'applicazione, riceverai broadcast fino a quando l'app è in esecuzione.

Per registrare un ricevitore con un contesto, segui questi passaggi:

  1. Nel file di build a livello di modulo dell'app, includi la versione 1.9.0 o successive della libreria AndroidX Core:

    Trendy

    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. Crea un'istanza di BroadcastReceiver:

    Kotlin

    val br: BroadcastReceiver = MyBroadcastReceiver()
    

    Java

    BroadcastReceiver br = new MyBroadcastReceiver();
    
  3. Crea un'istanza di IntentFilter:

    Kotlin

    val filter = IntentFilter(APP_SPECIFIC_BROADCAST)
    

    Java

    IntentFilter filter = new IntentFilter(APP_SPECIFIC_BROADCAST);
    
  4. Scegli se il ricevitore della trasmissione deve essere esportato e visibile ad altre app sul dispositivo. Se questo ricevitore è in ascolto di trasmissioni inviate dal sistema o da altre app (anche da altre app di tua proprietà), utilizza il flag RECEIVER_EXPORTED. Se invece questo ricevitore ascolta solo le trasmissioni inviate dalla tua app, usa il flag 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. Registra il destinatario chiamando registerReceiver():

    Kotlin

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

    Java

    ContextCompat.registerReceiver(context, br, filter, receiverFlags);
    
  6. Per interrompere la ricezione di annunci, chiama il numero unregisterReceiver(android.content.BroadcastReceiver). Assicurati di annullare la registrazione del destinatario quando non ne hai più bisogno o quando il contesto non è più valido.

    Fai attenzione a dove registri e annulli la registrazione del ricevitore. Ad esempio, se registri un ricevitore in onCreate(Bundle) utilizzando il contesto dell'attività, dovresti annullare la registrazione in onDestroy() per evitare che il ricevitore venga divulgato al di fuori del contesto dell'attività. Se registri un ricevitore in onResume(), devi annullarne la registrazione in onPause() per evitare di registrarlo più volte (se non vuoi ricevere trasmissioni quando il dispositivo è in pausa, altrimenti potresti ridurre il sovraccarico del sistema). Non annullare la registrazione in onSaveInstanceState(Bundle), perché questa operazione non viene richiamata se l'utente torna nell'elenco della cronologia.

Effetti sullo stato del processo

Il fatto che BroadcastReceiver sia operativo o meno sul suo processo contenuto, il che può alterare la probabilità di eliminazione del sistema. Un processo in primo piano esegue il metodo onReceive() di un destinatario. Il sistema esegue il processo, tranne in caso di estrema pressione della memoria.

BroadcastRicevir viene disattivato dopo il giorno onReceive(). Il processo host del destinatario è significativo solo quanto i componenti dell'app. Se questo processo ospita solo un ricevitore dichiarato da manifest (ricorrenza frequente per le app con cui l'utente non ha mai o non ha interagito di recente), il sistema potrebbe terminarlo dopo il giorno onReceive() per rendere disponibili le risorse per altri processi più critici.

Di conseguenza, i ricevitori di broadcast non devono avviare thread in background a lunga esecuzione. Il sistema può interrompere il processo in qualsiasi momento dopo il giorno onReceive() per recuperare memoria, interrompendo il thread creato. Per mantenere attivo il processo, programma una JobService dal ricevitore utilizzando JobScheduler, in modo che il sistema sappia che il processo è ancora in esecuzione. Panoramica del lavoro in background fornisce ulteriori dettagli.

Invio di annunci in corso...

Android offre tre modi con cui le app possono inviare annunci:

  • Il metodo sendOrderedBroadcast(Intent, String) invia trasmissioni a un ricevitore alla volta. Mentre ogni ricevitore viene eseguito a turno, può propagare un risultato al ricevitore successivo oppure può interrompere completamente la trasmissione in modo che non venga passato ad altri ricevitori. I ricevitori dell'ordine in esecuzione possono essere controllati con l'attributo android:Priority del filtro per intent corrispondente. I ricevitori con la stessa priorità verranno eseguiti in ordine arbitrario.
  • Il metodo sendBroadcast(Intent) invia trasmissioni a tutti i destinatari in un ordine non definito. si chiama trasmissione normale. Questa operazione è più efficiente, ma significa che i ricevitori non possono leggere i risultati di altri ricevitori, propagare i dati ricevuti dalla trasmissione o interrompere la trasmissione.

Lo snippet di codice riportato di seguito mostra come inviare una trasmissione creando un intent e chiamando 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);

Il messaggio broadcast è aggregato in un oggetto Intent. La stringa di azione dell'intent deve fornire la sintassi del nome del pacchetto Java dell'app e identificare in modo univoco l'evento di trasmissione. Puoi allegare ulteriori informazioni all'intent con putExtra(String, Bundle). Puoi anche limitare una trasmissione a un insieme di app nella stessa organizzazione chiamando setPackage(String) tramite l'intent.

Limitazione delle trasmissioni con autorizzazioni

Le autorizzazioni ti consentono di limitare le trasmissioni all'insieme di app che dispongono di determinate autorizzazioni. Puoi applicare limitazioni al mittente o al destinatario di una trasmissione.

Invio con autorizzazioni

Quando chiami sendBroadcast(Intent, String) o sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle), puoi specificare un parametro di autorizzazione. Solo i destinatari che hanno richiesto l'autorizzazione con il tag nel file manifest (e in seguito a cui è stata concessa l'autorizzazione se è pericolosa) possono ricevere la trasmissione. Ad esempio, il seguente codice invia una trasmissione:

Kotlin

sendBroadcast(Intent(BluetoothDevice.ACTION_FOUND),
              Manifest.permission.BLUETOOTH_CONNECT)

Java

sendBroadcast(new Intent(BluetoothDevice.ACTION_FOUND),
              Manifest.permission.BLUETOOTH_CONNECT)

Per ricevere la trasmissione, l'app ricevente deve richiedere l'autorizzazione, come mostrato di seguito:

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

Puoi specificare un'autorizzazione di sistema esistente, come BLUETOOTH_CONNECT o definire un'autorizzazione personalizzata con l'elemento <permission>. Per informazioni su autorizzazioni e sicurezza in generale, consulta Autorizzazioni di sistema.

Ricezione con autorizzazioni

Se specifichi un parametro di autorizzazione durante la registrazione di un ricevitore (con registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) o nel tag <receiver> nel file manifest), solo le emittenti che hanno richiesto l'autorizzazione con il tag <uses-permission> nel file manifest (e che successivamente hanno ottenuto l'autorizzazione se è pericolosa) possono inviare un intent al destinatario.

Ad esempio, supponiamo che l'app di destinazione abbia un ricevitore dichiarato da manifest, come mostrato di seguito:

<receiver android:name=".MyBroadcastReceiver"
          android:permission="android.permission.BLUETOOTH_CONNECT">
    <intent-filter>
        <action android:name="android.intent.action.ACTION_FOUND"/>
    </intent-filter>
</receiver>

Oppure l'app ricevente ha un ricevitore registrato in base al contesto, come mostrato di seguito:

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

Per poter inviare annunci a questi destinatari, l'app di invio deve quindi richiedere l'autorizzazione, come mostrato di seguito:

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

Considerazioni sulla sicurezza e best practice

Ecco alcune considerazioni sulla sicurezza e best practice per l'invio e la ricezione di broadcast:

  • Se molte app si sono registrate per ricevere la stessa trasmissione nel file manifest, ciò può causare il lancio di molte app da parte del sistema, con un impatto sostanziale sulle prestazioni del dispositivo e sull'esperienza utente. Per evitare che ciò accada, preferisci utilizzare la registrazione del contesto anziché la dichiarazione del file manifest. A volte, il sistema Android impone l'uso di ricevitori registrati in base al contesto. Ad esempio, la trasmissione CONNECTIVITY_ACTION viene inviata solo a destinatari registrati in base al contesto.

  • Non trasmettere informazioni sensibili utilizzando un intent implicito. Le informazioni possono essere lette da qualsiasi app che si registri per ricevere la trasmissione. Esistono tre modi per controllare chi può ricevere gli annunci:

    • Puoi specificare un'autorizzazione quando invii una trasmissione.
    • In Android 4.0 e versioni successive, puoi specificare un pacchetto con setPackage(String) quando invii una trasmissione. Il sistema limita la trasmissione all'insieme di app che corrispondono al pacchetto.
  • Quando registri un destinatario, qualsiasi app può inviare trasmissioni potenzialmente dannose al destinatario. Esistono diversi modi per limitare le trasmissioni ricevute dalla tua app:

    • Puoi specificare un'autorizzazione durante la registrazione di un ricevitore di trasmissione.
    • Per i ricevitori dichiarati da manifest, puoi impostare l'attributo android:exported su "false" nel manifest. Il destinatario non riceve trasmissioni da sorgenti esterne all'app.
  • Lo spazio dei nomi per le azioni di trasmissione è globale. Assicurati che i nomi delle azioni e altre stringhe siano scritti in uno spazio dei nomi di tua proprietà, altrimenti potresti entrare inavvertitamente in conflitto con altre app.

  • Poiché il metodo onReceive(Context, Intent) di un destinatario viene eseguito sul thread principale, dovrebbe essere eseguito e restituito rapidamente. Se devi eseguire operazioni a lunga esecuzione, fai attenzione alla generazione di thread o all'avvio di servizi in background, poiché il sistema può terminare l'intero processo dopo il ritorno di onReceive(). Per saperne di più, consulta Effetto sullo stato del processo. Per eseguire operazioni a lunga esecuzione, ti consigliamo di:

    • Chiamata a goAsync() nel metodo onReceive() del ricevitore e passaggio di BroadcastReceiver.PendingResult a un thread in background. In questo modo la trasmissione rimane attiva dopo il ritorno da onReceive(). Tuttavia, anche con questo approccio il sistema si aspetta di terminare la trasmissione molto rapidamente (inferiore a 10 secondi). Ti permette di spostare il lavoro su un altro thread per evitare di causare problemi al thread principale.
    • Pianificazione di un lavoro con JobScheduler. Per ulteriori informazioni, consulta Pianificazione intelligente dei job.
  • Non avviare attività da ricevitori perché l'esperienza utente è scioccante, specialmente se sono presenti più ricevitori. Potresti, invece, visualizzare una notifica.