Threading Android: tutto ciò che devi sapere
Pubblicato: 2022-03-11Ogni sviluppatore Android, prima o poi, deve gestire i thread nella propria applicazione.
Quando un'applicazione viene avviata in Android, crea il primo thread di esecuzione, noto come thread "principale". Il thread principale è responsabile dell'invio di eventi ai widget dell'interfaccia utente appropriati e della comunicazione con i componenti del toolkit dell'interfaccia utente Android.
Per mantenere la tua applicazione reattiva, è essenziale evitare di utilizzare il thread principale per eseguire qualsiasi operazione che potrebbe finire per mantenerla bloccata.
Le operazioni di rete e le chiamate al database, così come il caricamento di alcuni componenti, sono esempi comuni di operazioni da evitare nel thread principale. Quando vengono chiamati nel thread principale, vengono chiamati in modo sincrono, il che significa che l'interfaccia utente non risponderà completamente fino al completamento dell'operazione. Per questo motivo, di solito vengono eseguiti in thread separati, il che evita così di bloccare l'interfaccia utente durante l'esecuzione (ovvero, vengono eseguiti in modo asincrono dall'interfaccia utente).
Android offre molti modi per creare e gestire i thread ed esistono molte librerie di terze parti che rendono la gestione dei thread molto più piacevole. Tuttavia, con così tanti approcci diversi a portata di mano, scegliere quello giusto può essere piuttosto fonte di confusione.
In questo articolo imparerai alcuni scenari comuni nello sviluppo di Android in cui il threading diventa essenziale e alcune semplici soluzioni che possono essere applicate a tali scenari e altro ancora.
Threading in Android
In Android, puoi classificare tutti i componenti di threading in due categorie di base:
- Thread collegati a un'attività/frammento: questi thread sono legati al ciclo di vita dell'attività/frammento e vengono terminati non appena l'attività/frammento viene distrutto.
- Thread che non sono collegati ad alcuna attività/frammento: questi thread possono continuare a essere eseguiti oltre la durata dell'attività/frammento (se presente) da cui sono stati generati.
Componenti di filettatura che si attaccano a un'attività/frammento
AsyncTask
AsyncTask
è il componente Android più basilare per il threading. È semplice da usare e può essere utile per scenari di base.
Esempio di utilizzo:
public class ExampleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MyTask().execute(url); } private class MyTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String url = params[0]; return doSomeWork(url); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); // do something with result } } }
AsyncTask
, tuttavia, non è all'altezza se è necessario che l'attività posticipata venga eseguita oltre la durata dell'attività/frammento. Vale la pena notare che anche qualcosa di semplice come la rotazione dello schermo può causare la distruzione dell'attività.
Caricatori
I caricatori sono la soluzione al problema di cui sopra. I caricatori possono interrompersi automaticamente quando l'attività viene distrutta e possono anche riavviarsi dopo che l'attività è stata ricreata.
Esistono principalmente due tipi di caricatori: AsyncTaskLoader
e CursorLoader
. Imparerai di più su CursorLoader
più avanti in questo articolo.
AsyncTaskLoader
è simile ad AsyncTask
, ma un po' più complicato.
Esempio di utilizzo:
public class ExampleActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLoaderManager().initLoader(1, null, new MyLoaderCallbacks()); } private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new MyLoader(ExampleActivity.this); } @Override public void onLoadFinished(Loader loader, Object data) { } @Override public void onLoaderReset(Loader loader) { } } private class MyLoader extends AsyncTaskLoader { public MyLoader(Context context) { super(context); } @Override public Object loadInBackground() { return someWorkToDo(); } } }
Componenti di threading che non si attaccano a un'attività/frammento
Servizio
Service
è un componente utile per eseguire operazioni lunghe (o potenzialmente lunghe) senza alcuna interfaccia utente.
Service
viene eseguito nel thread principale del processo di hosting; il servizio non crea il proprio thread e non viene eseguito in un processo separato a meno che non venga specificato diversamente.
Esempio di utilizzo:
public class ExampleService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { doSomeLongProccesingWork(); stopSelf(); return START_NOT_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
Con Service
, è tua responsabilità fermarlo quando il suo lavoro è completo chiamando il stopSelf()
o stopService()
.
IntentoService
Come Service
, IntentService
viene eseguito su un thread separato e si interrompe automaticamente dopo aver completato il proprio lavoro.
IntentService
viene in genere usato per attività brevi che non devono essere collegate a nessuna interfaccia utente.
Esempio di utilizzo:
public class ExampleService extends IntentService { public ExampleService() { super("ExampleService"); } @Override protected void onHandleIntent(Intent intent) { doSomeShortWork(); } }
Sette modelli di filettatura in Android
Caso d'uso n. 1: fare una richiesta in rete senza richiedere una risposta dal server
A volte potresti voler inviare una richiesta API a un server senza doversi preoccupare della sua risposta. Ad esempio, potresti inviare un token di registrazione push al back-end della tua applicazione.
Poiché ciò comporta l'esecuzione di una richiesta sulla rete, dovresti farlo da un thread diverso dal thread principale.
Opzione 1: AsyncTask o caricatori
Puoi usare AsyncTask
o caricatori per effettuare la chiamata e funzionerà.
Tuttavia, AsyncTask
e caricatori dipendono entrambi dal ciclo di vita dell'attività. Ciò significa che dovrai attendere l'esecuzione della chiamata e cercare di impedire all'utente di abbandonare l'attività, oppure sperare che venga eseguita prima che l'attività venga distrutta.
Opzione 2: servizio
Service
potrebbe adattarsi meglio a questo caso d'uso poiché non è collegato ad alcuna attività. Potrà quindi continuare con la chiamata di rete anche dopo la distruzione dell'attività. Inoltre, poiché la risposta del server non è necessaria, anche qui un servizio non sarebbe limitante.
Tuttavia, poiché un servizio inizierà a essere eseguito sul thread dell'interfaccia utente, dovrai comunque gestire il threading da solo. Sarà inoltre necessario assicurarsi che il servizio venga interrotto una volta completata la chiamata di rete.
Ciò richiederebbe uno sforzo maggiore di quello necessario per un'azione così semplice.
Opzione 3: IntentService
Questa, secondo me, sarebbe l'opzione migliore.
Poiché IntentService
non si collega ad alcuna attività e viene eseguito su un thread non dell'interfaccia utente, soddisfa perfettamente le nostre esigenze qui. Inoltre, IntentService
si interrompe automaticamente, quindi non è nemmeno necessario gestirlo manualmente.
Caso d'uso n. 2: effettuare una chiamata di rete e ottenere la risposta dal server
Questo caso d'uso è probabilmente un po' più comune. Ad esempio, potresti voler richiamare un'API nel back-end e utilizzare la sua risposta per popolare i campi sullo schermo.
Opzione 1: Servizio o IntentService
Sebbene un Service
o un IntentService
andati bene per il caso d'uso precedente, usarli qui non sarebbe una buona idea. Cercare di ottenere i dati da un Service
o da un IntentService
nel thread dell'interfaccia utente principale renderebbe le cose molto complesse.
Opzione 2: AsyncTask o caricatori
A prima vista, AsyncTask
o caricatori sembrerebbero essere la soluzione ovvia qui. Sono facili da usare: semplici e diretti.
Tuttavia, quando usi AsyncTask
o caricatori, noterai che è necessario scrivere del codice standard. Inoltre, la gestione degli errori diventa un compito importante con questi componenti. Anche con una semplice chiamata di rete, è necessario essere consapevoli delle potenziali eccezioni, rilevarle e agire di conseguenza. Questo ci costringe a racchiudere la nostra risposta in una classe personalizzata contenente i dati, con possibili informazioni sull'errore e un flag indica se l'azione ha avuto successo o meno.
È un bel po' di lavoro da fare per ogni singola chiamata. Fortunatamente, ora è disponibile una soluzione molto migliore e più semplice: RxJava.
Opzione 3: RxJava
Potresti aver sentito parlare di RxJava, la libreria sviluppata da Netflix. È quasi magico in Java.
RxAndroid ti consente di utilizzare RxJava in Android e rende la gestione delle attività asincrone un gioco da ragazzi. Puoi saperne di più su RxJava su Android qui.
RxJava fornisce due componenti: Observer
e Subscriber
.
Un osservatore è un componente che contiene un'azione. Esegue tale azione e restituisce il risultato se riesce o un errore se fallisce.
Un abbonato , invece, è un componente che può ricevere il risultato (o l'errore) da un osservabile, iscrivendosi ad esso.
Con RxJava, crei prima un osservabile:
Observable.create((ObservableOnSubscribe<Data>) e -> { Data data = mRestApi.getData(); e.onNext(data); })
Una volta che l'osservabile è stato creato, puoi iscriverti ad esso.
Con la libreria RxAndroid, puoi controllare il thread in cui vuoi eseguire l'azione nell'osservabile e il thread in cui vuoi ottenere la risposta (cioè il risultato o l'errore).
Concateni sull'osservabile con queste due funzioni:
.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()
Gli scheduler sono componenti che eseguono l'azione in un determinato thread. AndroidSchedulers.mainThread()
è lo scheduler associato al thread principale.
Dato che la nostra chiamata API è mRestApi.getData()
e restituisce un oggetto Data
, la chiamata di base può assomigliare a questa:
Observable.create((ObservableOnSubscribe<Data>) e -> { try { Data data = mRestApi.getData(); e.onNext(data); } catch (Exception ex) { e.onError(ex); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(match -> Log.i(“rest api, "success"), throwable -> Log.e(“rest api, "error: %s" + throwable.getMessage()));
Senza nemmeno entrare negli altri vantaggi dell'utilizzo di RxJava, puoi già vedere come RxJava ci consente di scrivere codice più maturo astraendo la complessità del threading.
Caso d'uso n. 3: concatenare le chiamate di rete
Per le chiamate di rete che devono essere eseguite in sequenza (ovvero, dove ogni operazione dipende dalla risposta/risultato dell'operazione precedente), è necessario prestare particolare attenzione alla generazione del codice spaghetti.
Ad esempio, potresti dover effettuare una chiamata API con un token che devi recuperare prima tramite un'altra chiamata API.
Opzione 1: AsyncTask o caricatori
L'uso AsyncTask
o dei caricatori porterà quasi sicuramente al codice spaghetti. La funzionalità complessiva sarà difficile da ottenere correttamente e richiederà un'enorme quantità di codice standard ridondante durante tutto il progetto.
Opzione 2: RxJava usando flatMap
In RxJava, l'operatore flatMap
prende un valore emesso dalla sorgente osservabile e restituisce un altro osservabile. Puoi creare un osservabile e quindi crearne un altro osservabile usando il valore emesso dal primo, che sostanzialmente li concatenerà.
Passaggio 1. Crea l'osservabile che recupera il token:
public Observable<String> getTokenObservable() { return Observable.create(subscriber -> { try { String token = mRestApi.getToken(); subscriber.onNext(token); } catch (IOException e) { subscriber.onError(e); } }); }
Passaggio 2. Crea l'osservabile che ottiene i dati utilizzando il token:
public Observable<String> getDataObservable(String token) { return Observable.create(subscriber -> { try { Data data = mRestApi.getData(token); subscriber.onNext(data); } catch (IOException e) { subscriber.onError(e); } }); }
Passaggio 3. Concatena i due osservabili e iscriviti:
getTokenObservable() .flatMap(new Function<String, Observable<Data>>() { @Override public Observable<Data> apply(String token) throws Exception { return getDataObservable(token); } }) .subscribe(data -> { doSomethingWithData(data) }, error -> handleError(e));
Si noti che l'uso di questo approccio non è limitato alle chiamate di rete; può funzionare con qualsiasi insieme di azioni che deve essere eseguito in sequenza ma su thread separati.
Tutti i casi d'uso sopra sono abbastanza semplici. Il passaggio da un thread all'altro è avvenuto solo dopo che ciascuno ha terminato la propria attività. Anche scenari più avanzati, ad esempio in cui due o più thread devono comunicare attivamente tra loro, possono essere supportati da questo approccio.
Caso d'uso n. 4: comunicare con il thread dell'interfaccia utente da un altro thread
Considera uno scenario in cui desideri caricare un file e aggiornare l'interfaccia utente una volta completata.
Poiché il caricamento di un file può richiedere molto tempo, non è necessario far attendere l'utente. Potresti usare un servizio, e probabilmente IntentService
, per implementare la funzionalità qui.
In questo caso, tuttavia, la sfida più grande è riuscire a invocare un metodo sul thread dell'interfaccia utente dopo che il caricamento del file (che è stato eseguito in un thread separato) è stato completato.

Opzione 1: RxJava all'interno del servizio
RxJava, da solo o all'interno di un IntentService
, potrebbe non essere l'ideale. Dovrai usare un meccanismo basato su callback quando ti abboni a un Observable
e IntentService
è costruito per eseguire semplici chiamate sincrone, non callback.
D'altra parte, con un Service
, dovrai interrompere manualmente il servizio, che richiede più lavoro.
Opzione 2: BroadcastReceiver
Android fornisce questo componente, che può ascoltare eventi globali (ad es. eventi batteria, eventi di rete, ecc.) così come eventi personalizzati. Puoi utilizzare questo componente per creare un evento personalizzato che viene attivato al termine del caricamento.
A tale scopo, è necessario creare una classe personalizzata che estenda BroadcastReceiver
, registrarla nel manifest e utilizzare Intent
e IntentFilter
per creare l'evento personalizzato. Per attivare l'evento, avrai bisogno del metodo sendBroadcast
.
Manifesto:
<receiver android:name="UploadReceiver"> <intent-filter> <action android:name="com.example.upload"> </action> </intent-filter> </receiver>
Ricevitore:
public class UploadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getBoolean(“success”, false) { Activity activity = (Activity)context; activity.updateUI(); } }
Mittente:
Intent intent = new Intent(); intent.setAction("com.example.upload"); sendBroadcast(intent);
Questo approccio è un'opzione praticabile. Ma come hai notato, comporta del lavoro e troppe trasmissioni possono rallentare le cose.
Opzione 3: utilizzo del gestore
Un Handler
è un componente che può essere allegato a un thread e quindi creato per eseguire alcune azioni su quel thread tramite messaggi semplici o attività Runnable
. Funziona in combinazione con un altro componente, Looper
, che è responsabile dell'elaborazione dei messaggi in un particolare thread.
Quando viene creato un Handler
, può ottenere un oggetto Looper
nel costruttore, che indica a quale thread è collegato il gestore. Se si desidera utilizzare un gestore collegato al thread principale, è necessario utilizzare il looper associato al thread principale chiamando Looper.getMainLooper()
.
In questo caso, per aggiornare l'interfaccia utente da un thread in background, puoi creare un gestore allegato al thread dell'interfaccia utente e quindi pubblicare un'azione come Runnable
:
Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // update the ui from here } });
Questo approccio è molto migliore del primo, ma c'è un modo ancora più semplice per farlo...
Opzione 3: utilizzo di EventBus
EventBus
, una popolare libreria di GreenRobot, consente ai componenti di comunicare in sicurezza tra loro. Poiché il nostro caso d'uso è quello in cui vogliamo solo aggiornare l'interfaccia utente, questa può essere la scelta più semplice e sicura.
Passaggio 1. Crea una classe evento. ad esempio, UIEvent
.
Passaggio 2. Iscriviti all'evento.
@Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {/* Do something */}; register and unregister eventbus : @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }
Passaggio 3. Pubblica l'evento: EventBus.getDefault().post(new UIEvent());
Con il parametro ThreadMode
nell'annotazione, stai specificando il thread su cui desideri iscriverti per questo evento. Nel nostro esempio qui, stiamo scegliendo il thread principale, poiché vorremo che il destinatario dell'evento sia in grado di aggiornare l'interfaccia utente.
Puoi strutturare la tua classe UIEvent
in modo che contenga informazioni aggiuntive se necessario.
Nel servizio:
class UploadFileService extends IntentService { // … Boolean success = uploadFile(File file); EventBus.getDefault().post(new UIEvent(success)); // ... }
Nell'attività/frammento:
@Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {//show message according to the action success};
Utilizzando la EventBus library
, la comunicazione tra i thread diventa molto più semplice.
Caso d'uso n. 5: comunicazione bidirezionale tra thread basata sulle azioni dell'utente
Supponiamo che tu stia costruendo un lettore multimediale e desideri che sia in grado di continuare a riprodurre musica anche quando la schermata dell'applicazione è chiusa. In questo scenario, vorrai che l'interfaccia utente sia in grado di comunicare con il thread multimediale (ad es. riproduzione, pausa e altre azioni) e vorrai anche che il thread multimediale aggiorni l'interfaccia utente in base a determinati eventi (ad es. errore, stato del buffering , eccetera).
Un esempio completo di lettore multimediale va oltre lo scopo di questo articolo. Tuttavia, puoi trovare buoni tutorial qui e qui.
Opzione 1: utilizzo di EventBus
Puoi usare EventBus
qui. Tuttavia, in genere non è sicuro pubblicare un evento dal thread dell'interfaccia utente e riceverlo in un servizio. Questo perché non hai modo di sapere se il servizio è in esecuzione quando hai inviato il messaggio.
Opzione 2: utilizzo di BoundService
Un BoundService
è un Service
legato a un'attività/frammento. Ciò significa che l'attività/frammento sa sempre se il servizio è in esecuzione o meno e, inoltre, ottiene l'accesso ai metodi pubblici del servizio.
Per implementarlo, è necessario creare un Binder
personalizzato all'interno del servizio e creare un metodo che restituisca il servizio.
public class MediaService extends Service { private final IBinder mBinder = new MediaBinder(); public class MediaBinder extends Binder { MediaService getService() { // Return this instance of LocalService so clients can call public methods return MediaService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } }
Per associare l'attività al servizio, è necessario implementare ServiceConnection
, che è la classe che monitora lo stato del servizio, e utilizzare il metodo bindService
per effettuare l'associazione:
// in the activity MediaService mService; // flag indicates the bound status boolean mBound; @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, MediaService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { MediaBinder binder = (MediaBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } };
Puoi trovare un esempio di implementazione completo qui.
Per comunicare con il servizio quando l'utente tocca il pulsante Riproduci o Pausa, è possibile collegarsi al servizio e quindi chiamare il relativo metodo pubblico sul servizio.
Quando si verifica un evento multimediale e si desidera comunicarlo all'attività/frammento, è possibile utilizzare una delle tecniche precedenti (ad esempio, BroadcastReceiver
, Handler
o EventBus
).
Caso d'uso n. 6: eseguire azioni in parallelo e ottenere risultati
Supponiamo che tu stia creando un'app turistica e desideri mostrare le attrazioni su una mappa recuperata da più fonti (diversi fornitori di dati). Dal momento che non tutte le fonti potrebbero essere affidabili, potresti voler ignorare quelle che hanno fallito e continuare comunque a eseguire il rendering della mappa.
Per parallelizzare il processo, ogni chiamata API deve aver luogo in un thread diverso.
Opzione 1: utilizzo di RxJava
In RxJava, puoi combinare più osservabili in uno solo usando gli operatori merge()
o concat()
. Puoi quindi iscriverti sull'osservabile "unito" e attendere tutti i risultati.
Questo approccio, tuttavia, non funzionerà come previsto. Se una chiamata API non riesce, l'osservabile unito segnalerà un errore generale.
Opzione 2: utilizzo di componenti Java nativi
ExecutorService
in Java crea un numero fisso (configurabile) di thread ed esegue attività su di essi contemporaneamente. Il servizio restituisce un oggetto Future
che alla fine restituisce tutti i risultati tramite il metodo invokeAll()
.
Ogni attività inviata a ExecutorService
deve essere contenuta nell'interfaccia Callable
, che è un'interfaccia per la creazione di un'attività che può generare un'eccezione.
Una volta ottenuti i risultati da invokeAll()
, puoi controllare ogni risultato e procedere di conseguenza.
Supponiamo, ad esempio, di avere tre tipi di attrazione provenienti da tre diversi endpoint e di voler effettuare tre chiamate parallele:
ExecutorService pool = Executors.newFixedThreadPool(3); List<Callable<Object>> tasks = new ArrayList<>(); tasks.add(new Callable<Object>() { @Override public Integer call() throws Exception { return mRest.getAttractionType1(); } }); // ... try { List<Future<Object>> results = pool.invokeAll(tasks); for (Future result : results) { try { Object response = result.get(); if (response instance of AttractionType1... {} if (response instance of AttractionType2... {} ... } catch (ExecutionException e) { e.printStackTrace(); } } } catch (InterruptedException e) { e.printStackTrace(); }
In questo modo, esegui tutte le azioni in parallelo. È quindi possibile verificare la presenza di errori in ciascuna azione separatamente e ignorare i singoli errori a seconda dei casi.
Questo approccio è più semplice rispetto all'utilizzo di RxJava. È più semplice, più breve e non fallisce tutte le azioni a causa di un'eccezione.
Caso d'uso n. 7: interrogazione del database SQLite locale
Quando si ha a che fare con un database SQLite locale, si consiglia di utilizzare il database da un thread in background, poiché le chiamate al database (soprattutto con database di grandi dimensioni o query complesse) possono richiedere molto tempo, con conseguente blocco dell'interfaccia utente.
Quando si esegue una query per i dati SQLite, si ottiene un oggetto Cursor
che può quindi essere utilizzato per recuperare i dati effettivi.
Cursor cursor = getData(); String name = cursor.getString(<colum_number>);
Opzione 1: utilizzo di RxJava
Puoi usare RxJava e ottenere i dati dal database, proprio come riceviamo i dati dal nostro back-end:
public Observable<Cursor> getLocalDataObservable() { return Observable.create(subscriber -> { Cursor cursor = mDbHandler.getData(); subscriber.onNext(cursor); }); }
È possibile utilizzare l'osservabile restituito da getLocalDataObservable()
come segue:
getLocalDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(cursor -> String name = cursor.getString(0), throwable -> Log.e(“db, "error: %s" + throwable.getMessage()));
Sebbene questo sia sicuramente un buon approccio, ce n'è uno ancora migliore, poiché esiste un componente creato proprio per questo scenario.
Opzione 2: utilizzo di CursorLoader + ContentProvider
Android fornisce CursorLoader
, un componente nativo per caricare i dati SQLite e gestire il thread corrispondente. È un Loader
che restituisce un Cursor
, che possiamo usare per ottenere i dati chiamando metodi semplici come getString()
, getLong()
, ecc.
public class SimpleCursorLoader extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> { public static final String TAG = SimpleCursorLoader.class.getSimpleName(); private static final int LOADER_ID = 0x01; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_cursor_loader); textView = (TextView) findViewById(R.id.text_view); getSupportLoaderManager().initLoader(LOADER_ID, null, this); } public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { return new CursorLoader(this, Uri.parse("content://com.github.browep.cursorloader.data") , new String[]{"col1"}, null, null, null); } public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { if (cursor != null && cursor.moveToFirst()) { String text = textView.getText().toString(); while (cursor.moveToNext()) { text += "<br />" + cursor.getString(1); cursor.moveToNext(); } textView.setText(Html.fromHtml(text) ); } } public void onLoaderReset(Loader<Cursor> cursorLoader) { } }
CursorLoader
funziona con il componente ContentProvider
. Questo componente fornisce una pletora di funzionalità di database in tempo reale (ad es. notifiche di modifica, trigger, ecc.) che consentono agli sviluppatori di implementare una migliore esperienza utente molto più facilmente.
Non esiste una soluzione Silver Bullet per il threading in Android
Android offre molti modi per gestire e gestire i thread, ma nessuno di questi è un proiettile d'argento.
La scelta del giusto approccio di threading, a seconda del caso d'uso, può fare la differenza nel rendere la soluzione complessiva facile da implementare e comprendere. I componenti nativi si adattano bene per alcuni casi, ma non per tutti. Lo stesso vale per le fantasiose soluzioni di terze parti.
Spero che troverai questo articolo utile quando lavorerai al tuo prossimo progetto Android. Condividi con noi la tua esperienza di threading in Android o qualsiasi caso d'uso in cui le soluzioni di cui sopra funzionano bene o, se è per questo, nei commenti qui sotto.