Skoro každá aplikace si potřebuje ukládat některé údaje potřebné pro její vlastní běh, aby s jejich zjišťováním neobtěžovala uživatele při každém spuštění znovu. Standardní J2ME nemá k ukládání dat souborový systém, ale pouze velmi jednoduché ukládání polí bajtů, označované krycím názvem RMS (Record Management System neboli systém správy záznamů).

Idea úložiště záznamů

Úložiště záznamů neboli databáze (Record store) se skládá ze seznamu záznamů. Tyto záznamy tvoří pole bajtů a jsou indexovány od „1“. Jednotlivé databáze musí mít v rámci jedné sady midletů unikátní názvy. Za konzistenci databáze při práci s ní odpovídá implementace Javy, takže všechny operace nad databází jsou atomické (například záznam může být buď uložený nebo neuložený, žádný stav „napůl uložený“ neexistuje). Stejně také platforma odpovídá za zachování dat přes vypnutí telefonu či výměnu baterie. Při odstranění sady midletů z telefonu jsou automaticky odstraněny i všechny její databáze.

Stejně jako midlety mají přístup jen ke zdrojům obsaženým v téže sadě midletů, mají i přístup do databáze omezený pouze na ty, které vytvořil midlet z téže sady midletů. Tato omezení vyplývají ze snahy zamezit rozšíření virů na mobilních telefonech a zaručit větší bezpečnost, než panuje v internetovém světě.

Balík javax.microedition.rms

Třídy a rozhraní specifická pro práci s databázemi se nacházejí ve výše uvedeném balíku. Základní třídou je RecordStore. Databáze se otevírá statickou metodou openRecordStore(String recordStoreName, boolean createIfNecessary) a zavírá metodou closeRecordStore(), přičemž kolikrát byla otevřena, tolikrát musí být i zavřena, aby byla opravdu uzavřena. Sluší se databázi zavírat i na dobu, kdy je aplikace v pasivním stavu (zapauzovaná).

Pro ukládání a načítání dat se hodí použít výstupní a vstupní proudy z balíku java.io. Jako příklad si dnes vezmeme primitivní telefonní seznam (ukázky kódu uvedené v článku jsou zjednodušeným výtahem z příkladu).

Zápis záznamu do databáze:

String name = „blablabla“;
String number = „123456789“;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(buffer);
RecordStore store = null;
try {
    // otevřeme databázi
    store = RecordStore.openRecordStore(„autobus“, true);
    // zapíšeme jméno do výstupního proudu
    dos.writeUTF(name);
    // zapíšeme číslo do výstupního proudu
    dos.writeUTF(number);
    // převedeme ByteArrayOutputStream na pole bajtů
    byte[] bytes = buffer.toByteArray();
    // přidáme záznam do databáze
    store.addRecord(bytes, 0, bytes.length);
} catch (Exception e) {
    e.printStackTrace();
} finally {
    try {
       // ať se děje, co se děje, zavřeme databázi i výstupní proud
       store.closeRecordStore();
       dos.close();
    } catch (Exception e2) {
       e2.printStackTrace();
    }
}

Přečtení záznamu z databáze (domyslete si obalení try – catch blokem):

byte[] bytes = store.getRecord(i);
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
String name = dis.readUTF();
String number = dis.readUTF();

V databázi je samozřejmě možné záznamy mazat – metoda deleteRecord(int id), či smazat celou databázi – metoda deleteRecordStore(String name), ale na tom není nic zajímavého. Zajímavější je třídění či filtrování záznamů.

Třídění záznamů

K třídění záznamů je určeno rozhraní RecordComparator, které má jedinou metodu compare(byte[] b1, byte[] b2), kde b1 a b2 jsou dva záznamy z databáze. Zde je implementace této metody z příkladu:

public int compare(byte[] b1, byte[] b2) {
    try {
       // přečtení prvního textu z pole bajtů,
       // v našem případě to je jméno
       String s1 =
          new DataInputStream(
             new ByteArrayInputStream(b1)).readUTF();
       String s2 =
          new DataInputStream(
             new ByteArrayInputStream(b2)).readUTF();
       // porovnání jmen z prvního a druhého záznamu
       int i = s1.compareTo(s2);
       if (i == 0) {
          // jména jsou stejná
          return RecordComparator.EQUIVALENT;
       } else if (i < 0) {
          // první jméno je dříve než druhé
          return RecordComparator.PRECEDES;
       } else {
          // první jméno je později než druhé
          return RecordComparator.FOLLOWS;
       }
    } catch (Exception e) {
       e.printStackTrace();
    }
    // nastane-li nějaká neočekávaná výjimka, musíme také něco vrátit
    return RecordComparator.EQUIVALENT;
}

Filtrování záznamů

Chceme-li filtrovat záznamy v databázi, musíme implementovat rozhraní RecordFilter, které má také pouze jednu metodu, a to matches(byte[] candidate).

// vrátí true, pokud jméno v záznamu začíná na daný řetězec
public boolean matches(byte[] b) {
   try {
     String s =
      new DataInputStream(
         new ByteArrayInputStream(b)).readUTF();
     // aby nezáleželo na velikosti písmen, je prefix při zadání
     // zkonvertován na malá písmena a zde
     // konvertujeme text, který porovnáváme
     if (s.toLowerCase().startsWith(prefix)) {
       return true;
     }
  } catch (Exception e) {
     e.printStackTrace();
   }
   return false;
}

Teď už jen stačí zadat metodě enumerateRecords(RecordFilter filter, RecordComparator comparator, boolean keepUpdated) třídy RecordStore naši implementaci filtru a třídy, která záznamy porovnává, a vrácená enumerace bude setříděná a bude obsahovat pouze záznamy, které náš filtr schválí. Chceme-li získat enumeraci všech záznamů, můžeme zadat místo filtru pouze null.

Celý příklad…

…se skládá ze dvou souborů:

  • AddressMIDlet.java – hlavní třída příkladu
  • Storage.java – třída pro práci s databází
  • Pokud se zde náhodou najde nějaký laskavý čtenář, kterého příklad zaujal, může si na procvičení tématu udělat následující rozšíření, která by z dnešního příkladu udělala něco prakticky použitelného:

    • přidat k příkladu mazání záznamů
    • přidat možnost zadání více údajů, než jen jména a telefonního čísla (např. data narození, velikosti bot, počtu zubů apod.)
    • přidat formulář na vyhledávání, který použije již implementovaný RecordComparator
    • rozšířit vyhledávání i na jiné položky než jméno

    Odkazy, zdroje

    Starší komentáře ke článku

    Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

1 Příspěvěk v diskuzi

  1. Zdravim, diky za ukazkovy priklad. Trochu jsem se zadrhl na smazani a upravy zaznamu. Problem zrejme spociva v pridelovani ID. Pokud mazu zaznam pouze pomoci deleteREcord(int id) tak pak je pri dalsim zapisu zaznamu problem v tom, ze se zrejme ukladaji na mista vymaznych zaznamu a dale pak je problem. Diky za odpoved

Odpovědět