Procese și fire de execuție

Quickview

  • Care aplicație rulează în propriul proces și toate componentele aplicației rulează în acel proces, în mod implicit
  • Toate operațiile lente și blocante dintr-o activitate ar trebui să fie efectuate într-un nou fir de execuție, pentru a evita încetinirea interfeței cu utilizatorul

În acest document

  1. Procese
    1. Ciclul de viață al proceselor
  2. Fire de execuție
    1. Fire de execuție de lucru
    2. Fire de execuție-safe methods
  3. Interprocess Communication

Când o componentă a aplicației pornește și aplicația nu are alte componente în execuție, sistemul Android pornește un nou proces Linux pentru aplicație cu un singur fir de execuție. În mod implicit, toate componentele aceleiași aplicații rulează în același proces și fir de execuție (numit firul „principal”). În cazul în care o componentă a aplicației pornește și există deja un proces pentru acea aplicație (deoarece există o altă componentă din aplicație), atunci componenta este pornită în cadrul acelui proces și utilizează același fir de execuție. Cu toate acestea, puteți aranja ca diferite componente din aplicația dumneavoastră să ruleze în procese separate și puteți crea fire suplimentare pentru orice proces.

Acest document discută modul în care funcționează procesele și firele de execuție într-o aplicație Android.

Procese

În mod implicit, toate componentele aceleiași aplicații rulează în același proces și majoritatea aplicațiilornu ar trebui să schimbe acest lucru. Cu toate acestea, dacă considerați că trebuie să controlați procesul căruia îi aparține o anumităcomponentă, puteți face acest lucru în fișierul manifest.

Intrarea manifest pentru fiecare tip de element de componentă – <activity>, <service>, <receiver> și <provider> – suportă un atribut android:process care poate specifica un proces în care ar trebui să ruleze acea componentă. Puteți seta acest atribut astfel încât fiecare componentă să ruleze în propriul proces sau astfel încât unele componente să împartă un proces în timp ce altele nu. De asemenea, puteți seta android:process astfel încât componentele unor aplicații diferite să ruleze în același proces – cu condiția ca aplicațiile să aibă același ID de utilizator Linux și să fie semnate cu aceleași certificate.

Elementul <application> suportă, de asemenea, un atribut android:process, pentru a seta o valoare implicită care se aplică tuturor componentelor.

Android ar putea decide să închidă un proces la un moment dat, când memoria este redusă și este necesară pentru alte procese care deservesc mai imediat utilizatorul. Componentele aplicației care rulează în procesul care este ucis sunt în consecință distruse. Un proces este pornit din nou pentru acele componente atunci când există din nou treabă de făcut pentru ele.

Când decide ce procese să ucidă, sistemul Android cântărește importanța lor relativă pentru utilizator. De exemplu, el oprește mai ușor un proces care găzduiește activități care nu mai sunt vizibile pe ecran, în comparație cu un proces care găzduiește activități vizibile. Prin urmare, decizia de a termina sau nu un proces depinde de starea componentelor care rulează în acel proces. Regulile utilizate pentru a decide ce procese trebuie închise sunt discutate mai jos.

Ciclul de viață al proceselor

Sistemul Android încearcă să mențină un proces de aplicație cât mai mult timp posibil, dar în cele din urmă trebuie să elimine procesele vechi pentru a recupera memorie pentru procese noi sau mai importante. Pentru a determina ce procese trebuie păstrate și care trebuie omorâte, sistemul plasează fiecare proces într-o „ierarhie de importanță” pe baza componentelor care rulează în proces și a stării acestor componente. Procesele cu importanța cea mai mică sunt eliminate mai întâi, apoi cele cu următoarea importanță cea mai mică și așa mai departe, după cum este necesar pentru a recupera resursele sistemului.

Există cinci niveluri în ierarhia de importanță. Următoarea listă prezintă diferiteletipuri de procese în ordinea importanței (primul proces este cel mai important și este eliminat ultimul):

  1. Proces de prim-plan

    Un proces care este necesar pentru ceea ce face utilizatorul în acel moment. Se consideră că un proces se află în prim-plan dacă oricare dintre următoarele condiții este adevărată:

    • El găzduiește un Activity cu care utilizatorul interacționează (metoda onResume() a Activity a fost apelată).
    • Găzduiește un Service care este legat de activitatea cu care interacționează utilizatorul.
    • Găzduiește un Service care rulează „în prim-plan” – acest serviciu a apelat startForeground().
    • Găzduiește un Service care execută unul dintre procesele sale de retur ale ciclului de viață (onCreate(), onStart() sau onDestroy()).
    • Găzduiește un BroadcastReceiver care execută metoda sa onReceive().

    În general, există doar câteva procese în prim-plan la un moment dat. Acestea sunt omorâte doar în ultimă instanță – dacă memoria este atât de redusă încât nu pot continua să ruleze toate. În general, în acel moment, dispozitivul a ajuns la o stare de paginare a memoriei, astfel încât uciderea unor procese în prim-plan este necesară pentru a menține receptivitatea interfeței cu utilizatorul.

  2. Proces vizibil

    Un proces care nu are componente în prim-plan, dar care poate totuși afecta ceea ce vede utilizatorul pe ecran. Un proces este considerat a fi vizibil dacă una dintre următoarele condiții este adevărată:

    • El găzduiește un Activity care nu este în prim-plan, dar este totuși vizibil pentru utilizator (metoda sa onPause() a fost apelată). Acest lucru se poate întâmpla, de exemplu, în cazul în care activitatea din prim-plan a inițiat un dialog, ceea ce permite ca activitatea anterioară să fie văzută în spatele ei.
    • Găzduiește un Service care este legat de o activitate vizibilă (sau din prim-plan).

    Un proces vizibil este considerat extrem de important și nu va fi ucis decât dacă acest lucru este necesar pentru a menține în funcțiune toate procesele din prim-plan.

  3. Proces de serviciu

    Un proces care rulează un serviciu care a fost pornit cu metoda startService() și care nu se încadrează în niciuna dintre cele douăcategorii superioare. Deși procesele de serviciu nu sunt direct legate de nimic din ceea ce vede utilizatorul, ele fac în general lucruri de care utilizatorului îi pasă (cum ar fi redarea de muzică în fundal sau descărcarea de date în rețea), astfel încât sistemul le menține în funcțiune, cu excepția cazului în care nu există suficientă memoriepentru a le reține împreună cu toate procesele din prim-plan și cele vizibile.

  4. Proces de fundal

    Un proces care deține o activitate care nu este în prezent vizibilă pentru utilizator (metodaonStop() a activității a fost apelată). Aceste procese nu au nici un impact direct asupra experienței utilizatorului, iar sistemul le poate omorî în orice moment pentru a recupera memorie pentru un proces anterior, vizibil sau de serviciu. De obicei, există mai multe procese de fundal care rulează, astfel încât acestea sunt păstrate într-o listă LRU (least recently used) pentru a se asigura că procesul cu activitatea care a fost cel mai recent văzut de utilizator este ultimul care este ucis. În cazul în care o activitate își implementează corect metodele ciclului de viață și își salvează starea curentă, uciderea procesului său nu va avea un efect vizibil asupra experienței utilizatorului, deoarece atunci când utilizatorul navighează înapoi la activitate, aceasta își restabilește toată starea vizibilă. Consultați documentul Activitiesdocument pentru informații despre salvarea și restabilirea stării.

  5. Proces gol

    Un proces care nu deține nicio componentă activă a aplicației. Singurul motiv pentru a menține în viață acest tip de proces este în scopuri de cache, pentru a îmbunătăți timpul de pornire data viitoare când o componentătrebuie să ruleze în el. Sistemul ucide adesea aceste procese pentru a echilibra resursele generale ale sistemului între memoria cache a proceselor și memoria cache a nucleului care stă la baza acestora.

Android clasifică un proces la cel mai înalt nivel pe care îl poate, pe baza importanței componentelor active în acel proces. De exemplu, dacă un proces găzduiește un serviciu și o activitate vizibilă, procesul este clasificat ca proces vizibil, nu ca proces de serviciu.

În plus, clasificarea unui proces ar putea fi crescută deoarece alte procese sunt dependente de acesta – un proces care deservește un alt proces nu poate fi niciodată clasificat mai jos decât procesul pe care îl deservește. De exemplu, dacă un furnizor de conținut din procesul A deservește un client din procesul B, sau dacă un serviciu din procesul A este legat de o componentă din procesul B, procesul A este întotdeauna considerat cel puțin la fel de important ca și procesul B.

Pentru că un proces care rulează un serviciu este mai bine clasat decât un proces cu activități de fundal, o activitate care inițiază o operațiune de lungă durată ar putea face bine să pornească un serviciu pentru acea operațiune, mai degrabă decât să creeze pur și simplu un fir de lucru – în special dacă operațiunea va dura probabil mai mult decât activitatea.De exemplu, o activitate care încarcă o imagine pe un site web ar trebui să pornească un serviciu pentru a efectua încărcarea, astfel încât încărcarea să poată continua în fundal chiar dacă utilizatorul părăsește activitatea.Utilizarea unui serviciu garantează că operațiunea va avea cel puțin prioritatea „proces de serviciu”, indiferent de ceea ce se întâmplă cu activitatea. Acesta este același motiv pentru care receptoarele de difuzare ar trebui săfolosească servicii mai degrabă decât să pună pur și simplu operațiile consumatoare de timp într-un fir de execuție.

Firuri de execuție

Când o aplicație este lansată, sistemul creează un fir de execuție pentru aplicație,numit „main”. Acest fir de execuție este foarte important deoarece este responsabil de expedierea evenimentelor către widgeturile corespunzătoare ale interfeței cu utilizatorul, inclusiv evenimente de desen. Acesta este, de asemenea, firul de execuție în care aplicația dvs. interacționează cu componentele din setul de instrumente Android UI (componente din pachetele android.widget și android.view). Ca atare, firul principal este uneori numit și firul UI.

Sistemul nu creează un fir separat pentru fiecare instanță a unei componente. Toatecomponentele care rulează în același proces sunt instanțiate în firul UI, iar apelurile de sistem pentru fiecare componentă sunt expediate din acel fir. În consecință, metodele care răspund la chemările de sistem (cum ar fi onKeyDown() pentru a raporta acțiunile utilizatorului sau o metodă de apelare a ciclului de viață) se execută întotdeauna în firul UI al procesului.

De exemplu, atunci când utilizatorul atinge un buton de pe ecran, firul UI al aplicației dumneavoastră expediază evenimentul de atingere către widget, care, la rândul său, își stabilește starea de apăsare și postează o cerere de invalidare în coada de evenimente. Firul de interfață utilizator scoate din coadă cererea și notifică widgetul că ar trebui să se redeseneze.

Când aplicația dvs. efectuează o muncă intensivă ca răspuns la interacțiunea cu utilizatorul, acest model cu un singur fir poate avea performanțe slabe dacă nu vă implementați aplicația în mod corespunzător. Mai exact, dacă totul se întâmplă în firul UI, efectuarea unor operațiuni lungi, cum ar fi accesul la rețea sau interogările bazei de date, va bloca întreaga interfață. Atunci când firul de execuție este blocat, niciun eveniment nu poate fi expediat, inclusiv evenimentele de desen. Din punctul de vedere al utilizatorului, aplicația pare să se blocheze. Chiar mai rău, în cazul în care firul UI este blocat pentru mai mult de câteva secunde (aproximativ 5 secunde în prezent), utilizatorului i se prezintă infamul dialog „application notresponding” (ANR). Utilizatorul ar putea decide apoi să părăsească aplicația dumneavoastră și să o dezinstaleze dacă este nemulțumit.

În plus, setul de instrumente UI Andoid nu este thread-safe. Așadar, nu trebuie să vă manipulați interfața de utilizator dintr-un fir de lucru – trebuie să faceți toate manipulările la interfața de utilizator din firul UI. Astfel, există pur și simplu două reguli pentru modelul cu un singur fir de execuție al Android:

  1. Nu blocați firul UI
  2. Nu accesați setul de instrumente UI Android din afara firului UI

Fire de lucru

Din cauza modelului cu un singur fir de execuție descris mai sus, este vital pentru capacitatea de reacție a interfeței aplicației dumneavoastră să nu blocați firul UI. Dacă aveți de efectuat operații care nu sunt instantanee, ar trebui să vă asigurați că le efectuați în fire separate (fire de execuție „de fundal” sau „de lucru”).

De exemplu, mai jos este un cod pentru un ascultător de clic care descarcă o imagine dintr-un fir separat și o afișează într-un ImageView:

public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start();}

La început, acest lucru pare să funcționeze bine, deoarece creează un nou fir de execuție pentru a gestiona operația de rețea. Cu toate acestea, încalcă a doua regulă a modelului cu un singur fir: nu accesați setul de instrumente Android UI din afara firului UI – acest exemplu modifică ImageView din firul lucrător în loc de firul UI. Acest lucru poate avea ca rezultat un comportament nedefinit și neașteptat, a cărui depistare poate fi dificilă și consumatoare de timp.

Pentru a remedia această problemă, Android oferă mai multe modalități de a accesa firul UI din alte fire. Iată o listă de metode care vă pot ajuta:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)

De exemplu, puteți rezolva codul de mai sus folosind metoda View.post(Runnable):

public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start();}

Acum, această implementare este thread-safe: operațiunea de rețea se face de pe un fir separat, în timp ce ImageView este manipulată de pe firul UI.

Cu toate acestea, pe măsură ce complexitatea operațiunii crește, acest tip de cod poate deveni complicat și greu de întreținut. Pentru a gestiona interacțiuni mai complexe cu un fir de lucrător, ați putea lua în considerare utilizarea unui Handler în firul de lucrător, pentru a procesa mesajele livrate de la firul UI. Poate că cea mai bună soluție, totuși, este să extindeți clasa AsyncTask,care simplifică execuția sarcinilor firului de lucru care trebuie să interacționeze cu UI.

Utilizarea AsyncTask

AsyncTask vă permite să efectuați lucrări asincrone pe interfața cu utilizatorul. Efectuează operațiile de blocare într-un fir de lucru și apoi publică rezultatele pe firul UI, fără a vă solicita să gestionați voi înșivă fire și/sau gestionari.

Pentru a o utiliza, trebuie să subclasați AsyncTask și să implementați metoda doInBackground() callback doInBackground(), care rulează într-un grup de fire de fundal. Pentru a vă actualiza interfața de utilizator, trebuie să implementați onPostExecute(), care livrează rezultatul de la doInBackground() și rulează în firul UI, astfel încât să puteți actualiza interfața de utilizator în siguranță. Puteți apoi să executați sarcina apelând execute()din firul UI.

De exemplu, puteți implementa exemplul anterior folosind AsyncTask în acest mod:

public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png");}private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { /** The system calls this to perform work in a worker thread and * delivers it the parameters given to AsyncTask.execute() */ protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls); } /** The system calls this to perform work in the UI thread and delivers * the result from doInBackground() */ protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); }}

Acum interfața este sigură și codul este mai simplu, deoarece separă munca în partea care ar trebui să se facă pe un fir de lucru și partea care ar trebui să se facă pe firul UI.

Ar trebui să citiți referința AsyncTask pentru o înțelegere completă a modului de utilizare a acestei clase, dar iată o prezentare rapidă a modului în care funcționează:

  • Puteți specifica tipul parametrilor, valorile de progres și valoarea finală a sarcinii, folosind generice
  • Metoda doInBackground() se execută automat pe un fir de lucru
  • onPreExecute(), onPostExecute(), și onProgressUpdate() sunt toate invocate pe firul UI
  • Valoarea returnată de doInBackground() este trimisă cătreonPostExecute()
  • Puteți apela publishProgress() în orice moment în doInBackground() pentru a executa onProgressUpdate() pe firul UI
  • Puteți anula sarcina în orice moment, de pe orice fir

Atenție: O altă problemă pe care o puteți întâmpina atunci când utilizați un workerthread este repornirea neașteptată a activității dvs. din cauza unei modificări de configurare în timpul execuției (de exemplu, atunci când utilizatorul schimbă orientarea ecranului), care poate distruge workerthread-ul. Pentru a vedea cum puteți persista sarcina dvs. în timpul uneia dintre aceste reporniri și cum să anulați în mod corespunzător sarcina atunci când activitatea este distrusă, consultați codul sursă al aplicației de probă Rafturi.

Metode sigure pentru fire

În unele situații, metodele pe care le implementați pot fi apelate de mai multe fire și, prin urmare, trebuie să fie scrise pentru a fi sigure pentru fire.

Acest lucru este valabil în primul rând pentru metodele care pot fi apelate de la distanță – cum ar fi metodele dintr-un serviciu legat. Atunci când un apel la o metodă implementată într-un IBinder provine din același proces în care rulează IBinder, metoda este executată în firul de execuție al apelantului.Cu toate acestea, atunci când apelul provine dintr-un alt proces, metoda este executată într-un fir de execuție ales dintr-un grup de fire de execuție pe care sistemul îl menține în același proces ca și IBinder (nu este executată în firul UI al procesului). De exemplu, în timp ce metoda onBind() a unui serviciu ar fi apelată din firul UI al procesului serviciului, metodele implementate în obiectul pe care onBind() îl returnează (de exemplu, o subclasă care implementează metode RPC) ar fi apelate din firele din grup. Deoarece un serviciu poate avea mai mult de un client, mai multe fire de execuție din pool pot angaja aceeași metodă IBinder în același timp. Metodele IBinder trebuie, prin urmare, să fie implementate pentru a fi sigure pentru fire.

În mod similar, un furnizor de conținut poate primi cereri de date care provin din alte procese.Deși clasele ContentResolver și ContentProvider ascund detaliile modului în care este gestionată comunicarea interproces, metodele ContentProvider care răspund la aceste cereri – metodele query(), insert(), delete(), update() și getType() – sunt apelate dintr-un grup de fire de execuție din procesul furnizorului de conținut, nu din UIthread-ul pentru proces. Deoarece aceste metode pot fi apelate din orice număr de fire de execuție în același timp, și acestea trebuie să fie implementate pentru a fi sigure pentru fire de execuție.

Comunicare între procese

Android oferă un mecanism de comunicare între procese (IPC) folosind apeluri de procedură la distanță (RPC), în care o metodă este apelată de o activitate sau de o altă componentă a aplicației, dar executată la distanță (într-un alt proces), iar orice rezultat este returnat celui care l-a apelat. Acest lucru presupune descompunerea unui apel de metodă și a datelor sale la un nivel pe care sistemul de operare îl poate înțelege, transmiterea acestuia de la procesul și spațiul de adrese locale la procesul și spațiul de adrese de la distanță, apoi reasamblarea și reluarea apelului acolo. Valorile de returnare sunt transmise în direcția opusă. Android furnizează tot codul pentru a efectua aceste tranzacții IPC, astfel încât vă puteți concentra pe definirea și implementarea interfeței de programare RPC.

Pentru a efectua IPC, aplicația dumneavoastră trebuie să se lege de un serviciu, folosind bindService(). Pentru mai multe informații, consultați Ghidul dezvoltatorului de servicii.

Lasă un comentariu