Android Threading: tot ce trebuie să știți

Publicat: 2022-03-11

Fiecare dezvoltator de Android, la un moment dat sau altul, trebuie să se ocupe de fire în aplicația sa.

Când o aplicație este lansată în Android, aceasta creează primul fir de execuție, cunoscut sub numele de firul „principal”. Firul principal este responsabil pentru trimiterea evenimentelor către widget-urile de interfață cu utilizatorul corespunzătoare, precum și pentru comunicarea cu componentele din setul de instrumente Android UI.

Pentru a menține aplicația dvs. receptivă, este esențial să evitați utilizarea firului principal pentru a efectua orice operație care ar putea ajunge să o mențină blocată.

Operațiunile de rețea și apelurile la baze de date, precum și încărcarea anumitor componente, sunt exemple comune de operațiuni pe care ar trebui să le evitați în firul principal. Când sunt apelate în firul principal, sunt apelate sincron, ceea ce înseamnă că interfața de utilizare va rămâne complet neresponsabilă până la finalizarea operațiunii. Din acest motiv, acestea sunt de obicei efectuate în fire separate, ceea ce evită astfel blocarea interfeței de utilizare în timp ce sunt efectuate (adică sunt efectuate asincron de la interfața de utilizare).

Android oferă multe modalități de a crea și gestiona fire de execuție și există multe biblioteci terță parte care fac gestionarea firelor de execuție mult mai plăcută. Cu toate acestea, având la îndemână atât de multe abordări diferite, alegerea celei potrivite poate fi destul de confuză.

În acest articol, veți afla despre câteva scenarii comune în dezvoltarea Android în care threading-ul devine esențial și câteva soluții simple care pot fi aplicate acelor scenarii și multe altele.

Threading în Android

În Android, puteți clasifica toate componentele threading în două categorii de bază:

  1. Fire atașate la o activitate/fragment: Aceste fire sunt legate de ciclul de viață al activității/fragmentului și sunt încheiate de îndată ce activitatea/fragmentul este distrus.
  2. Fire care nu sunt atașate la nicio activitate/fragment: Aceste fire pot continua să ruleze dincolo de durata de viață a activității/fragmentului (dacă există) din care au fost generate.

Threading Componente care se atașează la o activitate/fragment

AsyncTask

AsyncTask este cea mai de bază componentă Android pentru threading. Este simplu de utilizat și poate fi bun pentru scenarii de bază.

Exemplu de utilizare:

 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 , totuși, nu este suficient dacă aveți nevoie ca sarcina amânată să ruleze dincolo de durata de viață a activității/fragmentului. Este demn de remarcat faptul că chiar și ceva atât de simplu precum rotirea ecranului poate duce la distrugerea activității.

Încărcătoare

Încărcătoarele sunt soluția pentru problema menționată mai sus. Încărcătoarele se pot opri automat atunci când activitatea este distrusă și se pot reporni și după ce activitatea este recreată.

Există în principal două tipuri de încărcătoare: AsyncTaskLoader și CursorLoader . Veți afla mai multe despre CursorLoader mai târziu în acest articol.

AsyncTaskLoader este similar cu AsyncTask , dar puțin mai complicat.

Exemplu de utilizare:

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

Threading Componente care nu se atașează la o activitate/fragment

Serviciu

Service este o componentă care este utilă pentru a efectua operațiuni lungi (sau potențial lungi) fără nicio interfață de utilizare.

Service rulează în firul principal al procesului său de găzduire; serviciul nu își creează propriul thread și nu rulează într-un proces separat decât dacă specificați altfel.

Exemplu de utilizare:

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

Cu Service , este responsabilitatea dvs. să-l opriți când funcționarea sa este completă, apelând fie metoda stopSelf() fie stopService() .

IntentService

La fel ca Service , IntentService rulează pe un fir separat și se oprește automat după ce își încheie activitatea.

IntentService este de obicei folosit pentru sarcini scurte care nu trebuie atașate la nicio interfață de utilizare.

Exemplu de utilizare:

 public class ExampleService extends IntentService { public ExampleService() { super("ExampleService"); } @Override protected void onHandleIntent(Intent intent) { doSomeShortWork(); } }

Șapte modele de threading în Android

Cazul de utilizare nr. 1: Efectuarea unei cereri prin rețea fără a necesita un răspuns de la server

Uneori este posibil să doriți să trimiteți o solicitare API către un server fără a fi nevoie să vă faceți griji cu privire la răspunsul acestuia. De exemplu, este posibil să trimiteți un jeton de înregistrare push către back-end-ul aplicației dvs.

Deoarece acest lucru implică efectuarea unei cereri prin rețea, ar trebui să o faceți dintr-un fir altul decât cel principal.

Opțiunea 1: AsyncTask sau încărcătoare

Puteți utiliza AsyncTask sau încărcătoare pentru a efectua apelul și va funcționa.

Cu toate acestea, AsyncTask și încărcătoarele depind ambele de ciclul de viață al activității. Aceasta înseamnă că va trebui fie să așteptați ca apelul să se execute și să încercați să împiedicați utilizatorul să părăsească activitatea, fie să sperați că se va executa înainte ca activitatea să fie distrusă.

Opțiunea 2: Service

Service poate fi mai potrivit pentru acest caz de utilizare, deoarece nu este atașat la nicio activitate. Prin urmare, va putea continua apelul în rețea chiar și după ce activitatea este distrusă. În plus, deoarece răspunsul de la server nu este necesar, nici un serviciu nu ar fi limitativ aici.

Cu toate acestea, deoarece un serviciu va începe să ruleze pe firul UI, va trebui totuși să gestionați singur thread-ul. De asemenea, va trebui să vă asigurați că serviciul este oprit odată ce apelul la rețea este finalizat.

Acest lucru ar necesita mai mult efort decât ar trebui să fie necesar pentru o acțiune atât de simplă.

Opțiunea 3: IntentService

Aceasta, după părerea mea, ar fi cea mai bună opțiune.

Deoarece IntentService nu se atașează la nicio activitate și rulează pe un fir non-UI, ne servește perfect nevoilor aici. Mai mult, IntentService se oprește automat, deci nu este nevoie să-l gestionați manual.

Cazul de utilizare nr. 2: Efectuarea unui apel de rețea și obținerea răspunsului de la server

Acest caz de utilizare este probabil puțin mai comun. De exemplu, poate doriți să invocați un API în back-end și să utilizați răspunsul acestuia pentru a completa câmpurile de pe ecran.

Opțiunea 1: Service sau IntentService

Deși un Service sau un IntentService s-a descurcat bine pentru cazul de utilizare anterior, folosirea lor aici nu ar fi o idee bună. Încercarea de a obține date dintr-un Service sau un IntentService în firul principal de UI ar face lucrurile foarte complexe.

Opțiunea 2: AsyncTask sau încărcătoare

La prima vedere, AsyncTask sau încărcătoarele ar părea a fi soluția evidentă aici. Sunt ușor de utilizat - simple și directe.

Cu toate acestea, când utilizați AsyncTask sau încărcătoare, veți observa că este nevoie să scrieți un cod standard. Mai mult, gestionarea erorilor devine o corvoadă majoră cu aceste componente. Chiar și cu un simplu apel de rețea, trebuie să fii conștient de potențialele excepții, să le înțelegi și să acționezi în consecință. Acest lucru ne obligă să împachetăm răspunsul nostru într-o clasă personalizată care conține datele, cu posibile informații despre eroare, iar un semnalizare indică dacă acțiunea a avut succes sau nu.

Este destul de multă muncă de făcut pentru fiecare apel. Din fericire, acum există o soluție mult mai bună și mai simplă disponibilă: RxJava.

Opțiunea 3: RxJava

Poate ați auzit despre RxJava, biblioteca dezvoltată de Netflix. Este aproape magie în Java.

RxAndroid vă permite să utilizați RxJava în Android și face ca rezolvarea sarcinilor asincrone să fie ușoară. Puteți afla mai multe despre RxJava pe Android aici.

RxJava oferă două componente: Observer și Subscriber .

Un observator este o componentă care conține o anumită acțiune. Efectuează acea acțiune și returnează rezultatul dacă reușește sau o eroare dacă eșuează.

Un abonat , pe de altă parte, este o componentă care poate primi rezultatul (sau eroarea) de la un observabil, abonându-se la acesta.

Cu RxJava, creați mai întâi un observabil:

 Observable.create((ObservableOnSubscribe<Data>) e -> { Data data = mRestApi.getData(); e.onNext(data); })

Odată ce observabilul a fost creat, vă puteți abona la el.

Cu biblioteca RxAndroid, puteți controla firul în care doriți să executați acțiunea în observabil și firul în care doriți să obțineți răspunsul (adică rezultatul sau eroarea).

Înlănțuiți observabilul cu aceste două funcții:

 .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()

Schedulerii sunt componente care execută acțiunea într-un anumit thread. AndroidSchedulers.mainThread() este planificatorul asociat cu firul principal.

Având în vedere că apelul nostru API este mRestApi.getData() și returnează un obiect Data , apelul de bază poate arăta astfel:

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

Fără a intra în alte beneficii ale utilizării RxJava, puteți vedea deja cum RxJava ne permite să scriem cod mai matur prin abstracția complexității threading-ului.

Cazul de utilizare nr. 3: Înlănțuirea apelurilor în rețea

Pentru apelurile de rețea care trebuie efectuate în secvență (adică, în cazul în care fiecare operație depinde de răspunsul/rezultatul operației anterioare), trebuie să fiți deosebit de atenți la generarea codului spaghetti.

De exemplu, poate fi necesar să efectuați un apel API cu un token pe care trebuie să îl preluați mai întâi printr-un alt apel API.

Opțiunea 1: AsyncTask sau încărcătoare

Utilizarea AsyncTask sau a încărcătoarelor va duce aproape cu siguranță la cod spaghetti. Funcționalitatea generală va fi dificil de corectat și va necesita o cantitate enormă de cod redundant pe parcursul proiectului dumneavoastră.

Opțiunea 2: RxJava folosind flatMap

În RxJava, operatorul flatMap preia o valoare emisă din observabilul sursă și returnează un alt observabil. Puteți crea un observabil și apoi creați un alt observabil folosind valoarea emisă de la prima, care practic le va înlănțui.

Pasul 1. Creați observabilul care preia jetonul:

 public Observable<String> getTokenObservable() { return Observable.create(subscriber -> { try { String token = mRestApi.getToken(); subscriber.onNext(token); } catch (IOException e) { subscriber.onError(e); } }); }

Pasul 2. Creați observabilul care primește datele folosind simbolul:

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

Pasul 3. Conectați cele două observabile împreună și abonați-vă:

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

Rețineți că utilizarea acestei abordări nu se limitează la apelurile în rețea; poate funcționa cu orice set de acțiuni care trebuie rulat într-o secvență, dar pe fire separate.

Toate cazurile de utilizare de mai sus sunt destul de simple. Comutarea între fire a avut loc numai după ce fiecare și-a încheiat sarcina. Scenariile mai avansate – de exemplu, în care două sau mai multe fire de execuție trebuie să comunice în mod activ între ele – pot fi susținute și de această abordare.

Cazul de utilizare nr. 4: Comunicați cu firul UI dintr-un alt fir

Luați în considerare un scenariu în care doriți să încărcați un fișier și să actualizați interfața cu utilizatorul odată ce este finalizată.

Deoarece încărcarea unui fișier poate dura mult timp, nu este nevoie să faceți utilizatorul să aștepte. Ai putea folosi un serviciu, și probabil IntentService , pentru implementarea funcționalității aici.

În acest caz, totuși, provocarea mai mare este posibilitatea de a invoca o metodă pe firul de execuție UI după ce încărcarea fișierului (care a fost efectuată într-un fir separat) este finalizată.

Opțiunea 1: RxJava în cadrul serviciului

RxJava, fie pe cont propriu, fie în interiorul unui IntentService , poate să nu fie ideal. Va trebui să utilizați un mecanism bazat pe apel invers atunci când vă abonați la un Observable , iar IntentService este creat pentru a efectua apeluri sincrone simple, nu apeluri inverse.

Pe de altă parte, cu un Service , va trebui să opriți manual serviciul, ceea ce necesită mai multă muncă.

Opțiunea 2: BroadcastReceiver

Android oferă această componentă, care poate asculta evenimente globale (de exemplu, evenimente de baterie, evenimente de rețea etc.), precum și evenimente personalizate. Puteți folosi această componentă pentru a crea un eveniment personalizat care este declanșat când încărcarea este finalizată.

Pentru a face acest lucru, trebuie să creați o clasă personalizată care extinde BroadcastReceiver , să o înregistrați în manifest și să utilizați Intent și IntentFilter pentru a crea evenimentul personalizat. Pentru a declanșa evenimentul, veți avea nevoie de metoda sendBroadcast .

Manifesta:

 <receiver android:name="UploadReceiver"> <intent-filter> <action android:name="com.example.upload"> </action> </intent-filter> </receiver>

Receptor:

 public class UploadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getBoolean(“success”, false) { Activity activity = (Activity)context; activity.updateUI(); } }

Expeditor:

 Intent intent = new Intent(); intent.setAction("com.example.upload"); sendBroadcast(intent);

Această abordare este o opțiune viabilă. Dar, după cum ați observat, implică ceva muncă, iar prea multe emisiuni pot încetini lucrurile.

Opțiunea 3: Utilizarea Handler

Un Handler este o componentă care poate fi atașată la un fir de execuție și apoi făcută să efectueze o anumită acțiune pe acel fir de execuție prin mesaje simple sau sarcini Runnable . Funcționează împreună cu o altă componentă, Looper , care se ocupă de procesarea mesajelor într-un anumit thread.

Când este creat un Handler , acesta poate obține un obiect Looper în constructor, care indică la ce fir de execuție este atașat handlerul. Dacă doriți să utilizați un handler atașat firului principal, trebuie să utilizați looper-ul asociat firului principal apelând Looper.getMainLooper() .

În acest caz, pentru a actualiza interfața de utilizare dintr-un thread de fundal, puteți crea un handler atașat firului de interfață și apoi puteți publica o acțiune ca Runnable :

 Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // update the ui from here } });

Această abordare este mult mai bună decât prima, dar există o modalitate și mai simplă de a face asta...

Opțiunea 3: Utilizarea EventBus

EventBus , o bibliotecă populară a GreenRobot, permite componentelor să comunice în siguranță între ele. Deoarece cazul nostru de utilizare este unul în care vrem doar să actualizăm interfața de utilizare, aceasta poate fi cea mai simplă și mai sigură alegere.

Pasul 1. Creați o clasă de eveniment. de exemplu, UIEvent .

Pasul 2. Abonează-te la eveniment.

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

Pasul 3. Postați evenimentul: EventBus.getDefault().post(new UIEvent());

Cu parametrul ThreadMode din adnotare, specificați firul la care doriți să vă abonați pentru acest eveniment. În exemplul nostru de aici, alegem firul principal, deoarece vom dori ca receptorul evenimentului să poată actualiza interfața de utilizare.

Vă puteți structura clasa UIEvent pentru a conține informații suplimentare, dacă este necesar.

În serviciu:

 class UploadFileService extends IntentService { // … Boolean success = uploadFile(File file); EventBus.getDefault().post(new UIEvent(success)); // ... }

În activitate/fragment:

 @Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {//show message according to the action success};

Folosind EventBus library , comunicarea între fire devine mult mai simplă.

Cazul de utilizare nr. 5: Comunicare bidirecțională între fire bazată pe acțiunile utilizatorului

Să presupunem că construiți un player media și doriți ca acesta să poată continua redarea muzicii chiar și atunci când ecranul aplicației este închis. În acest scenariu, veți dori ca interfața de utilizare să poată comunica cu firul media (de exemplu, redare, pauză și alte acțiuni) și, de asemenea, veți dori ca firul media să actualizeze interfața de utilizare în funcție de anumite evenimente (de exemplu, eroare, starea tamponării , etc).

Un exemplu complet de player media depășește scopul acestui articol. Totuși, puteți găsi tutoriale bune aici și aici.

Opțiunea 1: Utilizarea EventBus

Puteți folosi EventBus aici. Cu toate acestea, este, în general, nesigur să postezi un eveniment din firul UI și să-l primești într-un serviciu. Acest lucru se datorează faptului că nu aveți de unde să știți dacă serviciul rulează atunci când ați trimis mesajul.

Opțiunea 2: Utilizarea BoundService

Un BoundService este un Service care este legat de o activitate/fragment. Aceasta înseamnă că activitatea/fragmentul știe întotdeauna dacă serviciul rulează sau nu și, în plus, are acces la metodele publice ale serviciului.

Pentru a-l implementa, trebuie să creați un Binder personalizat în cadrul serviciului și să creați o metodă care returnează serviciul.

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

Pentru a lega activitatea la serviciu, trebuie să implementați ServiceConnection , care este clasa care monitorizează starea serviciului și să utilizați metoda bindService pentru a realiza legarea:

 // 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; } };

Puteți găsi un exemplu complet de implementare aici.

Pentru a comunica cu serviciul atunci când utilizatorul atinge butonul Redare sau Pauză, puteți să vă conectați la serviciu și apoi să apelați metoda publică relevantă a serviciului.

Când există un eveniment media și doriți să-l comunicați înapoi activității/fragmentului, puteți utiliza una dintre tehnicile anterioare (de exemplu, BroadcastReceiver , Handler sau EventBus ).

Cazul de utilizare nr. 6: Executarea acțiunilor în paralel și obținerea de rezultate

Să presupunem că construiți o aplicație turistică și doriți să afișați atracții pe o hartă preluată din mai multe surse (furnizori de date diferiți). Deoarece nu toate sursele pot fi de încredere, poate doriți să le ignorați pe cele care au eșuat și să continuați oricum să redați harta.

Pentru a paraleliza procesul, fiecare apel API trebuie să aibă loc într-un fir diferit.

Opțiunea 1: Utilizarea RxJava

În RxJava, puteți combina mai multe observabile într-unul singur folosind operatorii merge() sau concat() . Puteți apoi să vă abonați la observatorul „combinat” și să așteptați toate rezultatele.

Această abordare, însă, nu va funcționa așa cum era de așteptat. Dacă un apel API eșuează, observabilul îmbinat va raporta un eșec general.

Opțiunea 2: Utilizarea componentelor native Java

ExecutorService în Java creează un număr fix (configurabil) de fire și execută sarcini pe acestea simultan. Serviciul returnează un obiect Future care în cele din urmă returnează toate rezultatele prin metoda invokeAll() .

Fiecare sarcină pe care o trimiteți la ExecutorService ar trebui să fie conținută în interfața Callable , care este o interfață pentru crearea unei sarcini care poate arunca o excepție.

Odată ce obțineți rezultatele de la invokeAll() , puteți verifica fiecare rezultat și puteți continua în consecință.

Să presupunem, de exemplu, că aveți trei tipuri de atracție care vin de la trei puncte finale diferite și doriți să efectuați trei apeluri paralele:

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

În acest fel, executați toate acțiunile în paralel. Prin urmare, puteți verifica erorile în fiecare acțiune separat și puteți ignora erorile individuale, după caz.

Această abordare este mai ușoară decât utilizarea RxJava. Este mai simplu, mai scurt și nu eșuează toate acțiunile din cauza unei singure excepții.

Cazul de utilizare #7: Interogarea bazei de date SQLite locală

Când aveți de-a face cu o bază de date SQLite locală, se recomandă ca baza de date să fie utilizată dintr-un fir de execuție de fundal, deoarece apelurile la baze de date (în special cu baze de date mari sau interogări complexe) pot consuma mult timp, ceea ce duce la înghețarea interfeței de utilizare.

Când interogați datele SQLite, obțineți un obiect Cursor care poate fi apoi folosit pentru a prelua datele reale.

 Cursor cursor = getData(); String name = cursor.getString(<colum_number>);

Opțiunea 1: Utilizarea RxJava

Puteți folosi RxJava și obțineți datele din baza de date, așa cum primim date din back-end-ul nostru:

 public Observable<Cursor> getLocalDataObservable() { return Observable.create(subscriber -> { Cursor cursor = mDbHandler.getData(); subscriber.onNext(cursor); }); }

Puteți utiliza observabilul returnat de getLocalDataObservable() după cum urmează:

 getLocalDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(cursor -> String name = cursor.getString(0), throwable -> Log.e(“db, "error: %s" + throwable.getMessage()));

Deși aceasta este cu siguranță o abordare bună, există una care este și mai bună, deoarece există o componentă care este construită tocmai pentru acest scenariu.

Opțiunea 2: Utilizarea CursorLoader + ContentProvider

Android oferă CursorLoader , o componentă nativă pentru încărcarea datelor SQLite și gestionarea firului de execuție corespunzător. Este un Loader care returnează un Cursor , pe care îl putem folosi pentru a obține date apelând metode simple precum getString() , getLong() etc.

 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 funcționează cu componenta ContentProvider . Această componentă oferă o multitudine de funcții de bază de date în timp real (de exemplu, notificări de modificare, declanșatoare etc.) care le permit dezvoltatorilor să implementeze o experiență de utilizator mai bună mult mai ușor.

Nu există o soluție Silver Bullet pentru Threading în Android

Android oferă multe modalități de a gestiona și gestiona firele de execuție, dar niciuna dintre ele nu este gloanțe de argint.

Alegerea abordării corecte de threading, în funcție de cazul dvs. de utilizare, poate face toată diferența pentru a face soluția generală ușor de implementat și înțeles. Componentele native se potrivesc bine pentru unele cazuri, dar nu pentru toate. Același lucru este valabil și pentru soluțiile de lux ale terților.

Sper că veți găsi acest articol util atunci când lucrați la următorul proiect Android. Împărtășiți-ne experiența dvs. de threading în Android sau orice caz de utilizare în care soluțiile de mai sus funcționează bine – sau nu, de altfel – în comentariile de mai jos.