Mobiltelefonapplikationer med J2ME: (Preliminära) Lösningar till tentamen 2006-01-07

Observera att detta är förslag på lösningar. Det kan finnas andra lösningar som också är korrekta, och det kan hända att en del av lösningarna är mer omfattande än vad som krävs för full poäng på uppgiften.

Uppgift 1 (20 p)

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class Uppgift1 extends MIDlet implements CommandListener {
    private Display displayen;
    private List listan;
    private TextBox textrutan;
    private Form formuläret;
    private StringItem etiketten;
    private ChoiceGroup valgruppen;
    private Command exitkommandot, okkommandot, tillbakakommandot;
    private int antal_kommandon = 0;

    public Uppgift1() {
        listan = new List("Välj ett av följande alternativ", Choice.EXCLUSIVE);
        textrutan = new TextBox("Textrutan", "Antal kommandon: " +
                                antal_kommandon, 100, 0);
        formuläret = new Form("Formuläret");

        exitkommandot = new Command("Avsluta", Command.EXIT, 0);
        okkommandot = new Command("Välj", Command.OK, 1);
        tillbakakommandot = new Command("Tillbaka", Command.BACK, 1);

        listan.append("Fortsätt visa den här listan", null);
        listan.append("Visa textrutan", null);
        listan.append("Visa formuläret", null);
        listan.append("Avsluta", null);

        listan.addCommand(okkommandot);
        listan.addCommand(exitkommandot);
        listan.setCommandListener(this);

        textrutan.addCommand(exitkommandot);
        textrutan.addCommand(tillbakakommandot);
        textrutan.setCommandListener(this);

        etiketten = new StringItem("Antal kommandon: ", "" + antal_kommandon);
        etiketten.setLayout(Item.LAYOUT_2);
        formuläret.append(etiketten);

        valgruppen =
            new ChoiceGroup("Välj ett av följande alternativ",
                            ChoiceGroup.EXCLUSIVE);
        valgruppen.append("Gå tillbaka till listan", null);
        valgruppen.append("Visa textrutan", null);
        valgruppen.append("Fortsätt visa det här formuläret", null);
        valgruppen.append("Avsluta", null);
        formuläret.append(valgruppen);

        formuläret.addCommand(exitkommandot);
        formuläret.addCommand(okkommandot);
        formuläret.setCommandListener(this);
    } // Uppgift1

    public void startApp() {
        displayen = Display.getDisplay(this);
        displayen.setCurrent(listan);
    }

    public void commandAction(Command kommandot, Displayable s) {
        ++antal_kommandon;
        textrutan.setString("Antal kommandon: " + antal_kommandon);
        etiketten.setText("" + antal_kommandon);
        if (kommandot == exitkommandot) {
            destroyApp(false);
            notifyDestroyed();
        }
        else if (kommandot == okkommandot && displayen.getCurrent() == listan) {
            int valet;
            valet = listan.getSelectedIndex();
            if (valet == 0) {
                displayen.setCurrent(listan); // Onödigt
            }
            else if (valet == 1) {
                displayen.setCurrent(textrutan);
            }
            else if (valet == 2) {
                displayen.setCurrent(formuläret);
            }
            else if (valet == 3) {
                destroyApp(false);
                notifyDestroyed();
            }
        }
        else if (kommandot == okkommandot && displayen.getCurrent() == formuläret) {
            int valet;
            valet = valgruppen.getSelectedIndex();
            if (valet == 0) {
                displayen.setCurrent(listan);
            }
            else if (valet == 1) {
                displayen.setCurrent(textrutan);
            }
            else if (valet == 2) {
                displayen.setCurrent(formuläret); // Onödigt
            }
            else if (valet == 3) {
                destroyApp(false);
                notifyDestroyed();
            }
        }
        else if (kommandot == tillbakakommandot) {
            displayen.setCurrent(listan);
        }
    } // commandAction

    public void destroyApp(boolean unconditional) { }

    public void pauseApp() { }
} // class Uppgift1

Kommentar: Med den här lösningen gör Tillbaka-kommandot i textrutan att man alltid kommer till den första listan, även om man kom till textrutan från formuläret. Om man vill komma tillbaka till formuläret får man låta programmet hålla reda på var man kom ifrån.

Uppgift 2 (2 p)

En del av informationen i JAR-filen upprepas i den mycket kortare JAD-filen. Den är mest användbar när tillämpningar skickas via det dyra och långsamma mobilnätet (OTA, "over the air"). En mobilanvändare kan ladda ner den korta JAD-filen och studera den, innan hon laddar ner JAR-filen, som innehåller den körbara koden och de andra resurser som behövs för tillämpningen.

Uppgift 3 (4 p)

a)

Omstuvning av den kompilerade byte-koden, till exempel genom att byta namn på klasser och metoder, så att den blir svårare att förstå och de-kompilera till källkod. Dessutom blir den kortare, bland annat genom att oanvända klasser och metoder tas bort.

b)

Efter kompileringen. Det är den kompilerade byte-koden som obfuskeras, och innan kompileringen finns den ju inte.

(Man skulle förstås kunna tänka sig att obfuskera källkoden, men det fungerar inte lika bra. Det skulle till exempel inte gå att stoppa in illegal kod för att förvirra de-kompilatorer.)

c)

Före för-verifieringen. För-verifiering kontrollerar den byte-kod som ska skickas till telefonerna, och det är ju den obfuskerade koden som skickas. (Men det kan, kanske, fungera ett obfuskera efter för-verifieringen, med vissa obfuskerare.)

d)

Före det att MIDlet:en skickas till telefonen. Om den o-obfuskerade byte-koden skickades till telefonen, skulle ju telefonens ägare (eller någon som har möjlighet att tjuvlyssna på nätverket) kunna få tillgång till den. Dessutom gör obfuskeringen att byte-koden blir mindre, och man vill inte skicka onödiga data över det dyra och långsamma mobilnätet.

Uppgift 4 (2 p)

CLDC innehåller klasser som erbjuder grundfunktionalitet i Java, som String, Date och Thread. En klass som är grundläggande, och som man kanske känner igen från J2SE (skrivbordsvarianten av Java), finns förmodligen i CLDC och inte i MIDP.

MIDP innehåller gränssnittskomponenter, som Screen och Command, och andra plattformsspecifika klasser, som RecordStore. En klass som inte är meningsfull annat än i mobilsammanhang (antingen på en mobiltelefon eller en liknande apparat, till exempel en personsökare) finns förmodligen i MIDP och inte i CLDC.

Uppgift 5 (2 p)

Mobiltelefoner är helt enkelt inte avsedda att användas på ett sätt som gör det praktiskt att använda mus eller pekpenna. De ska till exempel kunna manövreras med en hand, eller medan man förflyttar sig. Därför saknar de (för det mesta) mus, tryckkänslig skärm eller annan pekmöjlighet.

Mobiltelefoner (och andra "MID:ar") har dessutom för det mesta en ganska liten skärm, där det skulle vara svårt att få plats med klickbara knappar med text.

(Andra små datorer, till exempel handdatorer med Pocket PC, är tänkta för en mer stillasittande användning, och har större skärm samt en tryckkänslig skärm med pekpenna. Användargränssnittet för dessa har vanliga knappar på skärmen att "klicka" på med pekpennan.)

Lösningen med kommandon ger dessutom flexibilitet till J2ME-implementationen. Olika hårdvara kan ha olika mekanismer för att låta användaren ge kommandon. Om man kör J2ME på hårdvara som faktiskt har mus eller pekskärm, skulle den till och med kunna visa kommandona som knappar som man kan klicka på.

Uppgift 6 (10 p)

I den här lösningen förutsätter jag att vi aldrig sparar mer än en enda post i postlagret som heter "antal-kommandon", och att den posten får identitetsnummer 1.

a)

    private void spara_antal_kommandon() {
        try {
            RecordStore postlagret =
                RecordStore.openRecordStore("antal-kommandon", true);
            // Vi skapar aldrig mer än en enda post
            String strängen = "" + antal_kommandon;
            byte[] data = strängen.getBytes();
            if (postlagret.getNumRecords() == 0)
                postlagret.addRecord(data, 0, data.length);
            else
                postlagret.setRecord(1, data, 0, data.length);
            postlagret.closeRecordStore();
        }
        catch (RecordStoreException e) {
            // Ignorera eventuella fel
        }
    }

Kommentar: Ovanstående kod kan läcka resurser. Om det skulle uppstå ett fel som kastar ett undantag till exempel i addRecord, kommer closeRecordStore inte att anropas. (Se s 338-339 i kapitel 17 i kursboken.) Nedanstående kod är en säkrare lösning. (Motsvarande modifiering kan också göras i b-uppgiften nedan.)

    private void spara_antal_kommandon() {
        RecordStore postlagret = null;
        try {
            postlagret =
                RecordStore.openRecordStore("antal-kommandon", true);
            // Vi skapar aldrig mer än en enda post
            String strängen = "" + antal_kommandon;
            byte[] data = strängen.getBytes();
            if (postlagret.getNumRecords() == 0)
                postlagret.addRecord(data, 0, data.length);
            else
                postlagret.setRecord(1, data, 0, data.length);
        }
        catch (RecordStoreException e) {
            // Ignorera eventuella fel
        }
        finally {
            try {
                if (postlagret != null)
                    postlagret.closeRecordStore();
            }
            catch (RecordStoreException e) { }
        }
    }

b)

Om det misslyckas att läsa posten från postlagret, struntar vi i att sätta variabeln antal_kommandon, och den kommer alltså att behålla sitt ursprungsvärde, 0.

    private void hämta_antal_kommandon() {
        try {
            RecordStore postlagret =
                RecordStore.openRecordStore("antal-kommandon", false);
            byte[] data = postlagret.getRecord(1);
            String strängen = new String(data);
            antal_kommandon = Integer.parseInt(strängen);
            postlagret.closeRecordStore();
        }
        catch (RecordStoreException e) {
            // Ignorera eventuella fel
        }
    }


Thomas Padron-McCarthy (Thomas.Padron-McCarthy@tech.oru.se) 22 januari 2006