Android-skolen del 2: Sådan skriver du din første applikation

Illustration: Android Logo
Anden undervisningstime i Android-skolen tager dig gennem udviklingen af en simpel telefonbog-applikation. Fat tastaturet og hiv Eclipse frem på skærmen.

Velkommen til anden del af Version2's skole for spirende Android-udviklere.

Anden lektion tager dig igennem kodningen af en simpel applikation, der henter oplysninger ud af telefonens adressebog og giver mulighed for at ringe op til kontaktpersonerne.

Der er lagt vægt på at introducere læseren for basale koncepter i Android-udvikling, mere end at fremstille verdens mest brugbare applikation fra begyndelsen. Husk, at du skal tale Java'sk i et vist omfang for at få det fulde udbytte af Android-skolen.

Gæsteunderviser er denne gang datalogistuderende ved Københavns Universitet Klaus Kartou. Han er en af udviklerne bag den danske Android-applikation Gigbox, der for tiden kæmper mod 199 andre Android-applikationer om 250.000 dollar i Google-konkurrencen Android Developer Challenge 2.

Vi lægger ud med at oprette et nyt Android-projekt, ganske som vi gjorde i første del af Android-skolen.

Her er det en god idé at udfylde Activity-feltet, selvom det ikke er strengt påkrævet. Det sikrer dig, at din applikation automatisk bliver afviklet på emulatoren, og at den installeres med et ikon i programmenuen. Vi vælger navnet AndroidSkolenDel2, som dermed bliver navnet på selve hovedklassen i applikationen.

Samtidig vælger vi at udvikle til Android SDK 1.6. Sørg derfor også for at den emulator, du oprettede i første lektion, har 1.6 som target.

1) Bliv fortrolig med ListViews

Et ListView i Android er en del af den grafiske brugergrænseflade, der viser elementer på en lodret liste med mulighed for scrolling, visning af tekst og billeder, klik for at aktivere handlingen bag elementet og meget mere.

ListViews bruges tit i Android-applikationer, fordi det ofte giver god mening af præsentere brugeren for informationer gennem en lodret liste af elementer. Det gælder for eksempel Gigbox-applikationen, hvor ListViews blandt andet bruges til at vise en liste over koncerter.

»ListViews er utroligt brugbare, så det er en god ting at lære tidligt,« siger Klaus Kartou.

Han peger på, at ListViews er en intuitiv måde for brugeren af interagere med telefonen på.

»Og så kan du proppe næsten hvad som helst ind i et ListView, fordi det æder alt, der nedarver fra View-klassen, som er den grundlæggende byggesten for brugerinterface-komponenter i Android. Vi bruger ListViews overalt i Gigbox, både til at visualisere store mængder koncertdata, men også som navigationsmenuer i applikationen,« siger Klaus Kartou.

Herunder er vist et af de mange ListViews i Gigbox-applikationen:

For at give dig en idé om ListViews funktionalitet begynder vi med at skrive en simpel applikation, der blot viser tre forskellige tekststrenge i et ListView.

Først sørger vi for, at vores nyoprettede klasse AndroidSkolenDel2 er en underklasse af ListActivity i stedet for blot Activity. Dernæst opretter vi en liste med de tekststrenge, vi gerne vil have vist i vores ListView.

Derefter sættes tekststrengene til visning i vores ListView med metoden setListAdapter.

Det hele sker indenfor rammerne af den autogenererede metode onCreate().

public class AndroidSkolenDel2 extends ListActivity {
 
  /** Called when the activity is first created. <em>/
  public void onCreate(Bundle savedInstanceState) {
    /</em> String array med elementer til visning i ListView <em>/
    final String[] listElements = { "Version2", "it",
      "for professionelle" };
 
    super.onCreate(savedInstanceState);
 
    /</em> Her sættes vores liste til visning */
    setListAdapter(new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, listElements));
  }
}

setListAdapter forbinder et ListView med de data, der skal vises i ListView'et. Et ListView kan i princippet vise alle slags data, så længe de er pakket ind i en ListAdapter. I dette tilfælde bruges underklassen ArrayAdapter som wrapper til vores liste af tekststrenge.

Bemærk argumentet android.R.layout.simple_list_item_1, som er et standardlayout af skærmbilledet på telefonen. Det modificerer vi senere til vores eget layout.

Oversæt og kør applikationen med ctrl+b efterfulgt af ctrl+F11.

Du vil nu se tekststrengene vist i et ListView, hvor du kan klikke på de forskellige elementer.

Indtil videre er underholdningsværdien af applikationen til at overse. Listens indhold er ubrugeligt, og vi får intet ud af at klikke på de enkelte entries.

Lad os derfor udvide applikationen med nye features.

2) Hent oplysninger fra telefonens adressebog

Begynd med at slette indmaden i onCreate()-metoden, på nær super.onCreate(savedInstanceState).

Hvis ikke du allerede har kontakter liggende i adressebogen i emulatoren, bør du oprette et par stykker, så der er lidt at arbejde med for applikationen. Hvis du ejer en Android-telefon, har du formentlig allerede en stak kontaktpersoner liggende i adressebogen.

Hver kontakt kan have en række oplysninger som privat-, mobil- og arbejdsnummer, e-mail og så videre knyttet til sig, men vi nøjes med at hente navn og det primære telefonnummer i denne omgang.

Det gøres ved gennem metodekaldet getContentResolver() at foretage en forespørgsel ? query - på alle kontakter fra telefonen og lægge dem over i en Cursor ? et interface, der giver læse-/skriveadgang til de data, der returneres fra en databaseforespørgsel.

Herefter beder vi Android om at stå for håndteringen af den oprettede Cursors 'livscyklus', så vi ikke selv skal bøvle med det, med metodekaldet startManagingCursor().

  /* Denne metode kaldes, når applikationen startes */
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // hent alle kontakter på telefonen
    Cursor c = getContentResolver().query(People.CONTENT_URI, null, null,
        null, null);
    startManagingCursor(c);
    ...

Nu har vi fat i den rå klump data, der udgør kontaktpersonerne i telefonens adressebog. Da kontaktpersonerne som nævnt har en lang række informationer knyttet til sig, er det tid til at bede om de data, vi gerne vil vise i vores ListView ? navn og telefonnummer.

(NB: Bemærk, at klassen Contacts.People er deprecated fra og med Android SDK 2.0, som i stedet benytter klassen ContractsContact.CommonDataKinds)

  /* Denne metode kaldes, når applikationen startes */
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // hent alle kontakter på telefonen
    Cursor c = getContentResolver().query(People.CONTENT_URI, null, null,
        null, null);
    startManagingCursor(c);
 
    // hent navn og tlf.nummer og map det over i vores layout
    String[] columns = new String[] { People.NAME, People.NUMBER };
    int[] contacts = new int[] { R.id.name, R.id.phone_number };
    ...

Læg her mærke til, at R.id.name og R.id.phone_number ikke automatisk findes på forhånd. Filen R.java (ligger under projektundermappen 'gen') autogenereres for hvert nyt Android-projekt, og den står for at forbinde vores Java-kildekode med det grafiske layout af brugergrænsefladen, der specificeres i XML-filen main.xml.

R.java kender lige nu ikke referencerne name og phone_number, som vi derfor skal tilføje i main.xml, hvorefter R.java vil blive autogenereret med de nye refererencer (NB: vi ændrer aldrig direkte i R.java!).

Åben main.xml fra projektundermappen res/layout (husk at vælge main.xml-fanen under editoren). Den ser således ud:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
</LinearLayout>

Fjern hele det eksisterende TextView-tag og indsæt i stedet to nye, så main.xml kommer til at tage sig således ud:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView android:id="@+id/name"
  android:textSize="18px" 
    android:textStyle="bold" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
/>
<TextView android:id="@+id/phone_number" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
/>
</LinearLayout>

Vi har dermed defineret vores eget layout, hvor teksten til navnet på kontaktpersonen har pixelstørrelsen 18 og er markedet med fed.

Gem og build projektet.

Herefter bør Eclipse holde op med at brokke sig over, at de to layout-referencer name og phone_number ikke findes i layout'et. (Bemærk, at vi har har valgt at beholde standardnavnet main.xml til layout-filen for nemheds skyld. I en større applikation med flere layoutfiler ville vi have valgt at omdøbe filen til contactView.xml eller et lignende, mere sigende navn.)

Nu mangler vi blot at 'mappe' vores data over i den grafiske brugergrænseflade. Det gøres med med en instans af klassen SimpleCursorAdapter, der mapper vores columns fra cursor'en over de to nye TextViews, vi netop har defineret i main.xml.

Hermed kommer den samlede onCreate()-metode til at se sådan ud:

private SimpleCursorAdapter cursorAdapter;
 
  /* Denne metode kaldes, når applikationen startes */
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // hent alle kontakter på telefonen
    Cursor c = getContentResolver().query(People.CONTENT_URI, null, null,
        null, null);
    startManagingCursor(c);
 
    // hent navn og tlf.nummer og map det over i vores layout
    String[] columns = new String[] { People.NAME, People.NUMBER };
    int[] contacts = new int[] { R.id.name, R.id.phone_number };
 
    cursorAdapter = new SimpleCursorAdapter(this, R.layout.main, c, columns,
        contacts);
    setListAdapter(cursorAdapter);
  }

Nu skal vi give applikationen lov til at læse kontakterne fra adressebogen.

Det sker i filen AndroidManifest.xml, som ligger i roden af projektbiblioteket. Den hentes frem i Eclipse-editoren ved at dobbeltklikke på filen og vælge fanebladet AndroidManifest.xml under editor-vinduet.

Her tilføjes linjen

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

under selve manifest-tag'et (se den komplette manifest-fil længere nede i artiklen).

Tast ctrl+b efterfulgt af ctrl+F11 for at build'e og køre projektet.

Når applikationen er startet op, kan du se en liste over kontaktpersoner med navn og telefonnummer.

Vi mangler dog stadig at give mulighed for at ringe til kontaktpersonen med et klik.

3) Tilføj mulighed for opkald

Tilbage i AndroidSkolenDel2.java skal vi nu oprette en metode, som lytter efter klik på kontaktpersonerne og sætter gang i et opkald ud fra det.

Det sker med metoden onListItemClick(), som vi finder en skabelon - eller stub - til med shift+alt+S -> Override/Implement methods... og et klik ind under ListActivity.

Bemærk, at onListItemClick() skal ligge i klassens globale scope, og ikke under metoden onCreate().

Vi tilføjer herefter en såkaldt Intent i onListItemClick(), som er en abstraktion over den operation, vi gerne vil gennemføre, når der klikkes på en kontaktperson - i dette tilfælde et opkald.

Herefter lægger vi positionen i ListView'et for den kontakt, vi har trykket på, over i en Cursor og henter id'et for kontaktens primære telefonnummer ud:

 /* Denne metode kaldes, når der klikkes på en kontaktperson */
  protected void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);
 
    // vores intention er at foretage et opkald
    Intent i = new Intent(Intent.ACTION_CALL);
    // hvor i ListView'et har vi klikket?
    Cursor c = (Cursor) cursorAdapter.getItem(position);
    // hent id'et for kontaktens primære telefonnummer
    long phoneId = c.getLong(c.getColumnIndex(People.PRIMARY_PHONE_ID));
    ...

Når det er gjort, beder vi vores Intent om at arbejde på den samlede adresse - eller Uri - med metoden setData(), hvorefter vi sætter vores Intent i sving med metodekaldet startActivity():

...
    // Intent skal arbejde på telefon-URI'et + telefon-id'et
    i.setData(ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId));
    // sæt gang i vores Intent
    startActivity(i);
  }

Nu ser den færdige kildekode til applikationen i AndroidSkolenDel2.java således ud:

package dk.version2.as2;
 
import android.app.ListActivity;
import android.content.ContentUris;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
 
public class AndroidSkolenDel2 extends ListActivity {
 
  private SimpleCursorAdapter cursorAdapter;
 
  /* Denne metode kaldes, når applikationen startes <em>/
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // hent alle kontakter på telefonen
    Cursor c = getContentResolver().query(People.CONTENT_URI, null, null,
        null, null);
    startManagingCursor(c);
 
    // hent navn og tlf.nummer og map det over i vores layout
    String[] columns = new String[] { People.NAME, People.NUMBER };
    int[] contacts = new int[] { R.id.name, R.id.phone_number };
 
    cursorAdapter = new SimpleCursorAdapter(this, R.layout.main, c, columns,
        contacts);
    setListAdapter(cursorAdapter);
  }
 
  /</em> Denne metode kaldes, når der klikkes på en kontaktperson */
  protected void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);
 
    // vores intention er at foretage et opkald
    Intent i = new Intent(Intent.ACTION_CALL);
    // hvor i ListView'et har vi klikket?
    Cursor c = (Cursor) cursorAdapter.getItem(position);
    // hent id'et for kontaktens primære telefonnummer
    long phoneId = c.getLong(c.getColumnIndex(People.PRIMARY_PHONE_ID));
    // Intent skal arbejde på telefon-URI'et + telefon-id'et
    i.setData(ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId));
    // sæt gang i vores Intent
    startActivity(i);
  }
}

Så mangler vi kun at give applikationen lov til at foretage et opkald.

Her tilføjer vi endnu et uses-permission-tag, CALL_PHONE, så manifest-filen samlet set kommer til at se ud som vist i koden herunder:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="dk.version2.as2"
      android:versionCode="1"
      android:versionName="1.0">
  <uses-permission android:name="android.permission.READ_CONTACTS"/>
  <uses-permission android:name="android.permission.CALL_PHONE"/>
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".AndroidSkolenDel2"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 
    </application>
    <uses-sdk android:minSdkVersion="4" />
</manifest>

Oversæt og kør projektet med ctrl+b og ctrl+F11. Nu kan du ringe op til en kontakt ved at klikke en enkelt gang på kontakten i vores ListView:

Og hermed er du færdig med din første, simple Android-applikation.

Ifølge Klaus Kartou er en af styrkerne ved Android, at det kræver så forholdsvis få kodelinjer at få sendt en lille Android-applikation ud over stepperne.

»Jeg har oplevet, at djævlen ligger i detaljerne, og det kan for eksempel tage lang tid at implementere multitrådning, så tunge opgaver ikke blokerer UI-tråden. Og noget så simpelt som at håndtere, at brugeren skifter skærmbilledets orientering ved at vende på telefonen, er noget som mange udviklere kæmper med. Men fordelen er, at det er nemt at komme i gang og få sparket noget kode afsted,« siger Klaus Kartou.

Det var alt for anden omgang af Android-skolen. Næste gang tager vi fat i GPS, accelerometer og andre dele af telefonens mere eksotiske hardware.

Litteratur:
[1] http://developer.android.com
[2] http://www.anddev.org

Tips og korrekturforslag til denne historie sendes til tip@version2.dk
Kommentarer (4)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
Log ind eller Opret konto for at kommentere