poniedziałek, 4 października 2010

Własna baza danych SQLite w Android

System operacyjny Android stworzony dla urządzeń mobilnych takich jak: telefony komórkowe, tablety czy netbooki pozwala na skorzystanie z bazy danych SQLite. Sporo materiałów w Internecie pokazuje jak krok po kroku utworzyć bazę danych, a następnie wprowadzać do niej dane. W tym artykule chciałbym pokazać jak przenieść do telefonu istniejącą bazę danych. Zakładam, że plik z bazą danych może być większy niż 1MB, dlatego użyjemy kompresji. Android posiada ograniczenie, które powoduje, że nie można skorzystać z pliku zapisanego w katalogu assets większego niż 1 MB.

Zanim utworzymy archiwum .zip z bazą danych należy lekko zmodyfikować już istniejącą bazę. Plik z bazą należy otworzyć za pomocą odpowiedniego narzędzia, które pozwoli na dodanie tabeli oraz zmianę nazwy kluczy głównych. Jednym z takich narzędzi jest wtyczka dla przeglądarki internetowej Firefox o nazwie: SQLite Manager. Do istniejącej bazy należy wprowadzić nową tabelę za pomocą polecenia SQL:

CREATE TABLE "android_metadata" ("locale" TEXT DEFAULT 'en_US')

oraz dodać jeden wpis:

INSERT INTO "android_metadata" VALUES ('en_US')


Teraz dla wszystkich tabel należy zmienić klucz główny na _id;



Następnym krokiem jest utworzenie archiwum zip zawierającym jedynie plik z bazą danych. Kopiujemy go do folderu assets, który znajduje się w projekcie naszej aplikacji.



Teraz dodajemy do projektu nową klasę o nazwie: DataHelper, która rozszerza klasę: SQLiteOpenHelper. Do podanej klasy kopiujemy poniższy kod.



package org.axlinux.android;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DataHelper extends SQLiteOpenHelper
{

private static String DATABASE_DIR = "/data/data/org.axlinux.android/databases/"; //Ścieżka do katalogu gdzie przechowywane są bazy danych
private static String DATABASE_NAME = "my_books.sqlite"; //Nazwa pliku z bazą danych
private static String DATABASE_ZIP = "my_books.sqlite.zip"; //Nazwa pliku zip w którym znajduje się skompresowany plik z bazą danych
private SQLiteDatabase db;
private final Context context;

public DataHelper(Context context)
{
super(context, DATABASE_NAME, null, 1);
this.context = context;
}

@Override
public void onCreate(SQLiteDatabase arg0)
{
//Implementację tej metody można pominąć

}

@Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2)
{
//Implementację tej metody można pominąć

}

/**
* Zadaniem metody jest sprawdzenie czy istnieje plik z bazą danych
* @return true jeżeli plik w katalogu istnieje,
* false gdy nie istnieje - nie został jeszcze skopiowany z assets do katalogu w którym android przechowuje pliki baz danych
*/
public boolean dataBaseExists()
{
File f = new File(DATABASE_DIR);
if(!f.exists())
f.mkdir();

boolean result = true;
SQLiteDatabase db_test = null;

try{
db_test = SQLiteDatabase.openDatabase(DATABASE_DIR+DATABASE_NAME, null, SQLiteDatabase.OPEN_READONLY);
} catch (Exception ex){
result = false;
}

if(db_test != null)
db_test.close();


return result;
}

/**
* Metoda rozpakowuje plik z assets do katalogu gdzie Android trzyma bazy danych pod warunkiem, że już nie zostało to zrobione
* @throws IOException
*/
public void copyDataBase() throws IOException
{
if(!this.dataBaseExists())
{
File f = new File(DATABASE_DIR);
if(!f.exists())
f.mkdir();

ZipInputStream zis = new ZipInputStream(context.getAssets().open(DATABASE_ZIP));
ZipEntry entry;

entry = zis.getNextEntry();

int BUFFER = 2048;
FileOutputStream fos = new FileOutputStream(DATABASE_DIR + DATABASE_NAME);
BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
int count;
byte data[] = new byte[BUFFER];

while ((count = zis.read(data, 0, BUFFER)) != -1)
dest.write(data, 0, count);


dest.flush();
dest.close();
zis.close();
}
}

/**
* Metoda otwiera bazę danych, po wywołaniu tej metody można wykonywać polecenie SQL
* @return - true gdy otwarcie się powiodło
*/
public boolean open()
{
boolean result = false;

try{
String myPath = DATABASE_DIR + DATABASE_NAME;
db = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE);
result = true;
} catch (Exception ex) {
//Otwarcie bazy danych nie powiodło się}
}

return result;
}

public synchronized void close() {
if(db != null)
db.close();
super.close();

}

/**
* Wykonuje zapytanie SQL
* @param query - zapytanie SQL
* @return zwraca Stringa z rezultatem
*/
public String executeQuery(String query)
{
String result = "";
Cursor cursor = db.rawQuery(query, null);

if(cursor.moveToFirst())
{
do
{
result += cursor.getString(0) +". "+ cursor.getString(1) +" "+ cursor.getString(2) +"\n";
}while(cursor.moveToNext());
}

return result;
}
}



Klasa z metodą onCreate rozszerzająca klasę Activity



package org.axlinux.android;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class Main extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

String out ="";

TextView tv = (TextView) this.findViewById(R.id.TextView01);
DataHelper dh = new DataHelper(this);

out += "Czy istnieje baza danych? " + dh.dataBaseExists()+"\n";
try
{
dh.copyDataBase();
out += "Czy istnieje baza danych po skopiowaniu? " + dh.dataBaseExists()+"\n";
out +="Rezultat otworzenia bazy danych: " + dh.open()+"\n";
out += "Wynik zapytania \n" + dh.executeQuery("SELECT * FROM books limit 3;");
} catch (Exception ex)
{
out += ex.toString() +"\n";
}

tv.setText(out);

}
}



Pierwszym krokiem po utworzeniu nowej klasy jest modyfikacja pól klasy. Należy odpowiednio zmodyfikować wartości: DATABASE_DIR, DATABASE_NAME, DATABASE_ZIP.

DATABASE_DIR to miejsce gdzie Android przechowuje pliki z bazami danych. Należy poprawnie wprowadzić nazwę głównego pakietu. DATABASE_NAME to nazwa naszej bazy danych, która jest umieszczona wewnątrz archiwum zip, a DATABASE_ZIP to nazwa archiwum z bazą danych w katalogu assets.


W metodzie onCreate możemy teraz stworzyć instancję klasy DataHelper i wywołać metodę copyDataBase. Po zakończeniu warto wywołać metodę dataBaseExists() w celu sprawdzenia rezultatu.

Brak komentarzy:

Prześlij komentarz