Prosessit ja säikeet

Pikakatselu

  • Jokainen sovellus toimii omassa prosessissaan, ja kaikki sovelluksen komponentit suoritetaan oletusarvoisesti kyseisessä prosessissa
  • Hitaat, estävät toiminnot toiminnossa tulisi tehdä uudessa säikeessä, jotta käyttöliittymä ei hidastuisi

Tässä dokumentissa

  1. Prosessit
    1. Prosessin elinkaari
  2. Säikeet
    1. Työskentelysä käytettävät säikeet
    2. Säikeet.safe methods
  3. Interprocess Communication

Kun sovelluskomponentti käynnistyy eikä sovelluksessa ole muita komponentteja käynnissä, Android-järjestelmä käynnistää sovellukselle uuden Linux-prosessin, jossa on yksi suoritussäie. Oletusarvoisesti kaikki saman sovelluksen komponentit suoritetaan samassa prosessissa ja säikeessä (jota kutsutaan ”pääsäikeeksi”). Jos sovelluskomponentti käynnistyy ja kyseistä sovellusta varten on jo olemassa prosessi (koska sovelluksen toinen komponentti on olemassa), komponentti käynnistyy kyseisessä prosessissa ja käyttää samaa suoritussäiettä. Voit kuitenkin järjestää sovelluksen eri komponentit suoritettaviksi erillisissä prosesseissa, ja voit luoda ylimääräisiä säikeitä mihin tahansa prosessiin.

Tässä dokumentissa käsitellään prosessien ja säikeiden toimintaa Android-sovelluksessa.

Prosessit

Oletusarvoisesti kaikki saman sovelluksen komponentit suoritetaan samassa prosessissa, ja useimmissa sovelluksissa tätä ei pitäisi muuttaa. Jos kuitenkin huomaat, että sinun täytyy kontrolloida, mihin prosessiin tietty komponentti kuuluu, voit tehdä sen manifestitiedostossa.

Kunkin komponenttielementtityypin –<activity>, <service>, <receiver> ja <provider> – manifesti-merkintä tukee android:process-attribuuttia, joka voi määrittää prosessin, jossa kyseisen komponentin tulisi toimia. Voit asettaa tämän attribuutin niin, että jokainen komponentti suoritetaan omassa prosessissaan tai niin, että jotkin komponentit jakavat prosessin ja toiset eivät. Voit myös asettaa android:process:n niin, että eri sovellusten komponentit toimivat samassa prosessissa – edellyttäen, että sovelluksilla on sama Linux-käyttäjätunnus ja että ne on allekirjoitettu samoilla varmenteilla.

Elementti <application> tukee myös android:process-attribuuttia, jonka avulla voit asettaa oletusarvon, joka pätee kaikkiin komponentteihin.

Android saattaa päättää sammuttaa prosessin jossakin vaiheessa, kun muisti on vähissä ja sitä vaativat sellaiset prosessit, jotka välittömämmin palvelevat käyttäjää. Lopetetussa prosessissa käynnissä olevat sovelluskomponentit tuhoutuvat näin ollen. Prosessi käynnistetään uudelleen näille komponenteille, kun niillä on taas tekemistä.

Päätettäessä, mitkä prosessit lopetetaan, Android-järjestelmä punnitsee niiden suhteellista merkitystä käyttäjälle. Se esimerkiksi sulkee helpommin prosessin, joka ylläpitää toimintoja, jotka eivät enää näy näytöllä, verrattuna prosessiin, joka ylläpitää näkyviä toimintoja. Päätös prosessin lopettamisesta riippuu siis prosessissa käynnissä olevien komponenttien tilasta. Seuraavassa käsitellään sääntöjä, joiden perusteella päätetään, mitkä prosessit lopetetaan.

Prosessin elinkaari

Android-järjestelmä pyrkii ylläpitämään sovellusprosessia mahdollisimman pitkään, mutta lopulta sen on poistettava vanhoja prosesseja, jotta muistia voidaan vapauttaa uusille tai tärkeämmille prosesseille. Sen määrittämiseksi, mitkä prosessit säilytetään ja mitkä lopetetaan, järjestelmä sijoittaa jokaisen prosessin ”tärkeyshierarkiaan” prosessissa käynnissä olevien komponenttien ja näiden komponenttien tilan perusteella. Prosessit, joilla on pienin merkitys, poistetaan ensin, sitten prosessit, joilla on seuraavaksi pienin merkitys, ja niin edelleen tarpeen mukaan järjestelmän resurssien palauttamiseksi.

Tärkeyshierarkiassa on viisi tasoa. Seuraavassa luettelossa esitellään eri prosessityypit tärkeysjärjestyksessä (ensimmäinen prosessi on tärkein ja se poistetaan viimeisenä):

  1. Etualalla oleva prosessi

    Prosessi, jota tarvitaan käyttäjän tämänhetkisen toiminnan kannalta. Prosessin katsotaan olevan etualalla, jos jokin seuraavista ehdoista on tosi:

    • Se isännöi Activity-prosessia, jonka kanssa käyttäjä on vuorovaikutuksessa (Activity:n onResume()-metodi on kutsuttu).
    • Se isännöi Service:tä, joka on sidottu aktiviteettiin, jonka kanssa käyttäjä on vuorovaikutuksessa.
    • Se isännöi Service:tä, joka on käynnissä ”etualalla” – tämä palvelu on kutsunut startForeground().
    • Se isännöi Service:tä, joka suorittaa jotakin elinkaaripalautettaan (onCreate(), onStart() tai onDestroy()).
    • Se isännöi BroadcastReceiver:tä, joka suorittaa onReceive()-menetelmäänsä.

    Yleisesti vain muutama etualaprosessi on olemassa kerrallaan. Ne lopetetaan vain viimeisenä keinona – jos muisti on niin vähissä, etteivät ne kaikki voi jatkaa toimintaansa. Yleensä laite on tällöin saavuttanut muistin hakemistotilan, joten joidenkin etualan prosessien tappaminen on tarpeen, jotta käyttöliittymä pysyy herkkänä.

  2. Näkyvä prosessi

    Prosessi, jolla ei ole etualan komponentteja, mutta joka voi silti vaikuttaa siihen, mitä käyttäjä näkee näytöllä. Prosessin katsotaan olevan näkyvä, jos jompikumpi seuraavista ehdoista on tosi:

    • Se isännöi Activity:a, joka ei ole etualalla, mutta on silti käyttäjän nähtävissä (sen onPause()-metodia on kutsuttu). Tämä voi tapahtua esimerkiksi, jos etualalla oleva aktiviteetti on käynnistänyt dialogin, jonka ansiosta edellinen aktiviteetti näkyy sen takana.
    • Se isännöi Service:tä, joka on sidottu näkyvään (tai etualalla olevaan)aktiviteettiin.

    Näkyvää prosessia pidetään erittäin tärkeänä, eikä sitä lopeteta, ellei sitä vaadita kaikkien etualalla olevien prosessien käynnissä pitämiseksi.

  3. Palveluprosessi

    Prosessi, joka suorittaa palvelua, joka on käynnistetty startService()-menetelmällä ja joka ei kuulu kumpaankaan kahdesta ylemmästä luokasta. Vaikka palveluprosessit eivät suoranaisesti liity mihinkään, mitä käyttäjä näkee, ne yleensä tekevät asioita, joista käyttäjä välittää (kuten musiikin soittaminen taustalla tai tietojen lataaminen verkkoon), joten järjestelmä pitää ne käynnissä, ellei muistia riitä niiden säilyttämiseen kaikkien etualalla olevien ja näkyvien prosessien ohella.

  4. Taustaprosessi

    Prosessi, joka pitää yllä aktiviteettia, joka ei ole tällä hetkellä käyttäjän nähtävissä (aktiviteetinonStop() metodia on kutsuttu). Näillä prosesseilla ei ole suoraa vaikutusta käyttäjäkokemukseen, ja järjestelmä voi tappaa ne milloin tahansa saadakseen muistia takaisin etukäteismuistia, näkyvää tai palveluprosessia varten. Taustaprosesseja on yleensä käynnissä useita, joten niitä pidetään LRU-luettelossa (vähiten käytetty) sen varmistamiseksi, että prosessi, jolla on käyttäjän viimeksi näkemä toiminto, lopetetaan viimeisenä. Jos aktiviteetti toteuttaa elinkaarimenetelmänsä oikein ja tallentaa nykyisen tilansa, sen prosessin tappamisella ei ole näkyvää vaikutusta käyttäjäkokemukseen, koska kun käyttäjä siirtyy takaisin aktiviteettiin, aktiviteetti palauttaa kaiken näkyvän tilansa. Katso lisätietoja tilan tallentamisesta ja palauttamisesta Activities-dokumentista.

  5. Tyhjä prosessi

    Prosessi, jossa ei ole aktiivisia sovelluskomponentteja. Ainoa syy pitää tämäntyyppinen prosessi elossa on välimuistitiedostojen tallentaminen, jotta voidaan parantaa käynnistymisaikaa seuraavan kerran, kun komponentti täytyy suorittaa siinä. Järjestelmä tappaa usein tällaisia prosesseja tasapainottaakseen järjestelmän kokonaisresursseja prosessien välimuistien ja taustalla olevien ytimen välimuistien välillä.

Android luokittelee prosessin korkeimmalle mahdolliselle tasolle prosessissa tällä hetkellä aktiivisten komponenttien tärkeyden perusteella. Jos prosessi esimerkiksi isännöi palvelua ja näkyvää toimintaa, prosessi luokitellaan näkyväksi prosessiksi, ei palveluprosessiksi.

Lisäksi prosessin luokitusta voidaan nostaa, koska muut prosessit ovat riippuvaisia siitä – prosessia, joka palvelee toista prosessia, ei voida koskaan luokitella alemmaksi kuin prosessia, jota se palvelee. Jos esimerkiksi prosessissa A oleva sisällöntarjoaja palvelee prosessissa B olevaa asiakasta tai jos prosessissa A oleva palvelu on sidottu prosessissa B olevaan komponenttiin, prosessia A pidetään aina vähintään yhtä tärkeänä kuin prosessia B.

Koska palvelua suorittava prosessi luokitellaan korkeammalle kuin prosessi, jolla on tausta-aktiviteetteja,aktiviteetti, joka käynnistää pitkäkestoisen operaation, voi olla hyvä käynnistää palvelua kyseistä operaatiota varten sen sijaan, että luodaan yksinkertaisesti työläissäie – erityisesti jos operaatio todennäköisesti kestää kauemmin kuin aktiviteetti.Esimerkiksi aktiviteetin, joka lataa kuvaa verkkosivustolle, pitäisi käynnistää palvelu suorittamaan lataus, jotta lataus voi jatkua taustalla, vaikka käyttäjä jättäisi aktiviteetin.Palvelun käyttäminen takaa, että toiminnolla on vähintään ”palveluprosessin” prioriteetti riippumatta siitä, mitä aktiviteetille tapahtuu. Tämä on sama syy, miksi lähetyksen vastaanottajien tulisi käyttää palveluita sen sijaan, että ne yksinkertaisesti laittaisivat aikaa vievät toiminnot säikeeseen.

Säikeet

Kun sovellus käynnistetään, järjestelmä luo sovellukselle suoritussäikeen, jota kutsutaan nimellä ”main”. Tämä säie on erittäin tärkeä, koska se vastaa tapahtumien lähettämisestä asianmukaisille käyttöliittymän widgeteille, mukaan lukien piirtotapahtumat. Se on myös säie, jossa sovelluksesi on vuorovaikutuksessa Androidin käyttöliittymätyökalupaketin komponenttien kanssa (komponentit android.widget– ja android.view-paketeista). Näin ollen pääsäiettä kutsutaan joskus myös UI-säikeeksi.

Järjestelmä ei luo erillistä säiettä jokaiselle komponentin instanssille. Kaikki samassa prosessissa suoritettavat komponentit instansoidaan UI-säikeessä, ja kunkin komponentin järjestelmäkutsut lähetetään kyseisestä säikeestä. Näin ollen metodit, jotka vastaavat järjestelmän takaisinkutsuihin (kuten onKeyDown() käyttäjän toimintojen ilmoittamiseen tai elinkaaren takaisinkutsumenetelmä), suoritetaan aina prosessin UI-säikeessä.

Kun käyttäjä esimerkiksi koskettaa näytön painiketta, sovelluksen UI-säie lähettää kosketustapahtuman widgetille, joka vuorostaan asettaa painettu-tilansa valmiiksi ja lähettää mitätöintipyynnön tapahtumajonoon. UI-säie poistaa pyynnön jonosta ja ilmoittaa widgetille, että sen pitäisi piirtää itsensä uudelleen.

Kun sovelluksesi suorittaa intensiivistä työtä käyttäjän vuorovaikutuksen perusteella, tämä yhden säikeen malli voi tuottaa huonoa suorituskykyä, ellet toteuta sovellusta oikein. Erityisesti, jos kaikki tapahtuu UI-säikeessä, pitkien operaatioiden, kuten verkkokäytön tai tietokantakyselyjen, suorittaminen estää koko UI:n käytön. Kun säie on estynyt, tapahtumia ei voida lähettää, piirtotapahtumat mukaan lukien. Käyttäjän näkökulmasta sovellus näyttää roikkuvan. Vielä pahempaa on se, että jos käyttöliittymäsäie pysähtyy useammaksi kuin muutamaksi sekunniksi (tällä hetkellä noin 5 sekunniksi), käyttäjälle ilmestyy surullisen kuuluisa ”sovellus ei vastaa” (ANR) -valintaikkuna. Käyttäjä saattaa sitten päättää lopettaa sovelluksen ja poistaa sen, jos hän on tyytymätön.

Lisäksi Andoidin UI-työkalupaketti ei ole säikeenkestävä. Joten et saa manipuloida käyttöliittymääsi työläissäikeestä käsin – sinun on tehtävä kaikki käyttöliittymän manipulointi UI-säikeestä käsin. Androidin yhden säikeen malliin liittyy siis yksinkertaisesti kaksi sääntöä:

  1. Ei saa blokata UI-säiettä
  2. Ei saa käyttää Androidin UI-työkalupakettia UI-säikeen ulkopuolelta

Työntekijäsäikeet

Yllämainitun yhden säikeen mallin vuoksi sovelluksen UI:n reagointikyvyn kannalta on elintärkeää, ettet blokkaa UI-säiettä. Jos sinulla on suoritettavia operaatioita, jotka eivät ole välittömiä, varmista, että teet ne erillisissä säikeissä (”tausta”- tai ”työläis”-säikeissä).

Alhaalla on esimerkiksi koodia klikkauksen kuuntelijalle, joka lataa kuvan erillisestä säikeestä ja näyttää sen 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();}

Aluksi tämä näyttää toimivan hienosti, koska se luo uuden säikeen käsittelemään verkko-operaatiota. Se kuitenkin rikkoo yhden säikeen mallin toista sääntöä: älä käytäAndroidin UI-työkalupakettia UI-säikeen ulkopuolelta – tämä esimerkki muokkaa ImageView:tä työläissäikeestä UI-säikeen sijaan. Tämä voi johtaa määrittelemättömään ja odottamattomaan käyttäytymiseen, jonka jäljittäminen voi olla vaikeaa ja aikaa vievää.

Tämän ongelman korjaamiseksi Android tarjoaa useita tapoja käyttää UI-säiettä muista säikeistä. Seuraavassa on luettelo menetelmistä, jotka voivat auttaa:

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

Voit esimerkiksi korjata yllä olevan koodin käyttämällä View.post(Runnable)-menetelmää:

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

Nyt tämä toteutus on säikeenkestävä: verkko-operaatio tehdään erillisestä säikeestä, kun taas ImageView:tä käsitellään UI-säikeestä.

Mutta operaation monimutkaisuuden kasvaessa tällaisesta koodista voi tulla monimutkaista ja vaikeasti ylläpidettävää. Jos haluat käsitellä monimutkaisempia vuorovaikutussuhteita työläisketjun kanssa, voit harkita Handler:n käyttämistä työläisketjussa, joka käsittelee UI-ketjusta toimitettuja viestejä. Ehkä paras ratkaisu on kuitenkin laajentaa AsyncTask-luokkaa,joka yksinkertaistaa sellaisten työläisketjutehtävien suorittamista, joiden on oltava vuorovaikutuksessa käyttöliittymän kanssa.

AsyncTask

AsyncTask:n käyttäminen mahdollistaa asynkronisen työn suorittamisen käyttöliittymässä. Se suorittaa estävät operaatiot työläissäikeessä ja julkaisee sitten tulokset käyttöliittymäsäikeessä ilman, että sinun tarvitsee itse käsitellä säikeitä ja/tai käsittelijöitä.

Käyttääksesi sitä, sinun on aliluokitettava AsyncTask ja implementoitava doInBackground():n takaisinsoittomenetelmä, joka suoritetaan taustasäikeiden poolissa. Jos haluat päivittää käyttöliittymääsi, sinun tulee toteuttaa onPostExecute(), joka toimittaa doInBackground():n tuloksen ja toimii UI-säikeessä, jotta voit turvallisesti päivittää käyttöliittymääsi. Voit sitten suorittaa tehtävän kutsumalla execute()UI-säikeestä.

Voit esimerkiksi toteuttaa edellisen esimerkin käyttämällä AsyncTask näin:

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

Nyt UI on turvallinen ja koodi on yksinkertaisempaa, koska se erottaa työn siihen osaan, joka pitäisi tehdä työläissäikeessä ja siihen osaan, joka pitäisi tehdä UI-säikeessä.

Sinun kannattaa lukea AsyncTask-viite saadaksesi täydellisen ymmärryksen tämän luokan käytöstä, mutta tässä on nopea yleiskatsaus sen toiminnasta:

  • Voit määrittää parametrien tyypin, etenemisarvot ja tehtävän loppuarvon käyttämällä geneerisiä
  • Metodi doInBackground() suoritetaan automaattisesti työläissäikeessä
  • onPreExecute(), onPostExecute(), ja onProgressUpdate() kutsutaan kaikki UI-säikeessä
  • doInBackground():n palauttama arvo lähetetään osoitteeseenonPostExecute()
  • Voit kutsua publishProgress() milloin tahansa doInBackground():ssä onProgressUpdate():n suorittamiseksi UI-säikeessä
  • Voit peruuttaa tehtävän milloin tahansa mistä tahansa säikeestä

Varoitus: Toinen ongelma, johon saatat törmätä workerthreadia käytettäessä, on odottamattomat uudelleenkäynnistykset aktiviteetissasi ajonaikaisen konfiguraatiomuutoksen takia (esimerkiksi kun käyttäjä vaihtaa näytön suuntausta), mikä voi tuhota workerthreadin. Katso Shelves-esimerkkisovelluksen lähdekoodista, miten voit säilyttää tehtäväsi tällaisen uudelleenkäynnistyksen aikana ja miten tehtävä peruutetaan oikein, kun aktiviteetti tuhotaan.

Säikeenkestävät metodit

Joissakin tilanteissa toteuttamiasi metodeja saatetaan kutsua useammasta kuin yhdestä säikeestä, ja siksi ne on kirjoitettava säikeenkestäviksi.

Tämä pätee ensisijaisesti metodeihin, joita voidaan kutsua etäältä – kuten sidotun palvelun metodeja. Kun kutsu IBinder:ssä toteutettuun metodiin tulee samasta prosessista, jossa IBinder on käynnissä, metodi suoritetaan kutsujan säikeessä.Kun kutsu kuitenkin tulee toisesta prosessista, metodi suoritetaan säikeessä, joka valitaan säikeiden joukosta, jota järjestelmä ylläpitää samassa prosessissa kuin IBinder (sitä ei suoriteta prosessin UI-säikeessä). Esimerkiksi palvelun onBind()-metodia kutsuttaisiin palvelun prosessin UI-säikeestä, mutta metodeja, jotka on toteutettu objektissa, jonka onBind() palauttaa (esimerkiksi alaluokka, joka toteuttaa RPC-metodit), kutsuttaisiin pooliin kuuluvista säikeistä. Koska palvelulla voi olla useampi kuin yksi asiakas, useampi kuin yksi poolin säie voi käyttää samaa IBinder-metodia samanaikaisesti. IBinder-metodit on siksi toteutettava säikeenkestäviksi.

Vastaavasti sisällöntarjoaja voi vastaanottaa tietopyyntöjä, jotka ovat peräisin muista prosesseista.Vaikka ContentResolver– ja ContentProvider-luokat kätkevät sisäänsä yksityiskohdat siitä, miten prosessien välistä viestintää hallitaan, näihin pyyntöihin vastaavia ContentProvider-metodeja – metodeja query(), insert(), delete(), update() ja getType() – kutsutaan sisällöntarjoajan prosessin säikeiden poolista, ei prosessin UIthreadista. Koska näitä metodeja voidaan kutsua kuinka monesta säikeestä tahansa samaan aikaan, myös ne on toteutettava säikeenkestäviksi.

Interprocess Communication

Android tarjoaa mekanismin prosessien väliseen kommunikaatioon (IPC) käyttämällä etäproseduurikutsuja (RPC), joissa toiminto tai muu sovelluskomponentti kutsuu metodia, mutta se suoritetaan etänä (toisessa prosessissa), ja kaikki tulokset palautetaan takaisin kutsujalle. Tämä edellyttää menetelmäkutsun ja sen tietojen purkamista käyttöjärjestelmän ymmärtämälle tasolle, niiden siirtämistä paikallisesta prosessista ja osoiteavaruudesta etäprosessiin ja -osoiteavaruuteen sekä kutsun kokoamista ja suorittamista uudelleen. Paluuarvot siirretään vastakkaiseen suuntaan. Android tarjoaa kaiken koodin näiden IPC-transaktioiden suorittamiseen, joten voit keskittyä RPC-ohjelmointirajapinnan määrittelyyn ja toteuttamiseen.

Toteuttaaksesi IPC:n, sovelluksesi on sitouduttava palveluun käyttämällä bindService(). Lisätietoja on Services-kehittäjän oppaassa.

Jätä kommentti