Sivuston mobiilioptimointi – miksi?

Seuraavassa lyhyesti avattuna muutamia syitä siihen miksi yritysten ja brändien kannattaa toteuttaa mobiilisivusto optimaalisen selaamisen ja käyttäjäkokemuksen varmistamiseksi.

Mobiiliselaus on erittäin vahvassa kasvussa maailmalla. Morgan Stanleyn analyytikkojen arvioden mukaan mobiili-Internetselaus ohittaa desktop selaamisen määrän vuoden 2013 aikana ja jatkaa merkittävää kasvua tämän jälkeenkin.

Huomattavaa on, että iOS laitteiden osuus selaamisesta on tällä hetkellä erittäin suuri, Android ja muut alustat kasvavat nyt ja todennäköisesti myös tulevaisuudessa huimaa vauhtia.

Mobiilidatan ja mobiililaajakaistojen määrä on myös Suomessa merkittävässä kasvussa. Mobiililaajakaistapenetraatio vuoden 2012 vaihteessa oli jo yli 30% (kaikista matkapuheliliittymistä) ja jatkaa tasaista kasvuaan. Merkille pantavaa on myös se, että noin 70% uusista myydyistä mobiililaitteista on älypuhelimia tai tabletteja joissa mobiiliselaaminen on mielekästä (Elisa 2011 osavuosikatsaus Q3, lehdistömateriaali).

Edellä mainittujen asioiden lisäksi kannatta huomioida myös muut mobiilin tuomat lisäedut verkkosivukonseptin laajentamiseen liittyen kuten lokaation hyödyntäminen, jne.

PS. Avaamme pian myös Qvikille uudet sivut jotka on – arvaatte sen – mobiilioptimoitu!

Puolet Sanomien mobiililiikenteestä iPhonesta ja iPadista

Sanoma News julkaisi pari päivää sitten tilastoja heidän palveluidensa mobiilikäytöstä. Ylipuolet liikenteestä tulee Applen päätelaitteista, Symbianista noin kolmasosa, Androidista viidennes ja Windows Phonesta vasta prosentti.

Mielenkiintoista on myös Sanomien arvio erilaisten tablet-tietokoneiden määrästä Suomessa. Sanomien mukaan Suomesta löytyy tällä hetkellä 150 000 iPadia ja noin 20 000 Android tablettia. Tablettien määrä on Suomessa siis varsin mittava, ottaen huomioon, että ensimmäisen tabletin julkaisusta on juuri ja juuri kulunut 2 vuotta.

ipad käyttäjämäärät
iPad oli monen pojan joululahjatoivelistalla,

Sanomien lehdistötiedote. 

Windows Phone 7 perehdytys – osa 1: yleistä WP7 kehityksestä, SDK ja XNA Hello World

Toimintaympäristö

WP7 kehitykseen on helppo päästä sisälle vaikkei aiempaa mobiilikehitystaustaa olisikaan. Alustan kehitysvaiheessa on juuri nyt otollinen hetki, joka mahdollistaa kenen tahansa pääsyn kehityksen terävimpään kärkeen varsin nopeasti. Microsoft julkaisi virallisen version WP7 Mango käyttöjärjestelmään perustuvasta SDK:sta syyskuussa 2011 ja uuteen käyttöjärjestelmään perustuvien puhelimien määrä kasvaa nopeasti markkinoilla. Eittämättä Nokian uusi, monia visuaalisesti miellyttävä, Lumia puhelinmallisto pohjustanee hyvän alun alustalle ja sovellusten markkinapaikalle.

Käydään pikaisesti läpi

  • WP7 SDK:sta pari sanaa,
  • SDK:n ja Zune:n asentaminen,
  • Puhelimen vapauttaminen sovelluskehitykseen (developer unlock),
  • Windowsphone Marketplace sekä
  • Ensimmäinen WP7 sovellus XNA ympäristössä.

WP7 SDK

WP7.1 SDK on ilmainen ja viimeistään kuukauden käytön jälkeen vaadittava rekisteröinti kannattaa tehdä heti, koska se tuo esiin lisäominaisuuksia Visual studio 2010 Express työkaluun.  Kannattaa huomioida SDK:n vaatimukset tietokoneen suorituskyvyn suhteen. Erityisesti vanhemmissa koneissa ongelmaksi saattaa osoittautua näytönohjain, koska DirectX 10.0 vaatimus on selkeä. Toisaalta muistin määräänkin kannattaa panostaa. Ainakin allekirjoittaneella jopa 4GB muistia tuntui tukkoiselta ja vasta 6GB antoi sulavan toiminnan emulaattoria käytettäessä. Sovelluksia voi ja tulee testata ja debugata työkalun mukana tulevalla, em. emulaattorilla, ja ennenpitkää oikeilla puhelinkoneilla.

WP7 SDK:n ja Zune:n asentaminen

Lataa ja asenna

  1. WP7.1 SDK. Seuraa ohjelman antamia asennusohjeita.
  2. Zune. (valinnainen, mikäli haluat kokeilla pelkästään emulaattorilla ensin). Seuraa ohjelman antamia asennusohjeita. Käyttämäsi puhelin täytyy liittää ohjelmaan, jotta synkronointi onnistuu.

Windowsphone marketplace

Halu julkaista omia sovelluksia tulee varsin pian mobiilikehityksen aloittamisen jälkeen, ehkä jopa ennenkin. Kehittäjän kannattaa testata työn alla oleva sovellus Visual Studio 2010 Express:in mukana tulevalla ‘Marketplace Test Kit':in avulla. Test Kit tarkistaa, että vaaditut oheismateriaalit ja perustoiminnallisuudet ovat kunnossa, sekä ajaa kevyen toiminnallisuustestin puhelimeen asennetulla sovelluksella. Sovelluskehittäjän tulee käydä läpi Test Kit:in tarkistuslista ennen julkaisua. Appsit jaetaan loppukäyttäjille julkaisemalla ne Windowsphone marketplacella www.windowsphone.com. Sovellusten julkaisijoiden täytyy rekisteröityä Microsoftin App Hub:iin kehittäjäksi, joka on maksullista. Tätä ei siis missään nimessä tarvitse tehdä välittömästi, vaan ensimmäistä sovellusta voi viimeistellä kaikessa rauhassa omalla tietokoneella ja emulaattorilla. Jäsenyys App Hubissa antaa myös vastinetta rahoille: sovellus testataan Microsoftin toimesta kolmella eri puhelinmallilla ja analysoidaan. Mikäli sovellus ei täytä vaatimuksia, App Hub toimittaa vikaluettelon ja mahdollisesti ohjeita ongelman toisintamiseen. Kun sovellus on hyväksytty jaettavaksi, julkaisija voi seurata sovelluksen latausmääriä, mahdollisia kaatumisia ja käyttäjäarvioita julkaisualueittain. Sovelluksen ollessa maksullinen, tuotoista annetaan selvitys. Mainoksien hyväksikäyttö vaatii erillisen PubCenter tilin luomisen ja mainoksien määrittelyn sekä asentamisen sovelluksiin. Ansaintamenetelmistä mahdollisesti lisää perehdytyksien myöhemmissä osissa.

Puhelimen vapauttaminen/rekisteröinti sovelluskehitykseen

Sovelluksen asentaminen puhelimeen vaatii toimenpiteenä puhelimen vapauttamisen (rekisteröimisen) kehityskäyttöön. Se tehdään seuraavasti – suorita ohjelma:

C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.1\Tools\Phone Registration\PhoneReg.exe

Avautuvaan ikkunaan syötetään omat App Hub tiedot (ks. Windowsphone Marketplace osuus). Painamalla ‘Register’, puhelin on valmis. Mikäli haluat aluksi testata pelkästään emulaattorissa, tätä vaihetta ei vielä tarvitse tehdä.

Windows Phone:n käytettävissä olevat viitekehykset (framework)

Riippuen sovelluksen ominaisuuksista, kehittäjän on päätettävä mielellään etukäteen millä viitekehyksellä se tehdään. Joko XNA tai Silverlight tai näiden yhdistelmä. Mikäli sovellus on peli, joka käyttää 2D/3D grafiikkaa, on XNA luonnollinen valinta. Toisaalta jos sovellus näyttää videoita tai hakee verkosta sisältöä, on Silverlight parempi vaihtoehto.

Perehdymme XNA:han käyttäen C#:ia (C sharp) tässä ensimmäisessä osassa.

 Ensimmäinen XNA sovellus – Hello World

Tehdään yksinkertainen ohjelma, jossa näytetään ‘Hello World’-teksti näytöllä ja koskettamalla ruutua (englanniksi ‘tap’), tekstin väri vaihtuu.

Avaa Visual Studio 2010 Express ja ylävalikosta File -> New project -> Visual C#/XNA Game Studio 4.0 -> Windows Phone Game (4.0). Nimeä ohjelma ja paina OK. Hyväksy ‘Windows Phone OS7.1′ seuraavasta laatikosta ja OK jälleen.

Vilkaistaan Solution Explorer:ia. Tässä harjoituksessa olemme kiinnostuneita Game1.cs tiedostosta, johon kirjoitamme toiminnallisuuden. Ellei Game1.cs tiedosto ole avattuna valmiiksi, tuplaklikkaa tiedoston nimeä.

Sovellusohjelma muodostuu alustusten jälkeen kolmesta eri metodista, jotka on hyvä pitää siisteinä ja tarkoitukselleen omistettuina, eli LoadContent, Update ja Draw. Visual Studio antaa lähdekooditemplaatin käyttäjälle, johon voi lähteä rakentamaan omaa sovellustaan. Alla kerrotaan minkälaisia lisäyksiä tehdään:

Lisätään ‘public class Game1 : Microsoft.Xna.Framework.Game’-luokkaan seuraavat rivit:

// Talletetaan tekstin väri tähän muuttujaan
private Color _color { get; set; }
// Käytetään myös fonttia
public SpriteFont Font;

Paina oikeaa hiiren nappia Solution Explorissa ‘HelloWorldContent (Content)’ ja valitse Add -> New Item -> Sprite Font ja OK. Näin luotiin sisältölinkki käytettyyn fonttiin.

Lisää ‘public Game1()’-constructoriin

// määrätään konfiguraatio
graphics.PreferredBackBufferWidth = 480;
graphics.PreferredBackBufferHeight = 800;
graphics.SupportedOrientations = DisplayOrientation.Portrait;

Lisää Initialize()-metodiin

// Määritetään, että ollaan kiinnostuneita vain kosketuksesta
TouchPanel.EnabledGestures = GestureType.Tap;
// Alustetaan muuttujan väriksi punainen
_color = Color.Red;

Lisää Loadcontent() -metodiin

// Ladataan fontti
Font = Content.Load<SpriteFont>("SpriteFont1");

Lisää Update(..)-metodiin

while (TouchPanel.IsGestureAvailable)
{
   // Haetaan mahdolliset kosketusnäyttötapahtumat
   GestureSample gestureSample = TouchPanel.ReadGesture();

   bool _tap = false;

   // Tarkista onko kosketus (=tap) havaittu
   if (gestureSample.GestureType== GestureType.Tap)
      _tap = true;

   if (_tap)
   {
      if (_color.Equals(Color.Red))
         _color = Color.Blue;
      else
         _color = Color.Red;
   }
}

Ja lopuksi lisää Draw(..)-metodiin

spriteBatch.Begin();
// Piirretään teksti näytölle
spriteBatch.DrawString(Font, "Hello World", new Vector2(200, 200), _color);
spriteBatch.End();

Mikäli kaikki meni hyvin, build onnistuu (paina F6 tai ylävalikosta Build -> Build Solution). Tarkastele mahdollisia virheilmoituksia ja korjaa kirjoitusvirheet.

Varmista kohdelaitteen olevan ‘Windows Phone Emulator’ ja Debug.

Paina F5 tai ylävalikosta Debug -> Start Debugging. Emulaattori aukeaa. Kun emulaattori on latautunut ja valmis, teksti Hello World näkyy ruudulla. Siirtämällä hiiren osoitin emulaattorin näytölle ja painamalla vasenta nappia tekstin väri muuttuu. Ohjelmasta voi poistua painamalla ‘nuoli vasemmalle’- näppäintä puhelimen alareunassa.

Vastaavasti puhelimella testatessa kohdelaite pitää olla määritettynä ‘Windows Phone Device’ ja puhelimen ruutulukitus pois päältä. Koskettamalla ruutua tekstin väri muuttuu.

   

Onneksi olkoon, olet tehnyt ensimmäisen WP7 sovelluksesi!

———-

Ehdotuksia ja ideoita otetaan vastaan tulevista jutuista! Esimerkiksi: XNA:sta tarkemmin jotain, Silverlight, Ads (mainokset), push service, Bing Maps etc…

Aloittelijan opas säikeisiin (threads) iOS-alustalla

Välillä iOS-ohjelmoinnissa törmää tilanteisiin, joissa ohjelman täytyy suorittaa paljon asioita ennekuin jokin tieto voidaan näyttää käyttäjälle. Nämä asiat voivat olla esimerkiksi XML-tiedoston parsiminen, viestit palvelimelle tai raskaat laskentarutiinit. Käyttäjän näkökulmasta on tärkeää, ettei käyttöliittymä jäädy missään vaiheessa siitä huolimatta, kuinka paljon ohjelma käsittelee tietoa. Jos ohjelma suorittaa kaiken yhdessä säikeessä voi käyttökokemus jäädä heikoksi. Tämän takia useat ohjelmointikielet Objective-C mukaan lukien tarjoavat mahdollisuuden suorittaa asioita samanaikaisesti säikeissä.

Säikeet eli threadit saattavat alussa kuulostaa monimutkaisilta. Objective-C:ssä threadien käyttö on onneksi hyvin yksinkertaista. Helpoin tapa tehdä ohjelmatasi monisäikeinen on käyttää NSObject luokan mukana tulevaa performSelectorInBackground:withObject: metodia. Se mahdollistaa minkä tahansa metodin suorittamista taustalla ilman, että käyttöliittymä jäätyy metodin suorituksen ajaksi.

Alla on yksinkertaistettu esimerkki luokasta, joka suorittaa taustalla paljon laskentaa.

MinunLaskuri.h

@interface MinunLaskuri : NSObject {
  //Tyhjä
}

-(void)laskeMiljoonaan;

@end

MinunLaskuri.m

@implementation MinunLaskuri

-(id)init {
  if ( (self = [super init]) ) {
    [self performSelectorInBackground: @selector(laskeMiljoonaan) withObject: nil];
  }
  return self;
}

-(void)laskeMiljoonaan {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  for (int i = 0; i < 1000000; i++) {
    NSString *teksti = [NSString stringWithFormat: @"numero on %i", i];
    NSLog(@"Laskurin %@", teksti);
  }

  [pool release];
}

@end

Alla oleva rivi luo uuden taustasäikeen ja suorittaa luokan metodin nimeltä laskeMiljoonaan kyseisessä säikeessä jäädyttämättä käyttöliittymää. Metodille annetaan
tässä tapauksessa parametriksi nil, koska se ei ota vastaan mitään parametreja.
[self performSelectorInBackground: @selector(laskeMiljoonaan) withObject: nil];

Seuraavat tärkeät huomioitavat ovat rivit:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool release];

Muistinhallinnan kannalta on tärkeää, että jokaisessa ohjelman säikeessä on ainakin yksi NSAutoreleasePool-objekti, joka ottaa vastaan autoreleasetut objektit vapauttaen ne automaattisesti muistista. Kun kirjoittaa metodia, joka suoritetaan taustasäikeessä kannattaa lisätä heti alkuun ja loppuun kyseiset rivit. Tämän jälkeen voi rauhassa koodailla metodin sisälle mitä haluaa. Yläpuolella olevassa esimerkkitapauksessa metodi laskee miljoonaan ja printtaa konsoliin jokaisen numeron.

Lisää tietoa säikeistä löytyy Applen sivuilta

Manuaalinen muistinhallinta “for dummies” iOS-alustalla – Osa 1

Manuaalinen muistinhallinta voi tuntua alussa haastavalta aloittelevalle ohjelmoijalle. Useissa ohjelmointikielissä vastuu muistinhallinnasta on siirretty garbage collectorille, joka hoitaa muistinhallintaa automaattisesti. Apple lisäsi iOS 5.0 version myötä alustalleen ARC:n (Automatic Reference Counting), joka estää tehokkaasti mm. muistivuotoja ja olioiden vapauttamisesta johtuvia kaatumisia. ARC ei tuo Objective-C:lle garbage collectoria vaan se lisää olioille tarvittavat retain ja release kutsut ennen ohjelman kääntämistä. Xcode 4.2 tarjoaa työkalun, jolla olemassa olevia projekteja voi konvertoida ARC-yhteensopiviksi. ARC:n voi myös kytkeä pois päältä tiedostokohtaisesti.

Jos et kuitenkaan pysty tai halua käyttää projektissasi ARC:tä voit noudattaa muutamaa ohjenuoraa, jotka estävät turhia muistivuotoja ja kaatumisia.

Luokkamuuttujat.

Kun luot uuden luokkamuuttujan voit yksinkertaistaa muistinhallinnan tekemällä siitä propertyn ja asettamalle propertylle retain parametrin. Heti tämän jälkeen kannattaa lisätä luokan dealloc metodin sisälle kutsun, jolla vapautetaan muuttuja. Sen jälkeen voit unohtaa muistinhallinnan kyseisen muuttujan osalta.

On vain yksi asia mikä pitää muistaa. Aina kun asetat muuttujaan tietoa käytä self notaatiota ja vain autoreleasettuja olioita. Käyttämällä self notaatiota autoreleasetulle objektille kutsutaan samalla retain metodia, koska propertyyn oli asetettu retain parametri. Kun ottaa tavaksi luoda luokan metodien sisällä vain autoreleasattuja objekteja välttyy monilta ikäviltä muistivuodoilta. Tällöin ohjelmoijan ei täydy koskaan kutsua manuaalisesti olioiden retain ja release metodeja, koska kaikki tarvittava on hoidettu propertyn esittelyssä ja luokan dealloc metodissa.

Alla havainnollistava esimerkki aiheesta.

EsimerkkiYksi.h

@interface EsimerkkiYksi : NSObject {
  NSString *muuttujaYksi;
}

@property (nonatomic,retain) NSString *muuttujaYksi;

-(void)minunMetodi;

EsimerkkiYksi.m

@implementation
@synthesize muuttujaYksi;

-(void)minunMetodi {
  self.muuttujaYksi = [NSString stringWithString: @”Testi teksti”];
}

-(void)dealloc {
  [muuttujaYksi release];
  [super dealloc];
}
@end

Lisää tietoa muistinhallinnasta iOS-alustalla löytyy Applen sivuilta

OpenGL ES 2.0 kehityksen aloittaminen

OpenGL ES 2.0 on ollut speksinä olemassa vuodesta 2007 ja tarjolla laitteissakin jo tovin. 3D-grafiikkaan nojaavia mobiileja menestystarinoita ei ihan Angry Birds -mittakaavassa ole näkynyt mutta laitteiden alati parantuessa ja käyttökokemusten karttuessa 3D-renderöinti saattaa hyvinkin nousta keskeisempään rooliin mobiilimaailmassakin. Eivätkä ohjelmoitavan OpenGL ES 2.0 shader pipelinen mahdollisuudet grafiikan piirtämiseen lopu!

Tässä artikkelissa käydään läpi muutama asia jotka on tiedostettava – ja ehkä jopa osattava – aloittaessaan OpenGL ES 2.0 kehitystä. Varsinaisena kohdeyleisönä ovat ne joilla on edes jonkinlainen käsitys 3D-grafiikan periaatteista ja ovat tehneet joitain asioita vanhanmallisella fixed pipeline OpenGL:llä (kuten OpenGL ES 1.1 tai desktop maailman OpenGL 1.x) mutta tarjonnee teksti hieman lohtua aivan vasta-alkajallekin. Ensin käydään läpi uudet asiat jotka tulevat vastaan kun siirrytään fixed function renderöinnistä programmable pipelinen maailmaan ja lopuksi rakennetaan yksinkertainen 3D-softa iOS:lle käyttäen Xcoden OpenGL ES wizardia.

Matriisit

3D-grafiikan kulmakivi, kuvausmatriisit, olivat sisäänrakennettuina vanhassa OpenGL:ssä glPushMatrix()/glPopMatrix()/glRotate() jne myötä. Programmable pipelinessä matriisipinoa ei ole tarjolla ja matriisioperaatiot on toteutettava itse. Käytännössä kaikki alustat tarjoavat omat matriisikirjastonsa tähän (QMatrix4x4 Qt:ssä, android.opengl.Matrix Androidille sekä iOS 5.0 alkaen GLKMatrix4 iPhonelle) tarkoitukseen. Jos affiinit kuvaukset eivät ole tuttuja, aika oppia ne on nyt! Kuvausmatriisien hallinta on käytännössä ehdoton vaatimus modernin OpenGL:n taitamiselle.

Shaderit

Matriisien ohella shaderit ovat suurin muutos. Fixed pipeline hoiti käyttäjän puolesta geometrian transformoinnin ja pikselien piirtämisen; programmable pipelinessä kaikki tehdään itse. Tämä mahdollistaa valtavan paljon enemmän kontrollia siihen mitä ollaan tekemässä: vanha OpenGL käytännössä vaan konfiguroitiin (uploadattiin geometria ja tekstuurit, asetettiin valot ja transformaatiot) ja sen jälkeen OpenGL hoiti loput; lopputulokseen ei voinut vaikuttaa kauheasti. Kaikki tämä on nyt toisin ja ainakin ne, joilla on softwarerenderöintitaustaa, iloinnevat tästä! Shaderin kirjoittajalla on täysi valta (ja toisaalta myös vastuu) piirtää geometria juuri sillä tavalla kuin huvittaa.

Shaderit ovat käytännössä pienia C-kieltä muistuttavia OpenGL Shader Language (“GLSL”) ohjelmia joita ajetaan erittäin tehokkaissa shader prosessoreissa näytönohjaimella. GLSL:n eri versiot ovat keskenään hieman erilaisia; versio 1.20 on yhteensopiva OpenGL ES 2.0:n kanssa. OpenGL ES 2.0:ssa näitä shadereita on kahdenlaisia; ns. Vertex Shaderit sekä Fragment Shaderit. Rutkasti tiivistäen shaderit toimivat seuraavasti: vertex shader -ohjelmaa kutsutaan (ajurin/raudan toimesta) kerran per polygonin vertex. Vertex shader hoitaa geometrian transformoinnin ja voi laskea lisää asioita kuten valaistuksia, distance fogia jne. Vertex shaderin laskutoimitusten lopputulokset (outputit) interpoloidaan yli piirrettävänä olevan polygonin ja tarjoillaan inputteina fragment shaderille “varying” muuttujina. Fragment shader laskee näiden avulla lopullisen piirrettävän pikselin (“fragmentin”) väriarvon. Tämä on fragment shaderin ainoa output. Nyrkkisääntönä, tee kaikki laskenta minkä voit vertex shaderissa ja säilytä fragment shader mahdollisimman yksinkertaisena.

Esimerkki Vertex Shaderista:

// "Attribuutteja" jotka lähetetään shaderille OpenGL:stä
// automaattisesti jahka ne on enabloitu
attribute vec4 position; // transformoimaton "object space" koordinaatti
attribute vec3 normal; // transformoimaton "object space" vertex normaali
attribute vec2 tex_coords; // tekstuurikoordinaatit u,v

// "Uniformeja"; OpenGL:stä manuaalisesti lähetettyjä arvoja jotka ovat
// samat koko piirrettävälle geometrialle
uniform mat4 modelViewProjectionMatrix;
uniform mat3 normalMatrix;

// Fragment shaderin outputit
varying lowp float ndotVarying;
varying mediump vec2 texCoordsVarying;

// Vakio
const vec3 lightPosition = vec3(0.0, 0.0, 1.0);

void main()
{
  // Lasketaan valaistusarvo pinnan normaalin ja valon suunnan välisestä
  // kulmasta kosinilla (dot()) joka rajataan väliin 0..1
  vec3 eyeNormal = normalize(normalMatrix * normal);
  float ndot = max(0.0, dot(eyeNormal, normalize(lightPosition)));

  // Valmistellaan outputit; näiden interpoloidut arvot päätyvät
  // fragment shaderille inputeiksi
  ndotVarying = ndot;
  texCoordsVarying = tex_coords;

  // Geometrian transformointi. gl_Position on varattu sana output
  // muuttujalle johon tämä informaatio kirjoitetaan.
  gl_Position = modelViewProjectionMatrix * position;
}

Esimerkki Fragment Shaderista:

uniform lowp sampler2D texture; // OpenGL:stä passattu "handle" tekstuuriin

// Shaderin inputit; nämä laskettiin fragment shaderissa, interpoloitiin
// raudan toimesta ja ovat nyt tarjolla lopullisen pikselin värin laskemiseksi
varying lowp float ndotVarying;
varying mediump vec2 texCoordsVarying;

void main()
{
  // Tämän shaderin tapauksessa lopullinen pikselin väri lasketaan seuraavasti:
  // sampletaan tekstuurista oikealta kohdalta pikseli ja kerrotaan se
  // valoisuusarvolla (0..1). Lopputulos on kauniisti Lambert/flat shadeutuva
  // pinta jossa valo paistaa katselusuunnasta.
  highp vec4 color = texture2D(texture, texCoordsVarying) * ndotVarying;

  // gl_FragColor on (kuten kaikki gl_ prefiksin GLSL nimet) varattu sana
  // muuttujalle johon lopullinen fragment shaderin output kirjoitetaan.
  gl_FragColor = color;
}

Muut asiat

Koska meillä on shaderit ja niiden myötä valta piirtää miten haluamme, monia vanhan OpenGL:n kutsuja ei tarvita enää mihinkaan. Esimerkkeinä vaikkapa glEnableClientState() ja glEnable(GL_TEXTURE_2D). Lisäksi GLUT-kirjastoa ei ole tarjolla suoraan; matriisikirjaston onkin kyettävä tarjoaamaan toiminnallisuus paitsi rotaatioiden/translaatioiden myös perspektiivimatriisien luomiseksi. Ideaalinen tila tietysti on että kehittäjä tietää miten glFrustum() toimii. Shadereihin liittyvät attributet ja uniformit pähkinänkuoressa: attributet ovat mekanismi tuoda geometriaan liittyvä data vertex shaderille. Niitä hallitaan glBindAttribLocation() / glEnableVertexAttribArray() / glVertexAttribPointer() kutsuilla. Uniformit taas ovat kehittäjän shadereilleen syöttämää dataa jotka ovat yhtenäisiä (tästä nimi..) läpi koko kappaleen (tai sen osan, tai usean kappaleen) geometrian, esimerkiksi transformaatiomatriisit, tekstuurit jne. Uniformeja hallitaan glGetUniformLocation() / glUniform*() kutsuilla.

Toteutus: OpenGL ES 2.0 “Hello, World”

Uusi Xcode ja iOS 5.0 ovat tehneet tämän helpoksi; Xcodesta löytyy projektinluontiwizard jolla syntyy toiminnallisuudeltaan täydellinen OpenGL ES 2.0 sovellus shadereineen kaikkineen. Wizardin luoma toteutus piirtää sinisen ja punaisen laatikon jotka pyörivät toistensa sekä omien keskipisteidensä ympäri. Toinen laatikko piirretään GLSL:llä, toinen GLKitin avulla. GLKit on hyvin laaja ja ominaisuusrikas OpenGL-apukirjasto joka
esiteltiin iOS 5.0 päivityksen myötä. Wizardin/GLKitin käytössä on puolensa; toisaalta pääsee nopeasti liikkeelle toimivalla rungolla, mutta toisaalta se myös tarjoaa kehittäjälle mahdollisuuden olla oikeasti oppimatta mitä konepellin alla tapahtuu.

Tässä harjoituksessa luodaan wizardilla oletusprojekti ja muokataan sita siten
että sininen kuutio piirretään tekstuurimapattynä väritetyn sijaan.
Valitaan Xcode:sta File > New > New Project.. ja valitaan GLKit wizard templaatiksi:

Korvataksemme alkuperäisen kovakoodatun värityksen tekstuurilla, on tehtävä
jokunen asia:

  1. Vertex attribuutteihin lisätään tekstuurikoordinaatit; muuttuja gCubeVertexData[] sisältämään taulukkoon lisätään u,v arvot ja muutetaan sitä käyttäviä osia koodissa sopivasti. (Tähän liittyen, omasta mielestäni on paljon elegantimpaa käyttää C:n structia kuin float-arrayta vertex attributejen kuvaamiseen).
  2. Lisätään attribute ATTRIB_TEXCOORD ja uniform UNIFORM_TEXTURE sekä niiden vaatimat muutokset muualle koodiin.
  3. Ladataan tekstuuri kuvatiedostosta ja uploadataan se OpenGL:n puskureihin;kuvan lataaminen tehdaan UIImage:lla ja byte datan ekstraktoimiseen käytetään seuraavaa apukirjastoa: UIImage-Conversion (Huom., GLKitin mukana voi hyvinkin olla jouhevampikin tapa tehdä tämä).
  4. Muutetaan shaderien koodi tukemaan tekstuuria; katso Shader-esimerkit yllä!

Kuvakaappaukset ennen ja jälkeen muutosten:

Wizardin generoima koodi; kuutiot piirretään pelkin värein
Wizardin generoima koodi; kuutiot piirretään pelkin värein
Muokattu koodi; toinen kuutio piirretään teksturoituna
Muokattu koodi; toinen kuutio piirretään teksturoituna

 

 

 

 

 

 

 

 

 

 

Esimerkin lähdekoodi Xcode 4.2 projektina (598kB)

 

Loppusanat

OpenGL -osaaminen on jossain määrin harvinaista mobiilikehittäjien joukossa, luultavasti lähinnä siksi että sen sovellusalueet ovat olleen rajalliset. Asiaa ei auta korkeahko oppimiskynnys. Sen tarjoamat mahdollisuudet ovat kumminkin valtavat; shadereissa voidaan suorittaa mitä tahansa laskentaa rinnakkain CPUn corejen kanssa – mahdollistaen vaikkapa reaaliaikaisen signaalinkäsittelyn sijoittamisen pois itse suorittimelta.

Tämä artikkeli aloittaa 3D grafiikka / OpenGL ES 2.0 -juttusarjan; seuraavalla kerralla katsotaankin sitten jotain mielekkäämpää kuin ihan perusteita! Kysymyksia, palautetta, muuta sanottavaa? Jätä kommentti!

Lisää luettavaa

WikiBooks: Modern OpenGL Introduction

Lighthouse GLSL Tutorials

 

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ä.