Category Archives: Android

OUYA, Android-konsoli

Kesälomalta paluu sorvin ääreen voi olla raskasta tai hauskaa… Kesälomalla hankittujen lelujen kanssa siitä onneksi saa hauskaa.

OUYA on Kickstarter-rahoituksen avulla luotu Android 4.1 -konsoli, jossa pyörivät kaikki normaalit Android-applikaatiot ilman suurempaa vaivaa. Konsoliin saa helposti ladattua SDK:n, jonka avulla ohjainta voi käyttää applikaatioissaan, mutta hiirellä ja näppäimistölläkin voi konsolia suoraan käyttää.

OUYA ei ole kovin tehokas konsoli eikä pelitarjonta ole vielä kovin laaja, tätä artikkelia kirjoitettaessa vain 384 peliä, mutta 99$ hintansa puolesta se on hyvä hankinta arcade pelien pelaamiseen olohuoneen sohvalla kavereiden kanssa.

Yrityksille OUYA tarjoaa halvan ja helpon ratkaisun tarjota interaktiivista sisältöä asiakasnäyttöihin. Valmiin applikaation kääntäminen ja asentaminen OUYA:lle on hyvin helppoa. Kehittäminen OUYA-konsolille ei ole yhtään sen vaikeampaa kuin mille tahansa muulle Android-laitteelle. Toki pieniä muutoksia joudutaan tekemään käyttöliittymään, jotta normaalisti kosketusnäytölle tarkoitettua applikaatiota voi käyttää peliohjaimella tai hiirellä. Kosketusnäytön korvaaminen hiirellä tai muulla ohjaimella johtaa helposti hyvin heikkoon käyttökokemukseen.

 

Asentaaksesi minkä tahansa applikaation OUYA:lle pitää sinun voida ladata applikaation .apk-tiedosto Internetistä. Mene OUYA:n kotinäkymän MAKE-valikkoon ja sisäänrakennetulla Internet-selaimella lataa APK-tiedosto. Tämän jälkeen palaa päävalikkoon, mene MANAGE-valikkoon, valitse SYSTEM ja siellä valitse ADVANCED. Tämä on jokaiselle Android-käyttäjälle tuttu asetukset-valikko Androidissa. Mene listassa alaspäin ja valitse Storage, jonka sisällä valitse Download. Täältä löydät juuri lataamasi APK-paketin ja asentaminen onnistuu ohjaimen O-napilla. Tämän jälkeen applikaatio löytyy samasta MAKE-valikosta kuin OUYA:n Internet-selain.

Samaan MAKE-valikkoon tulevat myös applikaatiot, jotka itse kehität ja asennat tietokoneen ADB:n avulla microUSB:n kautta.

Kehittäminen OUYA:lla

Kaikki OUYA-konsolit toimivat applikaatioiden kehittämiseen ilman maksuja tai muutoksia konsoliin. Jotta saat OUYA:n toimimaan normaalissa Android-kehitysympäristössäsi Macilla sinun täytyy vain lisätä kolmannen osapuolen USB Hex ID (0x2836) adb_usb.ini -tiedostoon .android-hakemistossa ja käynnistää ADB uudelleen. Tarkemmat ohjeet myös Windows-koneille on katsottavissa videona OUYA developers -sivustolta. Tämän jälkeen OUYA-konsolisi näkyy Eclipsessä ja ADB:ssä samalla tavalla kuin mikä tahansa muu Android-laite.

Seuraavaksi lataa OUYA Development kit (ODK) osoitteesta https://devs.ouya.tv/developers/odk, kopioi libs\-hakemistosta ouya-sdk.jar ja lisää se omaan Android-projektiisi. Tämän jälkeen ohjaimen komennot saa kiinni oman Activity -luokkasi onKeyDown() ja onGenericMotionEvent() -metodeista.

Tein tätä artikkelia varten yksinkertaisen applikaation, joka käyttää ODK:ta siirtelemään ImageView:tä FrameLayoutin sisällä.

Ota ohjain käyttöön Activity-luokan onCreate()-metodissa:

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	OuyaController.init(this);
}

Tunnista ohjaimen komennot:

@Override
public boolean onKeyDown(final int keyCode, KeyEvent event) {
	switch (keyCode) {
		case OuyaController.BUTTON_O:
			Log.d(TAG, "OUYA button O");
			break;
		case OuyaController.BUTTON_U:
			Log.d(TAG, "OUYA button U");
			break;
		case OuyaController.BUTTON_Y:
			Log.d(TAG, "OUYA button Y");
			break;
		case OuyaController.BUTTON_A:
			Log.d(TAG, "OUYA button A");
			break;
	}
	return true;
}
@Override
public boolean onGenericMotionEvent(final MotionEvent event) {
// Joystick
	if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
		float LS_X = event.getAxisValue(OuyaController.AXIS_LS_X);
		float LS_Y = event.getAxisValue(OuyaController.AXIS_LS_Y);
	}
	return true;
}

Tämän jälkeen voit tehdä ohjaimen antamilla komennoilla mitä itse haluat.
Koko koodin ja Eclipse projektin saat ladattua Githubista:
https://github.com/Androidkehitys/OUYA

Onnea pelikehitykseen! Palataan leikkimään telkkujen kanssa, kun Cromecast saapuu postissa.

Porttautuva koodi mobiilissa

“Write once, run anywhere” oli Javan silver bullet -mantra aikanaan. Eli kirjoitat koodin kerran, käännät sen kerran ja ajat missä vaan tuetussa ympäristössä ilman että ohjelmasi tarvitsee liiemmin tietää ajonaikaisesti mikä käyttöjärjestelmä / rauta ympäristössä on käytössä. No, vaikkei tuohon ole vieläkään – 18 vuotta myöhemmin – ole ihan päästy, on Javan ja vaikkapa Pythonin kaltaisten tulkattujen/scriptauskielten kanssa useimmiten paljon helpompaa elää monialustaympäristössä kuin natiivikoodin. Mutta entä jos halutaankin kehittää natiivisovellus jossa uudelleenkäytetään mahdollisimman suuri osa koodista sen sijaan että N alustaa varten tehdään N toteutusta? Vaikka nykyisten mobiililaitteiden rautaspeksejä (kotitehtävä: montako 25MHz i386 CPU:ta tarvitaan tuottamaan Samsung Galaxy S4:n CPU-teho?) ihmetellessä ei parhaalla omallatunnollakaan voi sanoa resurssien puutteen pakottavan natiivikoodiin, on sen käyttö esim. Android-alustallakin perusteltua sovelluksissa joiden on puristettava se viimeinenkin pisara suorituskykyä irti laitteesta.

Luonnollisesti kaikenlaisiin sovellustyyppeihin porttautuvaa koodia on turha yrittää ujuttaa; jos sovellus on 80%:sti käyttöliittymää eikä ole tarjolla/ei haluta käyttää kaikilla kohdealustoilla toimivaa porttautuvaa UI-kirjastoa, ei kannata väkisin lyödä päätään seinään vaan kirjoittaa toteutukset kiltisti alustan natiivi-API:a käyttäen – tai valita teknologiaksi HTML5 joka sopii webmäisiin lomakesovelluksiin kuin nyrkki silmään. Ym. UI-kirjastoa käyttämällä saatetaan myös – sen toteutuksesta toki riippuen – menettää alustan oma look-and-feel. Ihan hihasta ravistettuna porttautuvan arkkitehtuurin puolesta / vastaan listat voisivat näyttää vaikkapa tältä:

Vastaan:

  • Väkisin yritetty porttautuvuus tekee arkkitehtuurista kömpelön ja lisää turhaa kompleksisuutta
  • Alustan natiiveja ominaisuuksia saatetaan menettää (hyvässä ja pahassa)
  • Käytettäväksi valittu yhteinen ohjelmointikieli ei välttämättä ole tuttu kaikille projektin jäsenille

Puolesta:

  • Koodin joutuu kirjoittamaan vain kerran eli kehityskustannus pienenee
  • Bugit joutuu korjaamaan vain kerran
  • Koodipohjasta tulee merkittävästi pienempi jolloin sen ylläpidettävyys paranee ja siihen on nopeampaa perehdyttää uusia tekijöitä
  • Ohjelmiston luonteen salliessa paljon koodia kierrättävä arkkitehtuuri on hyvin elegantti

Esimerkkiprojekti: MMark13 – mobiilia suorituskykytestausta

Käytän esimerkkinä kuluneen vuoden aikana hiljalleen syntynyttä harrasteprojektiani MMark13, joka – hyvin tiivistetysti – on mobiililaitteiden CPU/GPU suorituskyvyn mittaamiseen tarkoitettu työkalu. Vastaavalla idealla (joskin merkittävästi isommalla tiimillä & budjetilla :)) rakennettu sovellus löytyy Desktop-puolelta, monelle tuttu suomalaista tekoa oleva Futuremark:in 3DMark. Ja ei, en ole suoraan rinnastamassa harrastelijatuotostani PC-maailman (Ja Futuremark:han puuhaa parasta aikaa myös mobiilibenchmarkingia, Androidille se jo ilmestyikin! -toim.) uraauurtavaan tuotteeseen.. no joka tapauksessa, projekti lähti liikkeelle halusta hioa/päivittää OpenGL ES 2.0 taitoja ja havaittuani ettei tuossa vaiheessa (alkuvuosi 2012) juuri vastaavia sovelluksia ollut tarjolla (paitsi kaupallinen ja maksullinen GLBenchmark 2.5 – joskin tuolloin vain iOS:lle), valitsin kyseisen sovellusalueen jo siksikin että porttautuvan arkkitehtuurin hyväksikäyttäminen laajalti tarjosi ainutlaatuisen edun: kun jokaisella alustalla ajettava koodi on 100% sama niiltä osin jotka grafiikan piirtoon / mittauksiin vaikuttavat, voidaan tuloksia verrata 1:1 toisiinsa alustojen välillä ilman että tarvitsee napista Javan tehottomuudesta Androidilla.

 

MMark13 koodin rakenne

 

Koodin uudelleenkäytön suunnittelussa on hyvä lähteä liikkeelle siitä että kartoittaa tarkasti kohdealustansa ja niiden tarjoamat asiat ja sitten hahmottelee niiden pohjalta suurimman yhteisen nimittäjän. Valitsin kohdealustoikseni iOS (Applen laitteet) ja Qt (Android, kaikki merkittävät desktop-käyttöjärjestelmät, Meego, Blackberry10, Symbian, Sailfish OS, Ubuntu Phone, luultavasti myös Tizen ja Firefox OS). Kun alustat ovat tiedossa, aloittaa voi vaikka ohjelmointikielestä; se on valinta jota alemmalle tasolle ei juuri pääse. C olisi aina varma valinta, mutta puhtaalla C:llä syntyy tulosta kovin hitaasti ja dynaamisten tietorakenteiden / merkkijonojen / jne käsittely on melko kömpelöä. Valitsinkin siksi pykälää korkeamman abstraktiotason eli C++:n. Ohjelmointikielen version valintaan kannattaa paneutua myös hetken eikä sännätä käyttämään heti uusinta; itse en uskaltanut vielä tähän projektiin valita tuoreinta C++11 versiota vaikka se oliskin tarjonnut muutamat elämää helpottavat asiat kuten “auto” tyypin muuttujille, sisäänrakennetun säiekirjaston ja paljon muuta. Sittemmin olen perehtynyt tarkemmin C++11 feature support matriisiin merkittävillä kääntäjillä (GCC, clang, MSVC++) ja vaikuttaa että aika on kypsä sen käyttöönotolle.

Ohjelmointikielen valinnan jälkeen seuraava looginen askel on kartoittaa APIen/ulkoisten kirjastojen tarve ja koettaa vaikkapa proof-of-concept -hengessä varmistaa että kirjastovalinnat täyttävät tarpeet. MMark13 -projektissa tarvitsin säikeistystä, joten käytin hyväkseni tietoa että kaikki kohdealustani ovat UNIX-perillisiä ja siten tarjoavat POSIX threads (pthreads) paketit. Tarvittiin myös JSON kirjastoa; pienellä googlauksella löysin “jsoncpp” paketin jonka koodi/dokumentaatio oli poikkeuksellisen laadukkaan oloista ja joka istui suoraan tarpeisiini. Kenties tuosta löydöksestä riemuitessani kirjoitin seuraavan askeleen eli tuloslaskelma-JSON lähetyksen palvelimelle abstraktion läpi siten että kirjoitin alustoille omat toteutuksensa sen sijaan että olisin käyttänyt ilmiselvää ratkaisua libcurlia. Tajusin mokani vasta projektin loppumetreillä enkä rupea yleensä korjaamaan jotain joka toimii, joten saivat jäädä. Lisäksi otin mukaan simppelin C++ MD5-toteutuksen. Halusin toteuttaa softani käyttöliittymän alustariippumattomasti siten että se käyttäytyy kaikilla laitteilla samalla tavalla ja myös näyttää samalta. Etsinkin melko laajalti OpenGL-pohjaista Widget-kirjastoa mutta löytämäni paketit olivat joko aivan järjettömiä bloatteja tai sitten erittäin huonolaatuisen näköistä koodia (usein molempia) että päädyin kirjoittamaan hyvin lightweight toteutuksen aivan alusta erään intensiivisen viikonlopun aikana. Eli ainakin yksi pyörä tuli keksittyä uusiksi – toisaalta aikaa ei tuohon uponnut paljoa, lopputulos miellyttää silmää, homma oli erittäin hauskaa ja lisäksi vieläpä opin paljon.

Lisäksi tarvittiin seuraavat asiat alustariippuvasti: softan bootstrapping, softa/hardisinformaation penkomista laitteelta, joitain sekalaisia pikkujuttuja sekä resurssifilejen lukeminen systeemistä – tähän olisi voinut käyttää ihan fread():ia mutta halusin toisaalta käyttää Qt:n mainiota resource systeemiä jonka kanssa tuo taas ei toimi. Alla muutama konkreettinen koodiesimerkki alustariippuvasta koodin jaosta:

CommonFunctionsQT.cpp:

std::string RandomUuid()
{
    // .mid(1,36) strips the curly braces added by toString(), duh
    return std::string(QUuid::createUuid().toString().mid(1,36).toUtf8());
}

CommonFunctionsIOS.cpp:

std::string RandomUuid()
{
    CFUUIDRef theUUID = CFUUIDCreate(NULL);
    CFStringRef cfuuid = CFUUIDCreateString(NULL, theUUID);
    CFRelease(theUUID);
    NSString* uuid = (NSString*)cfuuid;
    [uuid autorelease];

    return std::string([uuid UTF8String]);
}

Kurkistetaan vielä korkealla tasolla ohjelmiston toimintaa ja sitä miten alustariippuva ja porttautuva osa koodia pelaavat yhteen. iOS-buildissa tarvittava wrapperkoodi on luonnollisesti Objective-C(++):aa; kokemus osoitti ettei sen ja C++ -maailman sekoittaminen keskenään ole triviaalia tai kovin eleganttia, ja kehotankin eristämään kyseisen rajapinnan mahdollisimman pienelle alalle. Käytännössä toimiva pattern on antaa Objective-C++ koodille yksisuuntainen has-a -patternin mukainen suhde porttautuvaan C++ koodiin ja rakentaa iOS-specific toteutus C++ luokalle joka hoitaa callbackien tarvitseman alustariippuvan koodin. Qt maailma on natiivisti C++:aa, joten siellä ei tarvittu taikatemppuja. Ohjelman toimintalogiikka pääpiirteittäin:

  1. Alustariippuvainen osa ottaa vastaan inputit, muuttaa niiden datatyypit alustariippumattomaan muotoon ja välittää ne eteenpäin allaolevalle porttautuvalle osalle. Käytännössä inputit ovat:
    • Redraw timer. iOS:ssa CADisplayLink, Qt:ssä QTimer.
    • Käyttäjän kosketukset. iOS:ssa UITouch*, Qt:ssa QTouchEvent.
  2. Alustariippumaton osa päivittää tilan / menussa ollessa UI:n tilan touchien / kuluneen ajan avulla
  3. Uuden tilan perusteella piirretään uusi frame sekä mm. aloitetaan fysiikkalaskenta seuraavaa framea varten
  4. Tarvittaessa kutsutaan abstraktoituja callbackeja kun tarvitaan alustariippuvaa toiminnallisuutta; esimerkiksi kun ladataan testin vaatimia tekstuureita levyltä tai halutaan lähettää tulokset serverille

Jollekulle on saattanut tätä lukiessa tulla mieleen että mitenkäs se Windows Phone sitten? WP8 pois jättämiseen kohdealustojen joukosta on oikeastaan vain yksi syy – OpenGL APIn puute. Vaikka ymmärtääkseni WP8 hardis on OpenGL ES 2.0 yhteensopivaa, Microsoft ei tapansa mukaan tarjoa standardia rajapintaa vaan pakottaa kehittäjät porttaamaan koodinsa Direct3D:lle. Ja koska softani tärkeimpiä ominaisuuksia on olla suoraan vertailukelpoinen ympäristöjen välillä, ei tuo oikein ollut optio. Jos Googlen ANGLE projektista tulee ikina luotettava port WP:lle *tai* jostain taivaasta putoaa OpenGL ajuri, teen ilomielin myös WP-buildin. Tämän vuoden aikana on tarkoitus suoltaa ulos buildit ainakin Blackberry10:lle, Sailfish OS:lle sekä Ubuntu Phonelle riippuen siitä saadaanko moisia laitteita käsiimme.

Hyvää kevättä kaikille!

Linkkejä

Lisää luettavaa:

Push-viestit ja push-palvelin – mitä ne ovat?

Viimeisen kahden vuoden aikana push-viestien osuus mobiiliviestinnässä on kasvanut räjähdysmäisesti uusien älykkäämpien palvelujen myötä. Seuraavassa pyrimme avaamaan mitä push-viestit ovat sekä niiden hyötyjä ja mahdollisuuksia. Tulemme julkaisemaan myöhemmin myös push-viestintäteknologiaa ja -arkkitehtuuria tarkemmin käsittelevän artikkelin.

Push-viestejä voitaisiin kuvata esimerkiksi seuraavalla virkkeellä:

Push-viestit ovat viestejä, jotka laitteeseen saapuessaan lähes poikkeuksetta herättävät käyttäjän huomion. Käyttäjä voi viestin otsikosta riippuen joko unohtaa sen tai perehtyä siihen ja saada mahdollisesti lisää tietoa asiasta.

Kuvauksen mukaisesti push-viestejä voitaisiin verrata tekstiviesteihin, sillä lisäyksellä, että ne aina liittyvät puhelimelle ladattuun sovellukseen.

Mainonnan ja markkinoinnin näkökulmasta push-viestit eroavat merkittävästi tekstiviesteistä tai muista digitaalisista viestintämuodoista. Digitaalinen mainonta tekstiviestien avulla saattaa monien mielestä olla tunkeilevaa ja sähköpostin välityksellä tapahtuvana persoonatonta sekä harvoin tunteita herättävää. Push-viestit sen sijaan saapuvat aina käyttäjän sallimana, näkyvät reaaliaikaisesti laitteessa ja voivat sisältää monipuolista interaktiivista sisältöä.

Henkilökohtainen merkityksellisyys

Yksilön profilointi on perinteisessä mainonnassa usein pulmallista ja sen toteutus sekä järkevästi että käytännöllisesti on hankalaa, sillä käyttäjien taustat pitää kartoittaa ja sen perusteella jakaa käyttäjät lokeroihin. Käyttäjien profiilitietojen muuttuminen puolestaan ei välttämättä koskaan saavuta palveluiden tarjoaa. Tällainen tilanne saattaa olla kaikille vahingollista. Mainostaja tekee turhaa työtä ja tuhlaa resurssejaan. Viestin saajan osalta roskaviestinnäksi muuttuneen yhteydenottojen käsittely voidaan kokea jopa ärsyttäväksi, mikä saattaa herättää negatiivisia tunteita palvelun tarjoajaa kohtaan.

Push-viestien vastaanottamisen sallimisen mahdollisuus mobiilisovelluksessa estää turhien viestien saapumisen loppukäyttäjille. Viestien lähettämisen salliminen myös ennakoi vastaanottavampaa suhtautumista asiasisältöön. Mobiilisovellus, joka on yhteydessä push-palveluun, voi kerätä ja pitää ajantasalla käyttäjien palvelutietoja, listaa kiinnostuksen kohteista ja sekä esimerkiksi paikkatietoja – käyttäjän sen salliessa. Erityisesti paikkatietojen hyväksikäyttö on vielä vähän käytetty “se suuri” mahdollisuus; paikkatietoisuus yhdistettynä käyttäjien kiinnostuksien kohteisiin tuo merkityksellisyyttä ja korostaa palvelun tarjoajan kunnioitusta yksilöä kohtaan.

Push-viestit saavuttavat kohdeyleisön välittömästi

Ihmisillä on nykyään hyvin usein monia sähköpostitilejä: todennäköisesti tietty tili varattuna henkilökohtaiseen viestintään ja jokin toinen tai toiset tilit yleisempään viestintään, kuten rekisteröitymisiin ja muihin harvemmin käytettyihin palveluihin. Viestin ja loppukäyttäjän kohtaaminen saattaa tapahtua vasta päivien tai viikkojen kuluttua itse lähetystapahtumasta, koska käyttäjät eivät välttämättä seuraa aktiivisesti kaikkia tilejään.

Push-viestit saavuttavat vastaanottajan viimeistään muutaman minuutin sisällä lähetyksestä. Käyttäjien voidaan olettaa saaneen ja huomioineen viestin lähes välittömästi, sillä puhelin värisee, soittaa mahdollisesti merkkiäänen ja ruudulla tapahtuu visuaalinen päivitys.

Pushin avulla sisältöä mobiiliviestintään

Niin massaviestit kuin käyttäjien profiloinnin kautta yksilöidyt viestit kannattaa muotoilla huolellisesti ja sisällyttää niihin jotain, joka oikeuttaa välittömän yhteydenoton käyttäjiin.

Pelkkä tekstitieto,esimerkiksi vaikkapa “Liikkeemme on auki tänäänkin klo 9-18” voi olla paikallaan sillöin tällöin muistuttamassa palvelun olemassaolosta. Suositeltavampaa olisi kuitenkin antaa käyttäjille arvokkaampaa informaatiota, jota epush-viestipalvelua käyttämättömät eivät tietäisi. Esimerkiksi “Muotisuunnittelijamme on tänään liikkeessämme klo 18 asti antamassa henkilökohtaisia vinkkejä. Skumppaa 50 ensimmäiselle!” -tyyppinen viesti tuo lisäarvoa viestiin ja on myös käyttäjille syy olla liittyneenä viestipalveluun. Varsinkin lyhytaikaiset kaikille tarjottavat tiedotteet ja edut houkuttelevat palvelusta kiinnostuneet liittymään palveluun saadakseen tiedon tapahtumista ensimmäisten joukossa.

Push-viestivaihtoehdot

Push-viestit ovat perusidealtaan samankaltaisia kaikissa laitteissa: viesti saapuu, se mahdollisesti avataan ja siihen sidottu mobiilisovellus käynnistyy ja näyttää lisätietoa asiasta.

Applen iOS-laitteilla push-viestit tulevat laitteen ruudulle ja käyttäjä voi reagoida siihen välittömästi tai palata asiaan myöhemmin uudelleen viestikeskuksen kautta. Näkyvän viestisisällön lisäksi viestiin liitetään usein myös käyttäjälle näkymätöntä tietoa, kuten mikä merkkiääni soitetaan viestin saapuessa tai mitä dataa palveluun halutaan välittää sisällönpäivittämisen mahdollistamiseksi.

Windows Phone -laitteille on tarjolla puolestaan usean tyyppisiä push-viestejä, joista perusvaihtoehto (“toast”) on samankaltaisin muiden alustojen push-viesteihin verrattuna. Windows Phone -laitteelle on mahdollisuus lähettää myös raw- ja tile-viestejä. Raw-viestit tarjoavat mahdollisuuden palvelulle syöttää tietoa (esimerkiksi kuponki) mobiilisovellukseen taustalla, ilman että sovelluksen pitää hakea tietoa erikseen.

Tile-viesteissä puolestaan palvelu voi syöttää sovellustiileen (vastaa muilla alustoilla suurin piirtein ikonia työpöydällä) taustakuvia, tekstejä ja numerotietoa. Tämän avulla voidaan toteuttaa esimerkiksi käyttäjän työpöydän uutissovelluksen ikonin sisällönmuutos tai vastaava tilapäivitys.

Android-laitteilla mahdollisuuksia on useita. Käytännössä kehittäjä voi päättää mitä tehdään push-viestin tullessa. Yleisin käytäntö kuitenkin on, että käyttäjälle näytetään notifikaatti, jonka hän voi avata silloin kun on siihen sopiva hetki. Voidaan kuitenkin tehdä monimutkaisempiakin toteutuksia — esimerkiksi käsketään laite lataamaan jokin iso tiedosto ja vasta sen jälkeen ilmoitetaan käyttäjälle notifikaatilla. Käyttäjälle ei ole pakko ilmoittaa push-viestistä mitään, vaan se voi pelkästään olla taustalla tapahtuvaa logiikkaa.

Toimintaympäristö

Push-viestipalvelun käynnistämisen jälkeineen tapahtuva viestien lähetys voi parhaimmillaan olla varsin yksinkertaista ja suoraviivaista. Palvelun tarjoaja luo uuden push-viestin syöttämällä lähettävässä palvelussa lomakkeeseen viestin tiedot. Lähetysnapin painamisen jälkeen tiedot siirtyvät push-viestipalvelimelle, joka lähettää viestin edelleen Applen, Googlen ja Microsoftin kautta mobiililaitteille. Edellä kuvattu toimintatapa vaatii kuitenkin sen, että palvelun kehittäjä on joko itse kehittänyt push-palvelimen viestien välittämiseen tai käyttää tarjolla olevia kaupallisia vaihtoehtoja.

Qvik on toteuttanut push-palvelimen ja tarjoaa sitä mielellään kaikkien asiakkaiden käyttöön. Jatkamme Push-teknologiasta ja viestintäarkkitehtuurista tarkemmin seuraavassa artikkelissa.

 

Kuva 1. Push -viestipalvelin lähettää viestit mobiilikäyttäjille

 

Lua mobiilisoftassa

Käyttämällä skriptikieliä mobiiliohjelmoinnissa voidaan mm. edistää koodin jakamista eri alustojen kesken. Varsinkin peliohjelmoinnissa skriptien käyttäminen on suosittua: Pelilogiikkaa on helposti paljon ja sen kaiken kirjoittaminen erikseen eri ohjelmointikielillä eri alustoille olisi aikaavievää ja virhealtista. Eräs varsin suosittu skriptikieli on Lua. Lua on suhteellisen helppo yhdistää toiseen projektiin ja siihen on helppo luoda applikaatiospesifisiä laajennuksia. Lua on lähtökohtaisestikin suuniteltu laajennuskieleksi ja verrattuna esim. Pythoniin Lua on hyvin kevyt. Tässä artikkelissa opastetaan, miten Luaa voi käyttää iOS- ja Android-projekteissa.

Vaihe 0 — Luan lähdekoodi

Hae Luan lähdekoodi http://www.lua.org/ftp/.

Huomaa, että Lua on muuttunut jonkin verran versioiden välillä. Tämä ohje on kirjoitettu versiolle 5.2.1. Jotkin aiemmalle versiolle kirjoitetut Lua-ohjelmat eivät välttämättä toimi suoraan uusimmalla versiolla. Näitä ohjeita voi soveltaa myös muihin versioihin, mutta ne eivät välttämättä toimi sellaisenaan.


iOS

Vaihe 1 — Lua-kirjasto

  1. Luo uusi kirjastoprojekti. Tässä esimerkissä olen antanut projektille nimeksi Lua521 Luan versionumeron mukaan.Luo uusi kirjastoprojekti
  2. Poista projektiin automaattisesti luodut ProjektinNimi.m ja .h -tiedostot.
  3. Lisää projektiin Luan lähdekoodipaketista src-hakemiston sisältö lukuunottamatta tiedostoja lua.c, luac.c ja Makefile.
  4. Mene projektin Build Phases -ikkunaan ja lisää Copy Headers -vaihe.
    Build Phases
    Lisää vaihe
  5. Lisää Copy Headers -vaiheeseen projektin tiedostot lauxlib.h, lua.h, lua.hpp, luaconf.h ja lualib.h ja siirrä ne Public-osaan.
    Copy Headers -vaihe
  6. Lua-kirjasto on nyt valmis käytettäväksi.

Vaihe 2 — Lua-kirjaston käyttäminen toisessa projektissa

Esimerkkinä käytän kokonaan uutta projektia, mutta Luan lisääminen olemassaolevaan projektiin tapahtuu samoin.

  1. Lisää projektiin viittaus Lua-kirjastoprojektiin. Tämä tapahtuu kuten minkä tahansa muunkin olemassaolevan tiedoston lisääminen. Tässä tapauksessa lisättävä tiedosto on kirjastoprojektin .xcodeproj-projektitiedosto.
    Lisätään viittaus kirjastoon
    Lisättävä tiedosto on kirjastoprojektin .xcodeproj-tiedosto
  2. Aseta projektin targetti riippuvaiseksi Lua-kirjaston targetista.
  3. Lisää Lua-kirjasto linkitettäviin kirjastoihin.
    Appin Build Phase konffit
  4. Lisää Header Search Pathsiin polku $(BUILT_PRODUCTS_DIR)/usr/local/include.
    Header Search Paths
  5. Nyt projektissa voidaan käyttää Luaa.

Android

Vaihe 1 — Lua-kirjasto

Lua on kirjoitettu C-kielellä, joten Androidissa tarvitsemme NDK:ta sen kääntämiseen. Kätevintä on luoda siitä NDK-moduli. Android NDK:n ja NDK-modulien käytön opastaminen tässä olisi hieman liian työlästä, ja NDK:ssa on varsin hyvät dokumentaatiot ja esimerkit. Mainittakoon joka tapauksessa, että jos et ole aiemmin käyttänyt ja/tai luonut NDK-moduleita, helpointa on luoda yksi hakemisto jonka alle sijoitat kaikki NDK-modulit. Tämän hakemiston polku pitää olla NDK_MODULE_PATH-ympäristömuuttujassa.

  1. Luo uusi hakemisto NDK-modulihakemiston alle (tämän hakemiston nimi on luotavan modulin nimi).
  2. Kopioi tähän hakemistoon Lua-lähdekoodipaketin src-hakemiston sisältö lukuunottamatta tiedostoja lua.c, luac.c ja Makefile.
  3. Luo tähän hakemistoon seuraavansisältöinen Android.mk-tiedosto:
    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE := liblua521
    LOCAL_CFLAGS := -D"getlocaledecpoint()='.'"
    LOCAL_SRC_FILES := \
    	lapi.c \
    	lauxlib.c \
    	lbaselib.c \
    	lbitlib.c \
    	lcode.c \
    	lcorolib.c \
    	lctype.c \
    	ldblib.c \
    	ldebug.c \
    	ldo.c \
    	ldump.c \
    	lfunc.c \
    	lgc.c \
    	linit.c \
    	liolib.c \
    	llex.c \
    	lmathlib.c \
    	lmem.c \
    	loadlib.c \
    	lobject.c \
    	lopcodes.c \
    	loslib.c \
    	lparser.c \
    	lstate.c \
    	lstring.c \
    	lstrlib.c \
    	ltable.c \
    	ltablib.c \
    	ltm.c \
    	lundump.c \
    	lvm.c \
    	lzio.c
    
    LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
    include $(BUILD_STATIC_LIBRARY)

    Android.mk-tiedoston rivi LOCAL_MODULE := liblua521 määrää luotavan kirjaston nimen — sen voi muokata haluamakseen.

  4. Lua-kirjastomoduli on valmis käytettäväksi

Vaihe 2 — Lua-kirjaston käyttäminen toisessa projektissa

Koska itse Lua-kirjasto on NDK-moduli, pitää sitä myös käyttää NDK:lla käännettävän koodin kautta. Ohjeita NDK:n käyttöön saa muualta. Lua-kirjastomodulia käytetään kuten mitä tahansa NDK-modulia: Projektin Android.mk-tiedostoon lisätään tieto linkitettävästä kirjastosta, ja tiedoston lopussa kutsutaan import-module-makroa. Esimerkkiprojektini Android.mk-tiedosto näyttää seuraavanlaiselta:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE  := luatest
LOCAL_CFLAGS := -std=c99 -Wall -Werror
LOCAL_SRC_FILES := luatest.c
LOCAL_STATIC_LIBRARIES := lua521
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

$(call import-module,lua521)

Kiinnostavat rivit ovat siis LOCAL_STATIC_LIBRARIES := lua521, jossa linkitetään Lua-modulin luoma liblua521-kirjasto, sekä $(call import-module,lua521), jolla sisällytetään projektiin lua521-niminen Lua-moduli.


Vaihe 3 — Lua-koodin ajaminen

Jokaiselle sopivia tyhjentäviä ohjeita on hankala kirjoittaa, joten valaisen asiaa muutamalla yksinkertaisella esimerkillä. Näitä esimerkkejä soveltamalla päässee yllättävän pitkälle. Kaikki esimerkkikoodi on C:tä (ei Objective- tai -++); koodiesimerkkien yhdistäminen muuhun koodiin, oli kyse sitten Android- tai iOS-koodista, jätetään harjoitustehtäväksi.

Yksinkertaisen skriptin ajaminen

Lua-kirjaston funktiot on esitelty kolmessa .h-tiedostossa: lua.h, lualib.h sekä lauxlib.h. Koodiesimerkit on kirjoitettu olettaen että kaikki kolme otsikkotiedostoa on sisällytetty lähdekooditiedostoon. Huom! Nämä ovat nimeomaan C-headereita; jos Luaa käytetään C++-koodissa, sisällytettävä tiedosto on lua.hpp (ei muita).

Seuraava koodinpätkä ajaa lyhyen Lua-skriptin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
static void luaExample() {
     lua_State* lua = luaL_newstate();
     luaL_openlibs(lua);
     const char* script = “print(\”Printing from Lua!\”)\n”;
     luaL_loadstring(lua, script);
     int err = lua_pcall(lua, 0, 0, 0);
     if(err != LUA_OK) {
          const char* errMsg = lua_tostring(lua, -1);
          printf(“Error while calling Lua function: %s”, errMsg);
     }
     lua_close(lua);
}

Rivillä 2 luodaan uusi lua_State-olio. Tämä olio on käytännössä kokonainen Lua-tulkki. Tapauksesta riippuen appissa voisi olla yksi lua_State koko sen elinkaaren ajan, tai siinä voidaan vaikka luoda ja tuhota lua_Stateja tarpeen mukaan. Kutsun palauttama olio on periaatteessa käytettävissä suoraan, mutta käytännössä aina pitää tehdä myös rivillä 3 oleva kutsu, joka lataa Lua-tulkkiin Luan standardikirjastot.

Rivillä 5 ladataan merkkijonosta Lua-funktio. Tässä kohtaa lienee parasta paneutua hieman Lua-tulkin C-APIin. Lua-tulkilla on oma operandipino. Suurin osa C-API-kutsuista käsittelee tätä pinoa. Esimerkiksi Lua-funktion kutsuminen tapahtuu siten, että pinoon työnnetään ensiksi kutsuttava funktio ja sitten funktion kaikki argumentit ensimmäisestä aloittaen. Tämän jälkeen kutsutaan lua_call()- tai lua_pcall()-funktiota. Tämä kutsu poistaa funktion ja argumentit pinosta, kutsuu funktiota ja funktion palattua työntää funktion mahdolliset paluuarvot pinoon ensimmäisestä aloittaen. Kun uusi Lua-tulkki luodaan, pino on tyhjä. Kutsu luaL_loadstring()-funktioon lukee annetusta merkkijonosta Lua-funktion ja työntää sen pinon päällimäiseksi. Tämän kutsun jälkeen siis pinon päällimmäinen (ja ainoa) olio on tuo Lua-funktio (tai virheilmoitus, jos funktion lataaminen epäonnistui).

Rivillä 6 kutsutaan edellisellä rivillä ladattua funktiota. Käytettävä C-funktio on lua_pcall(). Ero lua_call()- ja lua_pcall()-funktioiden välillä on, että lua_pcall() (p = protected) palauttaa virhekoodin jos kutsu epäonnistuu. Funktio lua_call() sen sijaan käyttää Lua-tulkin virheenhallintajärjestelmää, joka lähtökohtaisesti tulostaa virheilmoituksen ja kaataa koko appin. Kannattaa siis lähes poikkeuksetta käyttää lua_pcall():ia. Lua_pcall():lle annettavat argumentit ovat: lua_State, argumenttien määrä, paluuarvojen enimmäismäärä, ja message handlerin indeksi pinossa (0 ei ole validi indeksi, vaan se merkitsee, että message handleria ei ole; pinon indeksit alkavat 1:stä). Tässä tapauksesssa emme välitä yhtään argumenttia funktiollemme, emmekä ole kiinnostuneita sen mahdollisista paluuarvoista.

Rivillä 7 tarkistetaan, onnistuiko funktiokutsu. Jos paluuarvo on LUA_OK (0), funktiokutsu onnistui, ja pinossa on nyt funktion mahdolliset paluuarvot, kuitenkin enintään niin monta arvoa kuin lua_pcall()-kutsussa oli määrätty. Tässä tapauksessa pino on siis tyhjä. Muu paluuarvo merkitsee, että funktiota suoritettaessa tapahtui virhe. Paluuarvo kertoo virheen laadusta jotain (katso tarkempaa tietoa Lua Reference Manualista), mutta tässä tapauksessa tarkastelemme vain virheilmoitusta. Jos kutsu epäonnistuu, pinoon työnnetään (funktion paluuarvojen sijaan) virheilmoitus. Rivillä 8 haemme virheilmoituksen pinosta. Indeksi -1 tarkoittaa pinon päällimmäisintä alkiota (negatiiviset indeksit lasketaan pinon päältä alkaen). Tässä esimerkissä tänne ei koskaan tulla, mutta voit kokeilla virheenhallintaa muokkaamalla Lua-skriptin virheelliseksi.

Rivillä 11 lua_State tuhotaan. Lua_close()-kutsun jälkeen lua_Statea ei voi enää käyttää. Jos appissa luodaan Lua-tulkkeja tarpeen mukaan, on tärkeää muistaa tuhota ne lua_close()-kutsulla kun niitä ei enää tarvita, etteivät ne jää varaamaan muistia.

Argumentit ja paluuarvot

Seuraavassa on esimerkki Lua-funktiosta joka ottaa argumentteja ja palauttaa arvoja:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static void argsAndRetsExample() {
    lua_State* lua = luaL_newstate();
    luaL_openlibs(lua);

    const char* script = "\
local arg1, arg2 = ...\n\
print(\"Lua function was called with \" .. arg1 .. \" and \" .. arg2)\n\
local ret1 = arg1 + arg2\n\
local ret2 = arg1 * arg2\n\
return ret1, ret2\n\
";
    luaL_loadstring(lua, script);

    lua_Number arg1 = 3.7, arg2 = -.1;
    printf("calling Lua function with %f and %f\n", arg1, arg2);
    lua_pushnumber(lua, arg1);
    lua_pushnumber(lua, arg2);
    int err = lua_pcall(lua, 2, 2, 0);
    if(err == LUA_OK) {
        lua_Number ret1 = lua_tonumber(lua, 1);
        lua_Number ret2 = lua_tonumber(lua, 2);
        printf("Lua function returned %f and %f\n", ret1, ret2);
    } else {
        const char* errMsg = lua_tostring(lua, -1);
        printf("Error while calling Lua function: %s\n", errMsg);
    }

    lua_close(lua);
}

Ajettaessa tämä tulostaa seuraavaa:

calling Lua function with 3.700000 and -0.100000
Lua function was called with 3.7 and -0.1
Lua function returned 3.600000 and -0.370000

Tarkastellaan ensin Lua-koodia. LuaL_loadstring()-funktiolla ladattu Lua-funktio on tyypiltään nk. vararg-funktio. Kaikki sen saamat argumentit on luettavissa vararg-lausekkeesta joka on muodoltaan kolme peräkkäistä pistettä (...). Lause local arg1, arg2 = ... rivillä 6 asettaa (paikallisen) muuttujan arg1 arvoksi funktion ensimmäisen argumentin ja muuttujan arg2 arvoksi toisen argumentin. Jos argumentteja ei ole tarpeeksi, muuttujan arvoksi tulee nil. Rivillä 10 Lua-funktio palauttaa arvoja. Lua-funktio voi palauttaa useita arvoja, tässä tapauksessa palautetaan kaksi arvoa.

Rivillä 12 ladataan funktio kuten edellä. Funktion argumentit työnnetään pinoon funktion jälkeen. Tässä kutsumme funktiota kahdella argumentilla. Riveillä 16 ja 17 työnnetään argumentit pinoon. Rivillä 18 suoritetaan kutsu. Argumenttien määrä on 2, ja haluamme enintään 2 paluuarvoa. Kutsun onnistuttua lukemme riveillä 20 ja 21 paluuarvot pinon pohjalta (funktio ja argumentit ovat poistuneet pinosta).

C-funktioiden kutsuminen Lua-koodista

Esimerkki C-funktion kutsumisesta:

1
2
3
4
static int myCFunction(lua_State* lua) {
    int numArgs = lua_gettop(lua);
    printf("myCFunction called with %d args\n", numArgs);
    for(int i = 1; i

Ajettaessa tämä tulostaa seuraavaa:

myCFunction called with 2 args
arg 1: 5
arg 2: bacon
myCFuntion returned 3.141592 and second retvalue

Lua-koodista kutsuttava C-funktio on muuten samankaltainen kuin mikä tahansa C-funktio, mutta se saa argumenttinsa ja palauttaa arvonsa Lua-pinon kautta. Kaikkien Luasta kutsuttavien funktioiden on otettava yksi argumentti tyypiltään lua_State* ja palautettava int. Kun funktiota kutsutaan, annetussa lua_Statessa on pinossa funktion saamat argumentit (ensimmäinen pinon pohjimmaisena jne.), eikä mitään muuta. Argumenttien määrä saadaan tietää pinon korkeudesta. Argumenttien tyypit voidaan myös selvittää. Funktio palauttaa arvoja siten, että kaikki paluuarvot työnnetään pinoon ensimmäisestä aloittaen ja funktion varsinainen paluuarvo (C-mielessä) kertoo Lua-paluuarvojen määrän. Pinoa ei tarvitse erikseen “puhdistaa” ennen palaamista.

Rivillä 2 selvitetään argumenttien määrä. Riveillä 4-6 oleva silmukka tulostaa kaikki funktion saamat argumentit (muutettuaan ne ensin merkkijonomuotoon). Funktio palauttaa kolme arvoa; ne työnnetään järjestyksessä pinoon riveillä 8-10. Rivillä 11 palautetaan paluuarvojen määärä.

Riveillä 18-21 on taulukko, jonka avulla C-funktio liitetään Luaan. Taulukossa on tietueita, joissa ensimmäinen arvo on funktion nimi Luassa ja toinen arvo on pointteri varsinaiseen funktioon. Viimeisenä on on tietue, jossa molemmat arvot ovat NULL. Tässä liitetään vain yksi funktio, mutta samalla voisi liittää useammankin funktion. Rivi 23 lukee em. taulukon ja luo Lua-taulukon, jossa on annetuilla nimillä viittaukset annettuihin C-funktioihin. Tässä tapauksessa siis (Lua-)taulukko, jossa on arvolla "myCFunction" viittaus rivin 1 funktioon. Rivillä 24 tämä taulukko annetaan globaalin muuttujan "myCFunctions"-arvoksi.

Lua-koodissa rivillä 27 kutsutaan "myCFunctions"-taulukon "myCFunction"-jäsentä, ts. edellä määriteltyä C-funktiota. Funktiokutsu tehdään kuten muutkin Lua-funktiokutsut. Tässä funktiolle annetaan 2 argumenttia. Funktion 2 ensimmäistä paluuarvoa asetetaan paikallisten muuttujien a ja b arvoiksi. Kuten tulosteesta huomataan, funktion kolmas paluuarvo jää huomiotta.

Voit kokeilla panna breakpointin myCFunction()-funktioon. Ohjelman pysähtyessä ko. breakpointiin, voit tarkastella kutsupinoa ja huomata, että kutsu on tullut Lua-tulkin sisältä.

Android-listat ja -adapterit

Näyttäessämme tietoa laitteen näytöllä joudumme usein turvautumaan listoihin. Emme usein tiedä kuinka monta riviä listassa on ja ne voivat ohjelman ajoaikana muuttua. Lähes jokaisessa ohjelmassa joutuu käyttämään jotain listaa, gridiä, galleriaa tai jotain muuta Androidin AdapterViewn toteuttavaa luokkaa.

Kaikki luokat jotka jatkavat AdapterViewtä on tarkoitettu näyttämään tietoa selkeissä riveissä (list), sarakkeissa (gallery) tai ruudussa (grid). AdapterView tarvitsee toimiakseen Adapterin, jolle on annettu tarvittavat objektit. Adapterin ei kuitenkaan tule itse hallinnoida objekteja vaan säilyttää vain viittaus niihin. Objektien säilyttäminen on tämän artikkelin asian ulkopuolella. Tässä artikkelissa käydään läpi kuinka Adapterit toimivat ja mikä on paras käytäntö niiden tekemisessä. Kiinnitämme myös huomiota listan ja sen myötä myös adapterin muistin kulutukseen, sillä esimerkiksi kuvien näyttäminen listassa aiheuttaa herkästi muistin ylittämisen ja ohjelman kaatumisen.

Kuinka tehdä Adapteri

Adapteria tehdessä suosittelen käyttämään yliluokkana ArrayAdapter<näytettävä objekti> luokkaa, joka toteuttaa jo lähes kaikki tarvitsemasi metodit. Sinun tarvitsee vain käsitellä, miten jokainen adapterille antamasi objekti näytetään getView()-metodissa.

Toimivan adapterin tekemiseen sinun täytyy jatkaa ArrayAdapteria ja toteuttaa tälle adapterille construktori ja haluamasi lainen getView()-metodi. Tässä esimerkki yksinkertaisesta adapterista, joka näyttää objektin tekstiä jokaisella adapteria käyttävän AdapterView:n alkiossa.

public class TestAdapter extends ArrayAdapter<YourObject> {
public TestAdapter(Context context, int textViewResourceId, List<YourObject> objects) {
    super(context, textViewResourceId, objects);
} 

// Luo AdapterViewn ilmentymän alkio tallennetusta objektista
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // Adapteri uudelleen käyttää listassa olevia näkymiä uudelleen,
    // mutta listaa luotaessa nämä ovat null:ja ja tarvitsee alustaa.
    // Jos listan näkymä on jo asetettu ei sitä tarvitse enää uudelleen luoda
    // tehokkaassa adapterissa tämä luonti jätetään väliin, kun sitä ei tarvita.
    if (convertView == null) {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        convertView = inflater.inflate(R.layout.your_adapter_view_layout, null);
    }
    // tämän jäälkeen tarvitsee vain ottaa oikeasta adapterin alkiosta tarvittavat tiedot
    //  ja asettaa ne näkymään oikeille paikoille.
    YourObject item = getItem(position);
    // Huomaa että tekstikentän etsiminen vie turhaan tehoja ja aiheuttaa tarpeettomia viittauksia
    TextView text = (TextView)
    convertView.findViewById(R.id.adapter_item_textview);
    text.setText(YourObject.getText()); 

    return convertView; }
}

 

ViewHolder pattern: Lisää adapterin suorituskykyä

ViewHolder-malli tehostaa Adapterien käyttöä poistamalla tarpeen etsiä tarvittavat näkymä widgetit uudelleen getView()-metodissa. Mallin idea on luoda uudelleen käytettävä, Adapterin sisäinen, ViewHolder luokka, joka asetetaan uudelleenkäytettävän convertViewn sisälle tagiin. ViewHolder pitää sisällään referenssit oman convertViewnsä käytettyihin widgetteihin. Tämä poistaa widgettien etsimisen findViewById()-metodilla.

Myös jos tekstikentille tarvitsee lisätä kuuntelijoita riittää että ne tehdään vain kerran ViewHolderia luotaessa. Adapterisi toteuttaa ViewHolder mallin esimerkiksi näin:

// Luo AdapterViewn ilmentymän alkio tallennetusta objektista
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        convertView = inflater.inflate(R.layout.your_adapter_view_layout, parent, false);

        // Luo ja lisää ViewHolder omalle näkymälleen tagiksi
        convertView.setTag(new ViewHolder(convertView));
    }
    ViewHolder viewHolder = (ViewHolder) convertView.getTag();
    YourObject item = getItem(position);

    viewHolder.text.setText(item.getText());
    viewHolder.image.setImageDrawable(item.getImageDrawable());

    return convertView;
}

private static class ViewHolder {
    final TextView text;
    final ImageView image;

    public ViewHolder(View convertView) {
        text = (TextView) convertView.findViewById(R.id.adapter_item_textview);
        image = (ImageView) convertView.findViewById(R.id.adapter_item_imageview);
    }
}

Monimutkaisemmissa riveissä ViewHoldering käyttäminen helpottaa ja tehostaa listan käyttämistä, koska vaikka oikean näkymän etsiminen convertView:stä kestää vain muutamia millisekuntteja, jos sen tekee esimerkiksi kymmenen kertaa voi lista nykiä selvästi kovassa käytössä.

Vinkki:

Listat ovat hyviä näyttämään tietoa, mutta vältä tiedon muokkauksen mahdollistamista listojen tai muiden AdapterView:n sisällä, esimerkiksi EditText widgeteillä. Tallentaminen ja oikean tiedon näyttäminen, kun listan näkymiä uudelleenkäytetään tulee todella vaikeaksi.

iPhone ja Android mobiiliasiantuntijat nyt samassa blogissa

Mobiilikehitys.fi yhdistää iPhonekehitys.fi- ja Androidkehitys.fi-blogit yhdeksi.

Mobiilikehitys.fi:ssä tullaan käsittelemään sekä iPhone- että Android-kehitystä ja tutustutaan myös Windows Mobile-alustaan, sekä HTML5:sen käyttämiseen mobiilisovellusten tekemisessä. Teknisen puolen lisäksi tullaan piipahtamaan myös käyttöliittymäsuunnitelussa, katsastellaan uusimpia laitteita ja uutisoidaan alan merkittävimpiä tapahtumia.

Mobiilikehitys.fi on Qvik Oy:n työntekijöiden ylläpitämä blogi, jossa esitetyt mielipiteet eivät välttämättä vastaa yrityksen mielipiteitä.

Rahaa applikaatioilla

Hauskojen ja hyödyllisten applikaatioiden tuottaminen Androidille ei tarvitse olla pyyteetöntä työtä. Applikaatioilla voi myös pieni kehittäjä tehdä rahaa. Androidilla on kolme yleistä talousmallia, joilla voit tienata applikaatiollasi.

1) Maksulliset applikaatiot

Applikaatiolle voi asettaa hinnan, kun sen laittaa markettiin myyntiin. Ongelma on että suurin osa applikaatioista on saatavilla myös ilmaiseksi. Asiakaskuntaa voi kasvattaa tarjoamalla esimerkiksi rajoitetun version applikaatiosta ilmaiseksi.

2) Applikaation sisäiset ostokset

Harvalla yksittäisellä koodaajalla on mahdollisuutta perustaa omaa kauppaa, mistä applikaation käyttäjä voi ostaa applikaatioon uutta sisältöä. Mikrotransaktiot toimivat hyvin peleissä kuten League of Legends. Android market tukee applikaation sisäistä maksamista ja lisää tietoa löytyy Android developer -sivustolta. Google ottaa 30% maksusta.

3) Mainokset applikaatiossa

Google tarjoaa valmiita paketteja mainosten lisäämiseen applikaation AdMob yrityksen kautta. Tässä artikkelissa käsitellään, kuinka mainoksia saa lisättyä omaan applikaatioonsa. Tässä artikkelissa neuvotaan, kuinka voit lisätä mainoksia omaan Android-applikaatioosi.

Mitä tarvitset?

Valmis applikaatio marketissa. Tarvitset AdMob:lle applikaation nimen ja linkin apk-pakettiin, mieluiten muodossa market://details?id=<app.package.name>

AdMob käyttäjätunnus:

Aloita täältä: Google Mobile Ads

Tässä kohtaa ainoa hämmennystä aiheuttava kohta yksityiselle Android-kehittäjälle voi olla Business name -kohta. Kaikki nämä tiedot voi vaihtaa koska tahansa. Jos et osaa täyttää jotain kohtaa ja se on pakollinen voit laittaa siihen väliaikaisen tiedon, tämä on myös AdMobin oma suositus. Verotiedot voi jättää tässä vaiheessa tyhjäksi, mutta muista ilmoittaa verottajalle AdMoblta saamasi tulot.

Tämän jälkeen voit lisätä applikaation “Sites & Apps” valikosta. Applikaation tietoja täyttäessäsi voit lisätä keksityt tiedot aluksi, jos haluat vain ladata ja testata SDK:n lisäämistä applikaatioosi. Voit myöhemmin päivittää nämä tiedot, kun olet lisännyt applikaatiosi markettiin.

Tuottojen maksu:

Mainoksista saamiesi tuottojen maksuun suositellaan PayPalia Yhdysvaltojen ulkopuolella. Tulot on mahdollista saada myös suoraan tilillesi, mutta pankkisi saattaa periä ylimääräisiä kuluja kansainvälisistä tilisiirroista. Huomaa myös että tulot maksetaan dollareina ja pankkisi voi periä ylimääräisiä kuluja euroiksi muuttamisesta.

AdMob maksaa jokaisesta kerrasta, kun applikaatiosi mainoksia painetaan. Maksu vaihtelee monesta asiasta riippuen, mutta on joitakin senttejä per painallus.

AdMob SDK applikaatioosi

Tarvittavan SDK:n löydät täältä: http://code.google.com/mobile/ads/docs/
Samasta linkistä löytää tarvittavat ohjeet mainosten lisäämiseen. Käymme tässä läpi pääkohdat.

1) Lisää SDK applikaatioosi

2) Lisää Manifestiin

2.1) Mainos aktiviteetti:

<activity android:name="com.google.ads.AdActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"/>

2.2)

Lisää permissionit:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

3) Lisää AdView layoutiisi

<com.google.ads.AdView android:id="@+id/adView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
ads:adUnitId="YOUR_ADMOB_AD_UNIT_ID"
ads:adSize="BANNER"
ads:testDevices="TEST_EMULATOR,e78b974e6ab65648b52951e69edcb571"
ads:loadAdOnCreate="true"/>

TestDevices-atributtiin lisätään laitteen device ID ja tämän avulla saat testimainoksia laitteellesi. Poista tämä markettiin menevästä versiosta.

Tämä mainos ladataan heti, kun aktiviteettisi luodaan.
Voit itse ladata uuden sisällön mainosnäkymään:

AdView adView = (AdView) findViewById(R.id.adView);
adView.loadAd(new AdRequest());

4) ???

(Olet valmis, markkinoi applikaatiotasi sosiaalisessa mediassa)

5) Profit

Testaus Androidilla

Testaus yleisesti

Testaus, tuo ohjelmistokehityksen välttämätön paha. Testausta ei koskaan voi suorittaa täysin kattavasti, eikä ole ohjelmistoa, jossa ei olisi yhtään virhettä, paitsi ehkä Nasalla. Testauksella kuitenkin kasvatetaan merkittävästi ohjelmiston laatua ja viimeistään työelämässä tämä osa-alue on hyvä hallita.

Testaus, niin kuin kaikki muukin välttämätön epämukavuus on hyvä suorittaa kehityksen ohella, kuin sen jälkeen. On paljon helpompaa testata se mitä on juuri tehnyt, kuin kaikki mitä on tehty kehitysvaiheen aikana. Testaus kehityksen yhdeydessä  tekee koodista myös helpommin testattavaa. Jälkikäteen testattaessa voi hyvinkin olla mahdotonta tehdä testejä ilman koodin muuttamista.

Tässä artikkelissa käymme läpi Androidin testivälineitä ja -käytäntöjä.

Testit on hyvä suunnitella yksinkertaisiksi, toistettaviksi ja mahdollisimman vähän aikaa vieviksi. Hyvä ratkaisu tähän on testikoodi, jotka voi sitten vain ajaa tarvittaessa. Jokaista loppukoodin luokkaa kohden on hyvä tehdä oma testiluokkansa, joka testaa luokan julkiset metodit “riittävällä” testijoukolla. Hyvä nyrkkisääntö on, ettei testimetodeita ole koskaan liikaa, eikä yhden metodin kannata kaikkea tehdä.

Yksikkötestaus JUnitilla

Eclipseen tulee Android SDK:n mukana valmis wizard Android JUnit testiprojektien luomiseen. Yksityiskohtainen ohje JUnit testien tekemiseen ja ajamiseen löytyy developer.android.com sivustolta: Hello, Testing. Käymme muutaman yksityiskohdan läpi, oman testikoodin ja testisalkun avulla. Testisalkku on kokoelma testiluokkia, jotka suoritetaan peräkkäin. Jokaisen testiluokan voi suorittaa myös erikseen.

Android developers sivuston esimerkissä on huonosti mainittu, että testi projektiin pitää muistaa lisätä linkki testattavaan projektiin. Voit lisätä tämän projektin asetuksista (properties), “Java build path” valikosta, “projects”-välilehdestä. Tai testiprojektia luotaessa heti toisessa valikossa, joten muista painaa “next” äläkä “finnish”. Developer sivuston esimerkki on myös hiukan vanhemmasta Android SDK:sta, mutta sisältö on sama, vain luonti wizard on yksinkertaistunut.

- Testisalkku:

package fi.androidkehitys.esimerkkiapp.test; import junit.framework.Test; import junit.framework.TestSuite; public class AllTests { /** * Testisalkku, missä kaikki testiluokat lisätään. * @return Test */ public static Test suite() { TestSuite suite = new TestSuite(AllTests.class.getName()); suite.addTestSuite(EsimerkkiTesti.class); return suite; } }

Testisalkun voi ajaa suoraan “run as” ja “Android JUnit test”.

- Testiluokka:

package fi.androidkehitys.esimerkkiapp.test;

import fi.androidkehitys.applikaatio.MyActivity;
import java.util.List;
import android.test.ActivityInstrumentationTestCase2;

public class RSSTest extends ActivityInstrumentationTestCase2<MyActivity> {

    public RSSTest() {
        super("fi.androidkehitys.applikaatio", MyActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
    }

    /**
    * Testi metodi.
    *
    */
    public void testPotenssiMetodiOne() {
        assertTrue(“Metodi parametrillä 1 palautti väärän arvon”, potenssiMetodi(1) == 1);
    }
    public void testPotenssiMetodiTwo() {
        assertTrue(“Metodi parametrillä 2 palautti väärän arvon”, potenssiMetodi(2) == 4);
    }

    @Override
    protected void tearDown() throws Exception {
        getActivity().finish();
        super.tearDown();
    }
}

Testiluokan kaikki julkiset metodit ovat testi tapauksia ja suoritetaan testauksessa. Tässä testiluokassa ei tarvitsisi käyttää aktiviteettiä vaan tehdä yksinkertainen TestCase-luokan ilmentymä, mutta usein Android-metodit voivat tarvita kontekstin tai pääsyn resursseihin, joten yleisin yleisimmissä testitapauksissa tarvitset aktiviteetin parametrien käyttöön.

Aktiviteettien testaus on hiukan ongelmallisempaa yksikkötesteissä, koska view-komponenttejä ei saa käsitellä UI-säikeen ulkopuolella. Ratkaisu tähän on seuraavassa kappaleessa esiteltävä black box testaus ja Robotium framework.

Black box testaus Robotiumilla

Black box testauksella tarkoitetaan testausta, missä ei tarvitse pääsyä koodiin, vaan painellaan laatikon nappuloita. Robotiumille riittää päästä käsiksi applikaation .apk pakettiin. Black box testauksella pystyy tekemään monimutkaisiakin käyttötapaustestejä, mutta yksinkertaisuus on yleensä parempi tavoite sillä monimutkaiset testit alkavat kaatua pienilläkin muutoksilla.

Android tarjoaa rasitustestaukseen oman Monkey Runner:in, joka simuloi satunnaisia, mutta toistettavia painalluksia applikaation näyttöön. Tällä on kuitenkin mahdotonta testata jotain tiettyä tarkasti määriteltyä ominaisuutta applikaatiossa.

Robotium on voimakas työkalu Andoridin black box testaukseen. Sen käyttö ei vaadi kattavaa tietoa applikaation koodista ja käyttö on hyvin suoraviivaista. Robotiumilla voi ActivityInstrumentationTestCase2-luokkaan luoda Solo-olion, joka käyttää ja testaa käyttöliittymäelementtejä.

@Override
protected void setUp() throws Exception {
    super.setUp();
    solo = new Solo(getInstrumentation(), getActivity());
}

public void testLogin() {
    solo.clickOnButton("Kirjaudu sisään");
    assertTrue(solo.waitForText("Kirjautuminen epäonnistui", 1, 120000));
}

@Override
protected void tearDown() throws Exception {
    try {
        solo.finalize();
    } catch (Throwable e) {
        e.printStackTrace();
    }
    getActivity().finish();
    super.tearDown();
}

Kun Solo:lle on annettu Instrumentaatio ja aloitus-aktiviteetti voi navigoinnin applikaatiossa hoitaa kokonaan Solo:lla. Voit myös mennä toisiin aktiviteetteihin ilman ongelmia, jos testitapaus tätä vaatii.

Lisää JavaDoc Robotiumiin

Robotiumin käyttö helpottuu huomattavasti, kun lisäät JavaDocin Robotiumin lähdekoodiin. Sekä lähdekoodin, että JavaDocin voit ladata täältä (uusimmat tiedostot listan alapäässä). Eclipsessä testiprojektin asetuksissa (properties), Java Build Path, Libraries ja lisättyäsi Robotium Solo .jar tiedosto avaa se vasemmassa laidassa olevasta nuolesta, valitse Javadoc location ja paina edit. Tämän jälkeen valitse ulkoinen tiedosto ja etsi Robotium javadoc .jar ja lisää se archive pathiin. Nyt näet Robotiumin JavaDocit Eclipsessä.

Widgetit Androidissa

Widgetit näyttävät applikaation kaikkein oleellisimman tiedon nopeasti ja yksinkertaisesti työpöydällä. Widgettejä voi myös käyttää käynnistämään applikaatioita tai taustaprosesseja, kuten mediasoittimen.

Tässä artikkelissa toteutamme mahdollisimman yksinkertaisen widgetin, jotta saamme idean mitä kaikkea sen toimintaan saamiseen tarvitaan. Toteutamme widgetin, jossa on tekstikenttä ja nappula, nappulaa painamalla kasvatamme tekstikentässä olevaa numeroa yhdellä. Erityisesti kiinnitämme huomiota widgetin tilan päivittämiseen sekä manuaalisesti, että automaattisesti.

Esimerkki koodi: https://github.com/Androidkehitys/Androidkehitys-Widget

Widget layout

Widgetin layout toteutetaan samalla tavalla, kuin minkä tahansa aktiviteetin layout. On kuitenkin otettava huomioon, että widgetti luodaan RemoteViews-luokan ilmentymänä ja layoutissa voi näin ollen käyttää vain seuraavia layout-elementtejä:

Layoutit

Widgetit

AppWidgetProviderInfo Metadata

Tämä XML-tiedosto määrittää widgetille tarpeelliset tiedot, kuten korkeuden ja leveyden, päivitysvälin, esikatselun, layoutin, asetukset-aktiviteetin sekä widgetin koon muuttamisen vaaka ja/tai pystysuunnassa (Android 3.1).

Androidin kotinäkymä koostuu ruudukosta, jossa jokainen ruutu on kooltaan 74dp * 74dp. Widgetin tulee jättää 2dp:tä tilaa leveydessä ja korkeudessa, jotta vältetään mahdolliset virheet ajonaikaisessa koon määrityksessä kun dp:t muutetaan pikseleiksi. Käytä kaavaa (solujen määrä * 74) – 2 widgetin koon määrittelemiseen.

Päivitysväli määrittää ajan, jonka välein Android päivittää widgetin tiedot. Tämä päivitys herättää laitteen lepotilasta ja suorittaa onUpdate()-metodin AppWidgetProvider-luokassa. Ei myöskään ole takuita tapahtuuko päivitys ajallaan. Suositeltavampaa on toteuttaa widgetti niin että se päivittää vain silloin, kun sen on pakko, eikä useammin.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  android:minWidth="146dp"
  android:minHeight="72dp"
  android:updatePeriodMillis="86400000"
  android:initialLayout="@layout/widget_layout"
  >
  </appwidget-provider>

Widget Manifestissä ja painallukseen reagointi

Manifestissä widget määritellään <receiver> elementin sisälle. Tässä elementissä määrittelet oman toteutuksesi AppWidgetProvider luokan ilmentymästä ja määrittelet ne Intent lähetykset (broadcast), jotka AppWidgetProvider hyväksyy. Nämä lähetykset suoritetaan onReceive() -metodissa.

<receiver android:name=".ExampleWidgetProvider">
  <intent-filter>
    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
  </intent-filter>
  <meta-data android:name="android.appwidget.provider"
   android:resource="@xml/appwidget_provider" />
</receiver>

AppWidgetProvideria voi myös kutsua PendingIntent:llä, kuten tässä esimerkissä napin painalluksiin reagointi on toteutettu:

Intent intent = new Intent(context, ExampleWidgetProvider.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
views.setOnClickPendingIntent(R.id.start_button, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);

AppWidgetProvider

Kokonaisuudessaan omaan toteutukseesi AppWidgetProvideristä tarvitset vain onUpdate()- ja onReceive()-metodit. Voit myös tehdä tämän esimerkin mukaisesti paketin sisällä näkyvän staattisen metodin, joka päivittää widgetin grafiikat esimerkiksi konfiguraatioaktiviteetistä.