Java Android JVM Singleton Thread

Android-udvikling – udfordringer og anbefalinger

Udgivet d. 14. september 2012 af Jacob Nordfalk

Når man programmerer apps er det selvsagt centralt, at det sker i overensstemmelse med det, brugeren forventer.

Det omfatter:
– En lynhurtig opstartstid, især hvis han har haft app’en åben lige før.
– Alt er ‘åbent og klar til brug’ – altid.
– Brugeren forventer at kunne fortsætte hvor han slap sidst
– Brugeren forventer at kunne installere hundredvis af apps
– Brugeren skal ikke generes af detaljer om hukommelsesstyring, såsom at skulle lukke programmer efter brug

For at understøtte disse forventninger vil Android-styresystemet på den ene side forsøge at holde så mange programmer i hukommelsen så længe som muligt, så de ikke behøver at blive indlæst igen, men på den anden side skal det kunne smide en app ud af hukommelsen uden varsel.

Disse vilkår giver programmørerne nogle ekstra udfordringer, for de har ingen kontrol over, hvordan deres app lukkes og kun lidt kontrol over, hvordan den startes – og samtidig kan der være meget stærkt svingende adgang til internettet.

Det siger sig selv, at brugerens oplevelse forringes væsentligt, hvis en app ikke tager højde for dette. Selvom artiklen herunder bruger en del ord som kun Android-udviklere kender, er den skrevet så andre Android-brugere også kan få et indblik i, hvad der foregår, og hvordan udviklerne burde have løst det.

Arkitekturen i Android-platformen

På grund af arkitekturen i Android – specielt at applikationerne ikke selv har kontrol over, hvornår og hvordan de lukkes ned og startes op – skal man skelne mellem den applikation, som brugeren oplever og den (Linux-)proces, som app’en kører i. Brugeren kan skifte væk fra app’en og hoppe tilbage igen og forventer at kunne tage den i brug fra det punkt, hun forlod den.

For at kunne understøtte en optimal brugeroplevelse, vil Android beholde så mange processer (med hver sin JVM) i hukommelsen, som der er plads til og så, når der er brug for mere hukommelse, fjerne (dræbe) den proces der vurderes som mindst vigtig for brugeroplevelsen.

Den dræbte proces får ikke mulighed for at gemme data eller rydde op, for oprydning ville forsinke den proces, der har brug for hukommelsen (typisk det program brugeren er i gang med). I stedet forventes det at Android-programmer rydder op og gemmer data, når de ikke mere er synlige, dvs. efter at onStop() er kørt færdigt (i Android 2.3.3 og tidligere kan det ske allerede efter onPause()).

Brugeren kan, ved at holde HJEM-knapppen nede, vælge mellem de senest brugte applikationer og ved ikke, at nogle af dem muligvis har fået dræbt deres proces. Vælger brugeren en af de apps, der er smidt ud af hukommelsen, vil Android starte en ny process (og en ny JVM) og genskabe applikationens tilstand.

Teknisk set sker det ved at Android kalder onCreate() på den forreste aktivitet (skærmbillede) i applikationen og giver den et Bundle med den gemte tilstand fra forrige gang (savedInstanceState). De parametre (intent’et), som den oprindelige aktivitet blev oprettet med, bliver også genskabt.

En måde at undgå at blive dræbt på er ved at starte en service. Da vil Android forsøge at undgå at slå processen ihjel, og, hvis f.eks. den aktive proces i forgrunden eller en af de andre processer med services kræver meget hukommelse, genstarte processen og servicen hurtigst muligt. Det siger sig selv, at det ikke giver nogen god brugeroplevelse, hvis der er for mange apps, der benytter sig af services.

Har man levende ikoner på hjemmeskærmen eller lytter efter broadcasts, bliver processen startet op fra tid til anden, men kan blive lukket lige så hurtigt igen, når broadcastet er blevet behandlet.

Og har man implementeret en content provider, kan ens process blive startet, når som helst nogen forespørger på de data, som app’en udbyder.

Et Androidprogram kan altså blive startet på en lang række måder, og den første stump programkode der køres kan derfor være meget forskellig afhængig af situationen.

Hvornår kan man risikere at starte i en frisk JVM?

Har man nogle data i hukommelsen, som man er afhængig af, skal man derfor tjekke for, om man kører på en frisk JVM, og data skal genindlæses/genskabes. Gør man det ‘lidt efter lidt’, når problemer opstår, vil man ende med et uoverskueligt program, for man kan risikere at starte op i en frisk JVM på et utal af måder.

Måderne er

  • Brugeren starter programmet fra hjemmeskærmen (eller på en anden måde)
    • Dvs. i alle de mulige start-skærmbilleder (aktiviteter med et <intent-filter>)
  • Brugeren holder HJEM nede og vælger programmet mellem de seneste brugte programmer
    • Dvs. der bør også være tjek i alle de interne aktiviteter i programmet
      (medmindre Android aldrig vil starte dem op som de første fordi man ikke kan skifte tilbage til dem – det styrer man i manifestet hvor man bl.a. kan sætte android:noHistory=”true” eller android:launchMode=”singleTask”)
  • Alle andre indgangspunkter til programmet, dvs.
    • Alle services (inkl. levende baggrunde)
    • Alle broadcast receivers (inkl. alle levende ikoner)
    • Alle content providers