XCoden työkalut: koodin staattinen analyysi

XCode on tarjonnut versiosta 3.2 lähtien hyödyllisen Analyze-toiminnon, joka analysoi koodia ja näyttää käyttäjällesen löytämät ongelmakohdat koodissa. Kun tyypillinen kääntäjä varoittaa käyttäjiä antamalla virheitä esimerkiksi käyttämättömille muuttujille, analyysitoiminto menee paljon pitemmälle, ja voi varoittaa sellaisestakin muuttujasta johon on asetettu ehkä jopa useita kertoja arvoa, mutta jota ei käytetä missään. Toimintoon pääsee käsiksi valikon “Product -> Analyze” kautta (XCode 4.x).

XCoden staattisen analyysin työkalu perustuu Clang-nimiseen työkaluun (http://clang.llvm.org/), ja jonka analysaattori on sittemmin liitetty XCodeen vakio-ominaisuutena (alkaen versiosta 3.2). Tässä tärkeimpiä ja yleisimpiä ongelmia joita tämä työkalu löytää:

1) Muistivuotodot

Analysaattori näyttää potentiaaliset muistivuodot applikaatiossa alla olevassa kuvassa esitetyllä tavalla. Tässä tilanteessa facebook-olio vuotaa, koska sitä ei releasata muualla ohjelmassa.

2) Alustamattomat muuttujat

Potentiaalisesti alustamattomien muuttujien käyttö voi johtaa isompiinkin bugeihin. Kuvan tilanteessa ‘val’-muuttujaa ei alusteta, kun test saa arvokseen 9. Vaikka koodi olisikin suunniteltu niin, että ‘test’ ei voi saada arvoa 9, niin tällainen koodi on silti virhealtista kun koodia ruvetaan muuttamaan tulevaisuudessa.

3) Metodin palauttama alustamaton arvo

Kuvassa metodi palauttaa ‘ar’-muuttujan, joka on taulu. Taulua ei kuitenkaan alusteta, sillä jos muuttujan ‘test’ arvo on 10, ‘ar’ ei ole koskaan alustettu.

Näiden kolmen lisäksi analyze antaa muitakin hyödyllisiä varoituksia. Jo pelkästään korjaamalla analyzen palauttamat varoitukset säästyy monelta potentiaaliselta bugilta ja muistivuodolta, joten toimintoa kannattaa käyttää aika ajoin. Analyzen saa myös automaattisesti käyntiin buildin yhteydessä – klikkaa projektin juurikansiota ja Build Optionsin alla vaihda kohta ‘Run Static Analyzer’ kohtaan yes.

Lisätietoa löytyy Applen kirjoittamasta artikkelista.

Liikkuva 2D-grafiikka Androidilla

Ensimmäinen asia, mitä lähes jokainen aloitteleva ohjelmoija haluaa tehdä on oma peli. Tämän toteuttamiseen tarvitaan lähes välttämättä liikkuvien grafiikkojen piirtämistä. Tässä artikkelissa esitetään Androidin 2D-grafiikkojen piirtäminen kankaalle. Käymme myös läpi hiukan yksinkertaista säikeiden käyttöä ja säieturvallisuutta.

Androidille 2D-grafiikkojen piirtämiseen tarvitaan näkymä, jolle voi piirtää. Toteutamme tässä esimerkissä oman versiomme SurfaceView:stä, jolle piirtäminen tapahtuu käytännössä antamalla SurfaceView:lle kankaan onDraw()-metodissa.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    starList = new ArrayList<StarFallActivity.Star>();

    setContentView(new StarSurface(this));
}

class StarSurface extends SurfaceView implements SurfaceHolder.Callback {
    private GameThread gameThread;
    private GraphicThread graphicThread;

public StarSurface(Context context) {
    super(context);
    getHolder().addCallback(this);
    gameThread = new GameThread(this);
    graphicThread = new GraphicThread(getHolder(), this);
}

@Override
public void onDraw(Canvas canvas) {
    canvas.drawColor(Color.BLACK);
    synchronized (starList) {
        for(Star star : starList) {
            canvas.drawBitmap(star.getImage(), star.getX(), star.getY(), null);
        }
    }
}
...

Pidämme kirjaa rajoittamattomasta määrästä putoavia objekteja ja piirrämme ne kankaalle niiden sisällään pitämän sijainnin muukaan onDraw()-metodissa. Resursseissa olevan kuvan piirtäminen valmiista kuvaresurssista ei vaadi, kuin resurssin bittikartaksi muuttamisen ja X- ja Y-koordinaatit, mihin kuva pintanäkymässä tulee.

BitmapFactory.decodeResource(getResources(), R.drawable.icon);

Säikeet ja säieturvallisuus

Pidämme objektien liikuttamisen ja ruudunpäivityksen erillisissä säikeissä ja laitamme säikeet odottamaan, kun niitä ei tarvita. Säikeiden käytössä täytyy myös ottaa huomioon samanaikaisten operaatioiden turvallisuus. Koska pidämme liikuvat objektit yhdessä listassa täytyy lista asettaa synkronoiduksi, niin että siihen voi kohdistua vain yksi operaatio kerrallaan. Tämä täytyy tehdä jokaisessa paikassa, missä listaa käsitellään. Esimerkiksi pelin osia liikuttelevassa säikeessä:

synchronized (starList) {
    for (Iterator<Star> iterator = starList.iterator(); iterator.hasNext();) {
        Star star = iterator.next();
        star.move();
        if(star.getY() >= maxHeight) {
            iterator.remove();
        }
    }
}

Koska poistamme listasta putoavan tähden, jos se menee ruudun ulkopuolelle joudumme käyttämään iteraattoria, koska listasta poistaminen sen läpikäynnin aikana ei suoraan ole mahdollista.

Säikeissä tarvitsee pääasiassa vain metodit säikeen käynnissä olon asettamiseen ja run()-metodin oma toteutus, missä säikeen suoritusaikainen toiminta tapahtuu. Sijettä ei tule tappaa stop()-metodilla, sillä se voi jättää ohjelmistosi outoon tilaan. Tämän sijasta tulisi käyttää run()-metodissa while lausetta joka toistuu niin kauan, kuin säikeen sisäinen while ehto muuttuu epätodeksi ja run()-metodi pääsee suorittamaan loppuun. Säikeet joiden run()-metodi on päässyt suoriutumaan kokonaan tapetaan automaattisesti järjestelmän toimesta.

Jos et halua säikeesi vievän kaikkea prosessointitehoa aseta säie jokaisen while luupin lopuksi odottamaan jonkin aikaa. Piirtopintaa ei esimerkiksi kannata päivittää useampaa, kuin 60 kertaa sekunnissa ja tämäkin on yleensä liikaa. Säikeen voi myös pakottaa käynnistymään kutsumalla sille notify()-metodia.

Lataa esimerkki tästä.
Voit nyt tehdä esimerkiksi oman versiosi kitarasankarista ja pudottaa ruudun yläpäästä kuvia ja tarkastella osuuko käyttäjä niihin ennen, kuin ne putoavat ulos ruudulta.

Android Manifest

Android-manifest voi aluksi näyttää vaikealta ymmärtää, mutta ilman sitä ei koodia voi suorittaa. Tässä artikkelissa opetetaan Android-manifestin perus käyttö ja käydään läpi vaativammat käyttömahdollisuudet.

Manifestin rakenne Androidin dokumentaatiosta:

<?xml version="1.0" encoding="utf-8"?>

<manifest>
<uses-permission />
 <permission />
 <permission-tree />
 <permission-group />
 <instrumentation />
 <uses-sdk />
 <uses-configuration />
 <uses-feature />
 <supports-screens />
 <compatible-screens />
 <supports-gl-texture />
 <application>
 <activity>
 <intent-filter>
 <action />
 <category />
 <data />
 </intent-filter>
 <meta-data />
 </activity>
 <activity-alias>
 <intent-filter> . . . </intent-filter>
<meta-data />
 </activity-alias>
 <service>
 <intent-filter> . . . </intent-filter>
 <meta-data/>
 </service>
 <receiver>
 <intent-filter> . . . </intent-filter>
 <meta-data />
 </receiver>
 <provider>
 <grant-uri-permission />
 <meta-data />
 </provider>
 <uses-library />
 </application>

</manifest>

Nämä on osattava

Käytetään esimerkkinä SQL-tietokanta artikkelissa annettua koodia ja sen AndroidManifest.xml:ää.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidkehitys.fi.database"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="7" />

<application android:icon="@drawable/icon" android:label="@string/app_name">

<activity android:name="DatabaseActivity" android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity android:name="EnterProductActivity"></activity>

</application>
</manifest>

Manifestissä ei elementtien sisään tule muuta, kuin toisia elementtejä. Arvon kirjottaminen elementin sisään ei toimi ja aiheuttaa virheen. Kaikki arvot mitä manifestissä annetaan tulee antaa elementtien parametreissä.

<manifest>-elementin attribuuteiksi tulee applikaation oletuspaketti, versionumeroja nimi. Elementin sisälle asetetaan minimi versio Android käyttöjärjestelmästä. Esimerkiksi tässä tapauksessa käytetty API versio 7 on “Android 2.1 – update 1”.

Manifest-elementin sisälle tulee myös <application>-elementti, jossa määritellään applikaation ikoni ja nimi.

Applikaatio elementtien sisällä tehdään jokaiselle aktiviteetille, jonka käyttäjä voi käynnistää, <activity>-elementti. Mikäli aktiviteetti on samassa paketissa, kuin manifest-elementissä määritelty oletuspaketti, riittää asettaa aktiviteetin nimeksi kyseisen luokan nimi. Mikäli aktiviteetti on jossain toisessa paketissa tulee sinun määrittää koko paketin polku.

Aktiviteetti-elementin sisälle voi määritellä <intent-filter>-elementin, joka määrää erityistilanteita, missä aktiviteetti käynnistetään. Ensimmäinen aktiviteetti, mikä applikaatiossasi käynnistetään täytyy määrittää oikeilla intent-filtereillä:

<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

Aktiviteetit, jotka eivät saa intenttejä applikaation ulkopuolelta ei tarvitse muuta, kuin nimetä manifestissä. Mikäli aktiviteettiä ei julisteta manifestissa sen käynnistäminen kaataa applikaation ja aiheuttaa ActivityNotFoundExceptionin;

Vaativampaa käyttöä varten hyvä tietää

Tässä käydään nopeasti läpi manifestin vaikeammin käytettäviä ja ymmärrettäviä ominaisuuksia. Näitä ei välttämättä tarvitse tietää yksinkertaisia applikaatioita tehtäessä, mutta ennemmin tai myöhemmin ne tulevat vastaan androidkehityksessä.

Tietyt osat Android-järjestelmästä vaativat käyttäjän lupaa ja ne täytyy määritellä manifestissä. Esimerkkinä tällaisista luvista on esimerkiksi internet-yhteyden käyttö:

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

Intenttien vastaanottamisen määrittely manifestissä taas mahdollistaa reagoinnin tapahtumiin, vaikka applikaatio ei olisikaan käynnissä. Tällaisia tapauksia voi esimerkiksi olla NFC-sirujen lukeminen tai push-notificaatioiden vastaanottaminen.

<service>-elementti määrittelee applikaation tarjoaman palvelun muille laitteen applikaatioille. Näin määritellyt palvelut pysyvät päällä, vaikka käyttäjä vaihtaisi toiseen applikaatioon. Tällaisten palveluiden tulisi suorittaa yksi tehtävä ja sitten sulkea itsensä, sillä muuten näitä palveluita ei tapeta koskaan.

Jos haluat applikaatiosi reagoivan järjestelmän tai muiden applikaatioiden lähettämiin intentteihin, kuten push-notifikaatioihin on sinun määriteltävä <receiver>-elementti ja sen sisältö manifestissä applikaatio-elementin sisällä.

<provider>-elementin sisällä määritellään applikaation tarjoama sisältö. ContentProvider tarjoaa pääsyn applikaation hallinoimaan tietoon, myös muista applikaatioista.

Manifestin vaativampaan käyttöön palataan, kun sitä tarvitaan käytännön esimerkeissä.