JSR-75, czyli dostęp do systemu plików

fatalbomb

Jeżeli MIDlet korzysta z jakichś danych, to zazwyczaj zapisuje je w pamięci telefonu za pomocą mechanizmu RMS. Może również korzystać z plików zaszytych w archiwum JAR. Obydwa rozwiązania mają poważne wady: z archiwum JAR możemy tylko czytać, a RMS wprawdzie pozwala na zapis, ale danych tych w żaden sposób nie wyciągniemy poza aplikację. Tymczasem wszystkie telefony mają jakąś pamięć, w której trzymamy dzwonki, tapety, zdjęcia, itd., a co lepsze mają nawet slot kart pamięci. Okazuje się, że do tych danych również mamy dostęp.

W tytule tego artykułu mamy JSR-75. Oznacza to, że dostęp do systemu plików nie jest jakimś rdzennym elementem J2ME. Jest to rozszerzenie, niewchodzące w skład specyfikacji MIDP. Dlatego też nie każdy telefon może to obsłużyć. Generalnie jednak wszystkie telefony z MIDP 2.0 obsługują to rozszerzenie.

Przygotowania

Przede wszystkim przyda nam się trochę klas. Dlatego bez wahania dopisujemy na początku naszego kodu:
import java.util.*;
import java.io.*;
import javax.microedition.io.*;
import javax.microedition.io.file.*;

Oczywiście ważne są też ustawienia projektu. Musimy się upewnić, czy jest włączona obsługa JSR-75. To będzie jedno z pól wyboru w oknie ustawień projektu.

Klasa FileConnection

Klasa FileConnection jest najważniejszym elementem. Udostępnia ona niemal wszystkie możliwe funkcje. Najpierw jednak musimy stworzyć obiekt tej klasy, a robi się to w następujący sposób:
FileConnection fc = (FileConnection)Connector.open("file:///ścieżka_dostępu"); 

Przykładowo, aby dostać się do pliku intro.mid znajdującego się w katalogu Dźwięki w pamięci telefonu Sony Ericsson K750i, wywołujemy taki kod:

FileConnection fc = (FileConnection)Connector.open("file:///c:/user/audio/intro.mid"); 
// Możliwe, że nie potrzeba tego "/user". Kwestia telefonu oraz tego, 
// czy przerabialiśmy swój telefon i dobiliśmy jakieś łaty VKP.

Powstaje jednak cała masa pytań: a co, jak nie znamy położenia pliku? Co, jeżeli chcemy, aby użytkownik go pokazał? Dlaczego jest to c:/ na początku?...

Wybór systemu plików

Po pierwsze, wybór systemu plików. Generalnie w telefonach SE zasada jest taka, że c:/ oznacza pamięć telefonu, a e:/ - kartę Memory Stick. Nie jest to jednak aż tak bardzo istotne. Zaraz napiszemy kod, który nam powie, jakie są systemy plików:
Enumeration e =  FileSystemRegistry.listRoots();
while (e.hasMoreElements()) {
            String rootName = (String)e.nextElement();
 System.out.println("System plików "+rootName);
 }  

Korzystamy tu z klasy Enumeration, czyli czegoś w rodzaju listy. Metoda statyczna klasy FileSystemRegistry nazwana listRoots() zwraca obiekt klasy Enumeration, który przechowuje listę dostępnych systemów plików.

Wyświetlenie zawartości katalogu

Wiemy już, że mamy jakieś tam systemy plików, i ładujemy jakiś przekazując go jako argument konstruktora obiektu klasy FileConnection. Teraz wypadałoby się dowiedzieć, do czego możemy się odwoływać.
Enumeration e = fc.list();
while (e.hasMoreElements()) {
 System.out.println(((String)e.nextElement()));
}

Tak naprawdę zmieniliśmy tylko jedną linijkę. Wywołujemy metodę list() utworzonego przez nas obiektu. Teraz możemy wybrać stamtąd jakiś element i przekazać jego pełną ścieżkę do konstruktora kolejnego obiektu klasy FileConnection. W ten sposób możemy poruszać się po systemie plików.

Jak odróżnić plik od katalogu?

Klasa FileConnection nie posiada jednak metody w stylu isDirectory() czy podobnej. Nie jest ona jednak potrzebna. Gdy przejrzysz nazwy plików, zauważysz jeden istotny szczegół: nazwy wszystkich katalogów kończą się znakiem '/'. Dlatego, jeżeli użytkownik wybierze taki plik, wiemy, że chce zmienić katalog. Jeżeli tego znaku tam nie ma, to znaczy, że mamy tam plik.

Odczyt plików

Odczyt plików jest bardzo prosty. Po prostu, jak to z reguły robimy w Javie, tworzymy strumień wejściowy (InputStream):
InputStream is = fc.openInputStream();

Korzystamy z niego tak, jak ze zwykłego strumienia. Przykładowo, chcąc odczytać z pliku 32 bajty, piszemy coś takiego:

byte[] d = new byte[32];
is.read(d); // mamy, co chcieliśmy!

Oczywiście pamiętamy o zamknięciu strumienia na koniec operacji na pliku:

is.close();

Zapis plików

Aby zapisać plik, robimy dokładnie analogiczną rzecz. Tworzymy obiekt klasy OutputStream:
OutputStream is = fc.openOutputStream();

i zapisujemy tablice bajtów. Możemy również skorzystać z klasy DataOutputStream, która daje nam większe możliwości (pisanie nie tylko tablic bajtów, ale i typów prostych)

DataOutputStream ds = fc. openDataOutputStream();

Podobnie w przypadku odczytu, możemy użyć klasy DataInputStream. Zależy to tylko od tego, co chcemy osiągnąć.
Jeżeli plik jeszcze nie istnieje, tworzymy go:

fc.create();

Jeżeli chcemy zaś założyć katalog, to wywołujemy taki kod:

fc.mkdir();

Inne przydatne metody

Oto kilka innych ciekawych metod udostępnianych przez klasę FileConnection:
  • boolean exists() - zwraca true, jeżeli obiekt FileConnection dotyczy już istniejącego pliku - czyli innymi słowy określa, czy plik istnieje
  • boolean canRead() - zwraca true, jeżeli program ma prawo czytania danego pliku
  • boolean canWrite() - zwraca true, jeżeli program ma prawo pisania do danego pliku
  • long fileSize() - zwraca rozmiar pliku w bajtach
  • long lastModified() - zwraca datę ostatniej modyfikacji
  • void delete() - usuwa plik

Obsługa wyjątków

Większośc metod klasy FileConnection może wyrzucać wyjątki. Dlatego musimy zamknąć odpowiedni kod w bloku try...catch. Inaczej kompilator będzie protestował. I nie bez powodu. Mimo, że plik istnieje, może wyskoczyć błąd, mimo, że kod jest w najlepszym porządku. Jeżeli napiszesz odpowiedni program i uruchomisz go na telefonie, zapewne co chwilę telefon będzie się pytał, czy chcesz zezwolić na dostęp do systemu plików. Jeżeli zezwolimy, kod pójdzie jak chcemy, ale jeżeli klikniemy nie... No właśnie, co wtedy? Aby przekonać się o tym, wpisz w bloku catch następujący kod:
...
catch (Exception e)
{
 Alert katastrofa = new Alert ("Katastrofa!",e.toString());
 Display.getDisplay(this).setCurrent(katastrofa);
}

Uruchom program na telefonie i zabroń programowi dostępu do systemu plików. W takiej sytuacji wyrzucony zostanie wyjątek bezpieczeństwa - SecurityException. Na to nic niestety nie możemy poradzić. Jest to dość uciążliwa przypadłość m. in. w telefonie SE K750i, który w ustawieniach aplikacji pozwala na ustawienie tylko dwóch opcji: Nie i Zawsze pytaj. Możemy zatem albo zablokować w ogóle dostęp do systemu, albo zezwolić, ale telefon będzie się nas pytał przy każdym wywołaniu konstruktora klasy FileConnection i strumieni.

<small>Nawiasem mówiąc, jest na to (karkołomna) metoda. Wystarczy wgrać łatę VKP, która zlikwiduje tą nieufność KVM-u. Niestety, wgranie łaty to nic innego jak przeflashowanie fragmentu firmware telefonu, a zatem wiąże się z utratą gwarancji i jest trochę ryzykowne. Ten temat wykracza w ogóle poza ramy tego serwisu</small>.

2 komentarzy

Na http://library.forum.nokia.com/ znaleźć można javadoki (rozwinąć gałąź 'Java ME Developer's Library', następnie 'Javadocs' i 'JSR-75 File Connection API')

Fajny artykuł. Trzeba będzie przetestować :)