Advertisement
  1. Code
  2. Android SDK

Android od podstaw: jak przechowywać dane aplikacji lokalnie

Scroll to top
Read Time: 7 min
This post is part of a series called Android From Scratch.
Android From Scratch: How to Use Resources In an Application
Android From Scratch: Background Operations

Polish (Polski) translation by Mateusz Kurlit (you can also view the original English article)

Jeśli chodzi o przechowywanie danych aplikacji lokalnie, deweloperzy Androida stają przed trudnym wyborem. Oprócz bezpośredniego dostępu do wewnętrznej i zewnętrznej pamięci urządzenia Android, platforma oferuje również bazy danych SQLite do przechowywania relacyjnych danych oraz specjalnych plików dla par klucz-wartość. Co więcej, aplikacje na Androida mogą również korzystać z baz danych firm trzecich, które oferują wsparcie dla NoSQL.

W tym poradniku, pokażę ci jak korzystać ze wszystkich opcji magazynu w aplikacjach na Androida. Spróbuję również wyjaśnić jak dobrać najbardziej odpowiednią opcję dla twoich danych.

Łatwiej uczysz się za pomocą wideo? Obejrzyj nasz kurs:

1. Przechowywanie par klucz-wartość

Jeśli szukasz szybkiego sposobu na przechowanie kilku ciągów lub liczb, powinieneś rozważyć użycie pliku preferences. Aktywności i usługi Androida mogą korzystać z metody getDefaultSharedPreferences() klasy PreferenceManager, aby uzyskać odniesienie do obiektu SharedPreferences, który może być użyty do odczytu i zapisu do domyślnego pliku preferencji.

1
SharedPreferences myPreferences
2
    = PreferenceManager.getDefaultSharedPreferences(MyActivity.this);

Aby rozpocząć zapisywanie do pliku preferencji, musisz wywołać metodę edit() obiektu SharedPreferences, która zwraca obiekt SharedPreferences.Editor.

1
SharedPreferences.Editor myEditor = myPreferences.edit();

Obiekt SharedPreferences.Editor posiada kilka intuicyjnych metod, które możesz wykorzystać do przechowania nowych par klucz-wartość w pliku preferencji. Na przykład, możesz użyć metody putString(), aby umieścić parę klucz-wartość, której wartość jest typu String. Podobnie, możesz skorzystać z metody putFloat(), aby umieścić parę klucz wartość, której wartość jest typu float. Poniższe fragmenty kodu tworzą trzy pary klucz-wartość:

1
myEditor.putString("NAME", "Alice");
2
myEditor.putInt("AGE", 25);
3
myEditor.putBoolean("SINGLE?", true);

Po dodaniu wszystkich par, musisz wywołać metodę comit() obiektu SharedPreferences.Editor, aby je zachować.

1
myEditor.commit();

Odczytywanie z obiektu SharedPreferences jest znacznie łatwiejsze. Wystarczy, że wywołasz odpowiednią metodę get*(). Przykładowo, aby uzyskać parę klucz wartość, której wartość jest typu String, musisz wywołać metodę getString(). Poniżej znajduje się fragment kodu wyszukujący wszystkie wartości dodane wcześniej:

1
String name = myPreferences.getString("NAME", "unknown");
2
int age = myPreferences.getInt("AGE", 0);
3
boolean isSingle = myPreferences.getBoolean("SINGLE?", false);

Jak widać w powyższym kodzie, wszystkie metody get*(), jako drugiego parametru, oczekują domyślnej wartości, która musi zostać zwrócona, jeśli klucz nie znajduje się w pliku preferencji.

Zwróć uwagę, że pliki preferencji są ograniczone tylko do ciągów i prostych typów danych. Jeśli chcesz przechowywać bardziej złożone typy danych lub dane binarne, musisz wybrać inną opcję magazynu.

2. Korzystanie z bazy danych SQLite

Każda aplikacja może utworzyć i wykorzystać bazy danych SQLite do przechowywania dużych ilości uporządkowanych danych. Jak zapewne już wiesz, SQLite jest nie tylko lekka, ale również bardzo wydajna. Może być twoją preferowaną opcją magazynu, jeśli masz doświadczenie w pracy z systemami zarządzania relacyjnymi bazami danych i znasz SQL (Structured Query Language) oraz JDBC (Java Database Connectivity).

Aby utworzyć nową bazę danych SQLite lub otworzyć już istniejącą, możesz skorzystać z metody openOrCreateDatabase() w aktywności lub usłudze. Jako argument, musisz przekazać nazwę bazy danych i tryb, w którym chcesz ją otworzyć. Najczęściej używanym trybem jest MODE_PRIVATE, który umożliwia uzyskanie dostępu do bazy danych tylko przez aplikację.  Na przykład, oto jak otworzyć lub utworzyć bazę danych o nazwie my.db:

1
SQLiteDatabase myDB = 
2
    openOrCreateDatabase("my.db", MODE_PRIVATE, null);

Po utworzeniu bazy danych, możesz użyć metody execSQL(), aby uruchomić na niej instrukcje SQL. Poniższy kod pokazuje jak użyć instrukcji CREATE TABLE, aby utworzyć tabelę o nazwie user, która zawiera trzy kolumny:

1
myDB.execSQL(
2
    "CREATE TABLE IF NOT EXISTS user (name VARCHAR(200), age INT, is_single INT)"
3
);

Chociaż istnieje możliwość wstawienia nowych wierszy do tabeli za pomocą metody execSQL(), lepiej skorzystać z metody insert(). Metoda insert() oczekuje obiektu ContentValues zawierającego wartości dla każdej kolumny tabeli. Obiekt ContentValues jest bardzo podobny do obiektu Map i zawiera pary klucz-wartość.

Poniżej znajdują się obiekty ContentValues, które możesz umieścić w tabeli user:

1
ContentValues row1 = new ContentValues();
2
row1.put("name", "Alice");
3
row1.put("age", 25);
4
row1.put("is_single", 1);
5
6
ContentValues row2 = new ContentValues();
7
row2.put("name", "Bob");
8
row2.put("age", 20);
9
row2.put("is_single", 0);

Jak zapewne zauważyłeś, klucze, które przekazujesz metodzie put() muszą pasować do nazw kolumn w tabeli.

Gdy twoje obiekty ContentValues są gotowe, możesz przekazać je metodzie insert() razem z nazwą tabeli.

1
myDB.insert("user", null, row1);
2
myDB.insert("user", null, row2);

Aby utworzyć zapytanie do bazy danych, możesz użyć metody rawQuery(), która zwraca obiekt Cursor zawierający wyniki zapytania.

1
Cursor myCursor = 
2
    myDB.rawQuery("select name, age, is_single from user", null);

Obiekt Cursor może zawierać zero lub więcej wierszy. Najłatwiejszym sposobem na zapętlenie wszystkich wierszy jest wywołanie metody moveToNext() wewnątrz pętli while.

Aby pobrać wartość z pojedynczej kolumny, musisz skorzystać z metod getString() lub getInt(), które oczekują indeksu kolumny. Przykładowo, oto jak pobrać wszystkie wartości wstawione do tabeli user:

1
while(myCursor.moveToNext()) {
2
    String name = myCursor.getString(0);
3
    int age = myCursor.getInt(1);
4
    boolean isSingle = (myCursor.getInt(2)) == 1 ? true:false;
5
}

Po pobraniu wszystkich wyników zapytania, upewnij się, że wywołałeś metodę close() obiektu Cursor w celu uwolnienia wszystkich zasobów, które zawiera.

1
myCursor.close();

Podobnie, gdy zakończysz wszystkie operacje bazy danych, nie zapomnij wywołać metody close() obiektu SQLiteDatabase.

1
myDB.close();

3. Korzystanie wewnętrznej pamięci

Każda aplikacja na Androida posiada powiązany z nią prywatny katalog pamięci wewnętrznej, w którym aplikacja może przechowywać pliki tekstowe i binarne. Pliki znajdujące się w tym katalogu nie są dostępne dla użytkownika lub innych aplikacji zainstalowanych na urządzeniu. Są również automatycznie usuwane, gdy użytkownik odinstaluje aplikacje.

Aby można było wykorzystać katalog wewnętrznej pamięci, musisz określić jego lokalizację. W tym celu, wywołaj metodę getFilesDir(), która jest dostępna w aktywnościach i usługach.

1
File internalStorageDir = getFilesDir();

Aby uzyskać odniesienie do pliku wewnątrz katalogu, możesz przekazać nazwę pliku wraz z określoną przez ciebie lokalizacją. Na przykład, oto jak uzyskać odniesienie do pliku o nazwie alice.csv:

1
File alice = new File(internalStorageDir, "alice.csv");

Od tego momentu możesz wykorzystać wiedzę na temat klas Java I/O i metod, aby odczytywać lub zapisywać do pliku. Poniższy fragment kodu pokazuje jak użyć obiektu FileOutputStream i jego metody write(), aby zapisać do pliku:

1
// Create file output stream

2
fos = new FileOutputStream(alice);
3
// Write a line to the file

4
fos.write("Alice,25,1".getBytes());
5
// Close the file output stream

6
fos.close();

4. Korzystanie z pamięci zewnętrznej

Ponieważ pojemność pamięci wewnętrznej jest zazwyczaj stała i często ograniczona, część urządzeń z Androidem obsługuje magazyny zewnętrzne takie jak karty microSD. Zalecam wybranie tej opcji magazynu dla dużych plików takich jak zdjęcia i filmy.

W przeciwieństwie do pamięci wewnętrznej, magazyn zewnętrzny nie zawsze jest dostępny. W związku z tym, musisz zawsze sprawdzać czy jest on podłączony. Aby to zrobić, użyj metody getExternalStorageState() klasy Environment.

1
if(Environment.getExternalStorageState()
2
              .equals(Environment.MEDIA_MOUNTED)) {
3
    // External storage is usable

4
} else {
5
    // External storage is not usable

6
    // Try again later

7
}

Po upewnieniu się, że pamieć zewnętrzna jest osiągalna, możesz uzyskać jej katalog poprzez wywołanie metody getExternalFilesDir() i przekazanie null jako jej argumentu. Wtedy możesz wykorzystać ścieżkę jako odniesienia do plików wewnątrz katalogu. Na przykład, oto jak nawiązać do pliku o nazwie bob.jpg w katalogu pamięci zewnętrznej twojej aplikacji.

1
File bob = new File(getExternalFilesDir(null), "bob.jpg");

Prosząc użytkownika o przyznanie uprawnienia WRITE_EXTERNAL_STORAGE, możesz uzyskać dostęp do odczytu/zapisu całego systemu plików w pamięci zewnętrznej. Następnie możesz skorzystać z dobrze znanych katalogów, aby przechowywać zdjęcia, filmy i inne pliki multimedialne. Klasa Environment oferuje metodę o nazwie getExternalStoragePublicDirectory(), która służy do określania ścieżek publicznych katalogów.

Na przykład, przekazując wartość Environment.DIRECTORY_PICTURES do metody, możesz określić ścieżkę publicznego katalogu, w którym możesz przechowywać zdjęcia. Podobnie, jeśli przekażesz wartość Environment.DIRECTORY_MOVIES do metody, uzyskasz ścieżkę publicznego katalogu, w którym mogą być przechowywane filmy.

Oto jak nawiązać do pliku o nazwie bob.jpg w publicznym katalogu ze zdjęciami.

1
File bobInPictures = new File(
2
    Environment.getExternalStoragePublicDirectory(
3
        Environment.DIRECTORY_PICTURES),
4
    "bob.jpg"
5
);

Po uzyskaniu obiektu File, możesz ponownie użyć klas FileInputStream i FileOutputStream, aby odczytywać i zapisywać do niego.

Podsumowanie

Teraz już wiesz jak najlepiej wykorzystać dostarczone przez Android SDK opcje lokalnego magazynu. Niezależnie od tego jaką opcję wybierzesz, operacje odczytu/zapisu mogą być czasochłonne w przypadku dużych ilości danych. Dlatego, aby mieć pewność, że główny wątek UI zawsze będzie responsywny, musisz wdrożyć uruchamianie operacji w różnych wątkach.

Aby dowiedzieć się więcej o zapisywaniu danych aplikacji lokalnie, przeglądnij oficjalny poradnik na temat API magazynu danych.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.