Creare un widget avanzato

In questa pagina vengono spiegate le pratiche consigliate per creare un widget più avanzato e offrire un'esperienza utente migliore.

Ottimizzazioni per l'aggiornamento dei contenuti del widget

L'aggiornamento del contenuto del widget può essere costoso dal punto di vista del calcolo. Per ridurre il consumo della batteria, ottimizza il tipo di aggiornamento, la frequenza e la tempistica.

Tipi di aggiornamenti dei widget

Esistono tre modi per aggiornare un widget: un aggiornamento completo, un aggiornamento parziale e, nel caso di un widget raccolta, un aggiornamento dei dati. Ognuno ha costi di calcolo e implicazioni diverse.

Di seguito vengono descritti tutti i tipi di aggiornamento e vengono forniti snippet di codice per ciascuno.

  • Aggiornamento completo: chiama AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews) per aggiornare completamente il widget. Questo sostituisce il precedente RemoteViews con un nuovo RemoteViews. Questo è l'aggiornamento più costoso dal punto di vista del calcolo.

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
    setTextViewText(R.id.textview_widget_layout1, "Updated text1")
    setTextViewText(R.id.textview_widget_layout2, "Updated text2")
    }
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
    remoteViews.setTextViewText(R.id.textview_widget_layout1, "Updated text1");
    remoteViews.setTextViewText(R.id.textview_widget_layout2, "Updated text2");
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
    
  • Aggiornamento parziale: chiama AppWidgetManager.partiallyUpdateAppWidget per aggiornare parti del widget. Il nuovo RemoteViews viene unito a RemoteViews fornito in precedenza. Questo metodo viene ignorato se un widget non riceve almeno un aggiornamento completo tramite updateAppWidget(int[], RemoteViews).

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
    setTextViewText(R.id.textview_widget_layout, "Updated text")
    }
    appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews)
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
    remoteViews.setTextViewText(R.id.textview_widget_layout, "Updated text");
    appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews);
    
  • Aggiornamento dei dati della raccolta: chiama AppWidgetManager.notifyAppWidgetViewDataChanged per invalidare i dati di una visualizzazione della raccolta nel widget. Questa operazione attiva RemoteViewsFactory.onDataSetChanged. Nel frattempo, nel widget vengono visualizzati i dati precedenti. Con questo metodo puoi eseguire in sicurezza attività costose in modo sincrono.

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview)
    
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview);
    

Puoi chiamare questi metodi da qualsiasi punto della tua app, a condizione che l'app abbia lo stesso UID della classe AppWidgetProvider corrispondente.

Stabilire la frequenza di aggiornamento di un widget

I widget vengono aggiornati periodicamente in base al valore fornito per l'attributo updatePeriodMillis. Il widget può essere aggiornato in risposta all'interazione dell'utente, agli aggiornamenti della trasmissione o a entrambi.

Aggiorna periodicamente

Puoi controllare la frequenza degli aggiornamenti periodici specificando un valore per AppWidgetProviderInfo.updatePeriodMillis nel codice XML appwidget-provider. Ogni aggiornamento attiva il metodo AppWidgetProvider.onUpdate(), che consente di inserire il codice per aggiornare il widget. Tuttavia, prendi in considerazione le alternative per gli aggiornamenti del ricevitore di trasmissione descritte nella sezione seguente se il widget deve caricare i dati in modo asincrono o se l'aggiornamento richiede più di 10 secondi, perché dopo 10 secondi il sistema considera BroadcastReceiver non risponde.

updatePeriodMillis non supporta i valori inferiori a 30 minuti. Tuttavia, se vuoi disabilitare gli aggiornamenti periodici, puoi specificare 0.

Puoi consentire agli utenti di modificare la frequenza degli aggiornamenti di una configurazione. Ad esempio, potrebbero volere che un codice di borsa si aggiorni ogni 15 minuti o solo quattro volte al giorno. In questo caso, imposta updatePeriodMillis su 0 e usa invece WorkManager.

Aggiornamento in risposta a un'interazione di un utente

Ecco alcuni modi consigliati per aggiornare il widget in base all'interazione dell'utente:

  • Da un'attività dell'app: chiama direttamente AppWidgetManager.updateAppWidget in risposta a un'interazione utente, ad esempio il tocco dell'utente.

  • Dalle interazioni remote, ad esempio tramite una notifica o un widget dell'app: crea un PendingIntent, quindi aggiorna il widget dai Activity, Broadcast o Service richiamati. Puoi scegliere la priorità che preferisci. Ad esempio, se selezioni un Broadcast per PendingIntent, puoi scegliere una trasmissione in primo piano per assegnare la priorità BroadcastReceiver.

Aggiornamento in risposta a un evento di trasmissione

Un esempio di evento di trasmissione che richiede l'aggiornamento di un widget è quando l'utente scatta una foto. In questo caso, vuoi aggiornare il widget quando viene rilevata una nuova foto.

Puoi pianificare un job con JobScheduler e specificare una trasmissione come trigger utilizzando il metodo JobInfo.Builder.addTriggerContentUri.

Puoi anche registrare un BroadcastReceiver per la trasmissione, ad esempio l'ascolto di ACTION_LOCALE_CHANGED. Tuttavia, poiché questa operazione consuma risorse del dispositivo, usala con attenzione e ascolta solo la trasmissione specifica. Con l'introduzione delle limitazioni di trasmissione in Android 7.0 (livello API 24) e Android 8.0 (livello API 26), le app non possono registrare trasmissioni implicite nei file manifest, con alcune eccezioni.

Considerazioni sull'aggiornamento di un widget da un BroadcastRicevir

Se il widget viene aggiornato da BroadcastReceiver, che include AppWidgetProvider, tieni presente le seguenti considerazioni relative alla durata e alla priorità dell'aggiornamento di un widget.

Durata dell'aggiornamento

Come regola, il sistema consente ai ricevitori di broadcast, che in genere vengono eseguiti nel thread principale dell'app, di essere eseguiti per un massimo di 10 secondi prima di considerarli non adattabili e di generare un errore L'applicazione non risponde (ANR). Se l'aggiornamento del widget richiede più tempo, prendi in considerazione le seguenti alternative:

  • Pianifica un'attività utilizzando WorkManager.

  • Concedi più tempo al destinatario con il metodo goAsync. In questo modo i ricevitori possono essere eseguiti per 30 secondi.

Per ulteriori informazioni, consulta Considerazioni e best practice sulla sicurezza.

Priorità dell'aggiornamento

Per impostazione predefinita, le trasmissioni, incluse quelle effettuate utilizzando AppWidgetProvider.onUpdate, vengono eseguite come processi in background. Ciò significa che le risorse di sistema sovraccaricate possono causare un ritardo nella chiamata del ricevitore della trasmissione. Per dare priorità alla trasmissione, impostala come processo in primo piano.

Ad esempio, aggiungi il flag Intent.FLAG_RECEIVER_FOREGROUND al Intent trasmesso a PendingIntent.getBroadcast quando l'utente tocca una determinata parte del widget.

Crea anteprime accurate che includono elementi dinamici

Figura 1: anteprima del widget che non mostra elementi dell'elenco.

Questa sezione illustra l'approccio consigliato per visualizzare più elementi nell'anteprima di un widget per un widget con una visualizzazione raccolta, ovvero un widget che utilizza ListView, GridView o StackView.

Se il widget utilizza una di queste visualizzazioni, la creazione di un'anteprima scalabile fornendo direttamente il layout effettivo del widget riduce l'esperienza quando nell'anteprima del widget non vengono visualizzati elementi. Questo accade perché i dati della visualizzazione raccolta sono impostati in modo dinamico in fase di runtime e sono simili all'immagine mostrata nella Figura 1.

Per fare in modo che le anteprime dei widget con visualizzazioni raccolta vengano visualizzate correttamente nel selettore di widget, ti consigliamo di mantenere un file di layout separato designato solo per l'anteprima. Questo file di layout separato include l'effettivo layout del widget e una visualizzazione della raccolta segnaposto con elementi falsi. Ad esempio, puoi simulare un elemento ListView fornendo un segnaposto LinearLayout con diverse voci di elenco fittizie.

Per illustrare un esempio di ListView, inizia con un file di layout separato:

// res/layout/widget_preview.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="@drawable/widget_background"
   android:orientation="vertical">

    // Include the actual widget layout that contains ListView.
    <include
        layout="@layout/widget_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    // The number of fake items you include depends on the values you provide
    // for minHeight or targetCellHeight in the AppWidgetProviderInfo
    // definition.

    <TextView android:text="@string/fake_item1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

    <TextView android:text="@string/fake_item2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

</LinearLayout>

Specifica il file di layout di anteprima quando fornisci l'attributo previewLayout dei metadati AppWidgetProviderInfo. Devi comunque specificare il layout effettivo del widget per l'attributo initialLayout e utilizzare quello effettivo durante la creazione di un RemoteViews in fase di runtime.

<appwidget-provider
    previewLayout="@layout/widget_previe"
    initialLayout="@layout/widget_view" />

Voci di elenco complessi

L'esempio nella sezione precedente fornisce voci di elenco fittizie, perché le voci di elenco sono oggetti TextView. Può essere più complesso fornire articoli falsi se hanno layout complessi.

Considera un elemento dell'elenco definito in widget_list_item.xml e composto da due oggetti TextView:

<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <TextView android:id="@id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_title" />

    <TextView android:id="@id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_content" />
</LinearLayout>

Per fornire voci fittizie dell'elenco, puoi includere il layout più volte, ma in questo modo ogni elemento dell'elenco sarà identico. Per fornire voci di elenco univoche, segui questi passaggi:

  1. Crea un insieme di attributi per i valori di testo:

    <resources>
        <attr name="widgetTitle" format="string" />
        <attr name="widgetContent" format="string" />
    </resources>
    
  2. Utilizza questi attributi per impostare il testo:

    <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
        <TextView android:id="@id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetTitle" />
    
        <TextView android:id="@id/content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetContent" />
    </LinearLayout>
    
  3. Crea tutti gli stili necessari per l'anteprima. Ridefinisci i valori in ciascun stile:

    <resources>
    
        <style name="Theme.Widget.ListItem">
            <item name="widgetTitle"></item>
            <item name="widgetContent"></item>
        </style>
        <style name="Theme.Widget.ListItem.Preview1">
            <item name="widgetTitle">Fake Title 1</item>
            <item name="widgetContent">Fake content 1</item>
        </style>
        <style name="Theme.Widget.ListItem.Preview2">
            <item name="widgetTitle">Fake title 2</item>
            <item name="widgetContent">Fake content 2</item>
        </style>
    
    </resources>
    
  4. Applica gli stili agli elementi falsi nel layout di anteprima:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" ...>
    
        <include layout="@layout/widget_view" ... />
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview1" />
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview2" />
    
    </LinearLayout>