SQLite на SD карте

SQLite, Preferences, файлы, SD, Content Provider, XML, JSON
Ответить
tyapavel
Сообщения: 11
Зарегистрирован: 09 янв 2012, 01:31

SQLite на SD карте

Сообщение tyapavel » 31 янв 2012, 17:01

Как-то мне понадобилось создать в приложении базу данных и сохранить её на карте памяти. Использовать для этого SQLiteOpenHelper не получилось, потому что он создаёт базу не на карте, а в телефоне, и скопировать её потом без root проблематично. damager82 помог разобраться с SQLiteDatabase.openOrCreateDatabase(), огромное ему спасибо. Я написал класс - MyDataBaseOpenHelper, выкладываю может кому пригодится.

Код: Выделить всё

import java.io.File;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;

public abstract class MyDataBaseOpenHelper {
	private SQLiteDatabase db;
	private SQLiteDatabase readDb;
	private SQLiteDatabase writeDb;
	private File dbFile;
	private String dbName;
	private boolean isExistDB;
	public MyDataBaseOpenHelper(Context context, String dbName) {
		super();
		this.isExistDB = false;
		this.dbName = dbName;
		this.prepareBD();
		if(!isExistDB){
			onCreate(db);
		}
		this.setReadDb();
		this.setWriteDb();
	}

	private void prepareBD() {
		try{
		    File sdCard = Environment.getExternalStorageDirectory();
		    File directory = new File(sdCard.getAbsolutePath () + "/" + dbName);
		    directory.mkdirs();
		    dbFile = new File(directory, dbName);
		    if(dbFile.exists())isExistDB=true;
		    this.db = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
		}catch (Exception e) {
			this.db = SQLiteDatabase.openOrCreateDatabase(dbName, null);
		}
	}

	abstract public void onCreate(SQLiteDatabase db);

	abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);

	public void close(){
		db.close();
	        writeDb.close();
	        readDb.close();
	}

	public SQLiteDatabase openMyDb() {
		SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
		return db;
	}

	public SQLiteDatabase getReadableDatabase(){
		return this.readDb;
	}

	private void setReadDb(){
	SQLiteDatabase resdDb = SQLiteDatabase.openDatabase(dbFile.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
		this.readDb = resdDb;
	}

	public SQLiteDatabase getWritableDatabase(){
		return this.writeDb;
	}

	private void setWriteDb(){
	SQLiteDatabase resdDb = SQLiteDatabase.openDatabase(dbFile.getAbsolutePath(), null, SQLiteDatabase.OPEN_READWRITE);
	    this.writeDb = resdDb;
	}
}
Вопрос у меня вот какой! За объектом SQLiteOpenHelper следит активность в которой он создан вметоде onCreate(), и переопределять onDestroy() не нужно, наверно для этого и передаётся в конструкторе SQLiteOpenHelper Context. Объект моего класса обязательно надо закрыть явно в onDestroy() Activity. Какой метод в Context отвечает за закрытие открытых баз данных. Извините если что-то не так. Я не профессионал.
Последний раз редактировалось tyapavel 01 фев 2012, 20:36, всего редактировалось 1 раз.

Аватара пользователя
damager82
Администратор
Сообщения: 1383
Зарегистрирован: 07 янв 2012, 11:32
Контактная информация:

Re: SQLite на SD карте

Сообщение damager82 » 01 фев 2012, 16:35

tyapavel писал(а):За объектом SQLiteOpenHelper следит активность в которой он создан в методе onCreate()
Я не встречал у Activity возможности следить за SQLiteOpenHelper. Вы уверены?
Activity умеет управлять объектом Cursor, если вызвать метод startManagingCursor.
Добро пожаловать на форум сайта StartAndroid
ИзображениеИзображение

tyapavel
Сообщения: 11
Зарегистрирован: 09 янв 2012, 01:31

Re: SQLite на SD карте

Сообщение tyapavel » 01 фев 2012, 20:42

Сделал два простеньких тестовых приложени. Одно с родным SQLiteOpenHelper другое с моим MyDataBaseOpenHelper
1.)

Код: Выделить всё

import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;


public class MainActivity extends Activity {
	DbHelper dbHelper;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        dbHelper = new DbHelper(this, "Test");
    }
    
    class DbHelper extends SQLiteOpenHelper{

		public DbHelper(Context context, String name) {
			super(context, name, null, 1);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL("create table TestDataDase(_id integer primary key autoincrement, firstname text, lastname text)");
			ContentValues cv = new ContentValues();
	    	        cv.put("firstname", "Fyodor");
	    	        cv.put("lastname","Lastochkin");
	    	        db.insert("TestDataDase", null, cv);
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			db.execSQL("DROP TABLE IF EXISTS TestDataDase");
			onCreate(db);
		}
    	
    }
}
2.)

Код: Выделить всё

import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;


public class MainActivity extends Activity {
	DbHelper dbHelper;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        dbHelper = new DbHelper(this, "Test");
    }
    
    class DbHelper extends MyDataBaseOpenHelper{

		public DbHelper(Context context, String name) {
			super(context, name);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL("create table TestDataDase(_id integer primary key autoincrement, firstname text, lastname text)");
			ContentValues cv = new ContentValues();
	    	        cv.put("firstname", "Fyodor");
	    	        cv.put("lastname","Lastochkin");
	    	        db.insert("TestDataDase", null, cv);
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			db.execSQL("DROP TABLE IF EXISTS TestDataDase");
			onCreate(db);
		}
    	
    }
}
В первомслучае программа закрывается нормально, а во втором LogCat выводит

02-01 17:21:10.564: E/Database(282): close() was never explicitly called on database '/mnt/sdcard/Test/Test'
02-01 17:16:27.914: E/Database(282): android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here

А если во втором случае переопределить onDestroy() и вызвать dbHelper.close(), то всё нормально! :?:

Аватара пользователя
damager82
Администратор
Сообщения: 1383
Зарегистрирован: 07 янв 2012, 11:32
Контактная информация:

Re: SQLite на SD карте

Сообщение damager82 » 02 фев 2012, 00:59

Все верно.

В первом случае подключение не открывается.

А во втором вы в конструкторе суперкласса открываете подключения и их надо закрывать перед выходом из приложения.
Добро пожаловать на форум сайта StartAndroid
ИзображениеИзображение

tyapavel
Сообщения: 11
Зарегистрирован: 09 янв 2012, 01:31

Re: SQLite на SD карте

Сообщение tyapavel » 02 фев 2012, 14:30

Понятно. Изменил суперкласс. Теперь Actyvity.onDestroy() можно не переопределять.

Код: Выделить всё

import java.io.File;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;

public abstract class MyDataBaseOpenHelper {

        private SQLiteDatabase db;
        private File dbFile;
        private String dbName;
        private boolean isExistDB;

        public MyDataBaseOpenHelper(Context context, String dbName) {
            super();
            this.isExistDB = false;
            this.dbName = dbName;
            this.prepareBD();
            if(!this.isExistDB){
                    onCreate(db);
            }
            db.close();
        }
        
        private void prepareBD() {
            try{
                File sdCard = Environment.getExternalStorageDirectory();
                File directory = new File(sdCard.getAbsolutePath () + "/" + dbName);
                directory.mkdirs();
                dbFile = new File(directory, dbName);
                if(dbFile.exists())this.isExistDB=true;
                this.db = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
            }catch (Exception e) {}
        }

        abstract public void onCreate(SQLiteDatabase db);
        
        abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);

        public void close(){
                db.close();
        }
        
        public SQLiteDatabase getReadableDatabase(){
        	SQLiteDatabase resdDb = SQLiteDatabase.openDatabase(dbFile.getAbsolutePath(), null, SQLiteDatabase.OPEN_READWRITE);
            return resdDb;
        }

        public SQLiteDatabase getWritableDatabase(){
        	SQLiteDatabase resdDb = SQLiteDatabase.openDatabase(dbFile.getAbsolutePath(), null, SQLiteDatabase.OPEN_READWRITE);
        	return resdDb;
        }
}

SKR
Сообщения: 13
Зарегистрирован: 30 мар 2012, 09:54
Откуда: Россия, респ. Мордовия, пгт. Торбеево

Re: SQLite на SD карте

Сообщение SKR » 24 апр 2012, 08:43

tyapavel, спасибо, очень пригодилось!

beeline09
Сообщения: 33
Зарегистрирован: 23 сен 2012, 23:10

Re: SQLite на SD карте

Сообщение beeline09 » 10 мар 2013, 13:49

У меня есть одна проблема. Есть класс ExternalDbOpenHelper, который открывает мою базу с флешки, а если ее нет, то копирует ее из assets в папку на флешке. В нем база открывается в режиме readwrite. Так вот, при сохранении записей в БД, они есть и я спокойно отображаю нужное значение в spinner-е, но после пересоздания активити они все пропадают.

Вот класс:

Код: Выделить всё

;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Environment;
import android.util.Log;

public class ExternalDbOpenHelper extends SQLiteOpenHelper {

	//Путь к папке с базами на устройстве
	public static String DB_PATH;
	//Имя файла с базой
	public static String DB_NAME;
	public SQLiteDatabase database;
	public final Context context;
	
	final String DIR_SD = "Hudeem";
	final String dbname = "data.db";

	public SQLiteDatabase getDb() {
		return database;
	}

	public ExternalDbOpenHelper(Context context, String databaseName) {
		super(context, databaseName, null, 1);
		this.context = context;
		//Составим полный путь к базам для вашего приложения
		File sdPath = Environment.getExternalStorageDirectory();
		DB_NAME = databaseName;
		DB_PATH = String.format(sdPath.getAbsolutePath() + "/" + DIR_SD + "/", DB_NAME);
		try {
			copyDataBase();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		openDataBase();
	}

	//Создаст базу, если она не создана
	public void createDataBase() {
		boolean dbExist = checkDataBase();
		if (!dbExist) {
			this.getReadableDatabase();
			try {
				copyDataBase();
			} catch (IOException e) {
				Log.e(this.getClass().toString(), "Copying error");
				throw new Error("Error copying database!");
			}
		} else {
			Log.i(this.getClass().toString(), "Database already exists");
		}
	}
	//Проверка существования базы данных
	private boolean checkDataBase() {
		SQLiteDatabase checkDb = null;
		try {
			String path = DB_PATH + DB_NAME;
			checkDb = SQLiteDatabase.openDatabase(path, null,
					SQLiteDatabase.OPEN_READONLY);
		} catch (SQLException e) {
			Log.e(this.getClass().toString(), "Error while checking db");
		}
		//Андроид не любит утечки ресурсов, все должно закрываться
		if (checkDb != null) {
			checkDb.close();
		}
		return checkDb != null;
	}
	//Метод копирования базы
	private void copyDataBase() throws IOException {
		// Открываем поток для чтения из уже созданной нами БД
		//источник в assets
		InputStream externalDbStream = context.getAssets().open(DB_NAME);

		// Путь к уже созданной пустой базе в андроиде
		String outFileName = DB_PATH + DB_NAME;

		// Теперь создадим поток для записи в эту БД побайтно
		OutputStream localDbStream = new FileOutputStream(outFileName);

		// Собственно, копирование
		byte[] buffer = new byte[1024];
		int bytesRead;
		while ((bytesRead = externalDbStream.read(buffer)) > 0) {
			localDbStream.write(buffer, 0, bytesRead);
		}
		// Мы будем хорошими мальчиками (девочками) и закроем потоки
		localDbStream.close();
		externalDbStream.close();

	}

	public SQLiteDatabase openDataBase() throws SQLException {
		String path = DB_PATH + DB_NAME;
		createDataBase();
		if (database == null) {
			createDataBase();
			database = SQLiteDatabase.openDatabase(path, null,
				SQLiteDatabase.OPEN_READWRITE);
		}
		return database;
	}
	@Override
	public synchronized void close() {
		if (database != null) {
			database.close();
		}
		super.close();
	}
	@Override
	public void onCreate(SQLiteDatabase db) {}
	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
Вот так записываю данные:

Код: Выделить всё

ExternalDbOpenHelper extdb = new ExternalDbOpenHelper(this, DB_NAME);
db = extdb.openDataBase();
String sql = "INSERT INTO Data(data, title,  ves, kkal100, itogo)  VALUES ("+"'"+date_time+"'"+","+"'"+title+"'"+","+"'"+ves+"'"+","+"'"+kkal100+"'"+","+"'"+itogo_str+"'"+")";
						db.execSQL(sql);
Obnovut_spiner_iz_bazy();
Spinner обновляется и новая строчка в нем появляется. Но как только переоткрою активити, то никаких изменений в БД не замечается. Т.е. она не записалась :-(

MoonNah
Сообщения: 2
Зарегистрирован: 16 мар 2013, 00:27

Re: SQLite на SD карте

Сообщение MoonNah » 16 мар 2013, 00:36

beeline09 писал(а):Spinner обновляется и новая строчка в нем появляется. Но как только переоткрою активити, то никаких изменений в БД не замечается. Т.е. она не записалась :-(
Брат, ты все-таки дело с БД имеешь :)

Код: Выделить всё

ExternalDbOpenHelper extdb = new ExternalDbOpenHelper(this, DB_NAME);
db = extdb.openDataBase();
			try {
				db.beginTransaction();

                                                   String sql = "INSERT INTO Data(data, title,  ves, kkal100, itogo)  VALUES ("+"'"+date_time+"'"+","+"'"+title+"'"+","+"'"+ves+"'"+","+"'"+kkal100+"'"+","+"'"+itogo_str+"'"+")";
				db.execSQL(sql);

				db.setTransactionSuccessful();
			} catch (SQLException e) {
			} finally {
				db.endTransaction();
			}
Obnovut_spiner_iz_bazy();

без транзакции фиг что в базу запишется :)

beeline09
Сообщения: 33
Зарегистрирован: 23 сен 2012, 23:10

Re: SQLite на SD карте

Сообщение beeline09 » 18 мар 2013, 08:07

MoonNah писал(а):
без транзакции фиг что в базу запишется :)
А что мне даст это? Если у меня база одна. Доступ к ней только из одной активти, пишу в нее только в том куске кода, который указал выше, курсор закрывается каждый раз. И что значит фиг знает что? Хочешь сказать, что разные значения не в свои столбцы могут попасть?

MoonNah
Сообщения: 2
Зарегистрирован: 16 мар 2013, 00:27

Re: SQLite на SD карте

Сообщение MoonNah » 21 мар 2013, 01:40

beeline09 писал(а):А что мне даст это?
так, давай определимся, ты хочешь подискутировать на эту тему, или хочешь чтобы данные сохранились в базе , если второе, то используй транзакцию, потому что
1. при таком подходе база будет залочена для любого другого обращения (сам ты не сможешь это гарантировать на 100%)
2. в базу запишется корректно инфа, если там будут ошибки, то все откатится и не положит базу
3. Самое главное, что информация физически запишется в файл базы данных

Короче проверяй и убедишься (просто я так работаю в своей программе переводчике и программе контроля за автомобилем со спутниковой сигналкой и неоднократно сталкивался с подобным вопросом, а на начальном этапе и сам ловил такую плюху, да и мануал SQLite, как и у других БД, рекомендует пользовать транзакции).

И еще нафига ты такое в запросе нагородил (куча ненужных плюсов и кавычек)

Код: Выделить всё

  String sql = "INSERT INTO Data(data, title,  ves, kkal100, itogo)  VALUES ("+"'"+date_time+"'"+","+"'"+title+"'"+","+"'"+ves+"'"+","+"'"+kkal100+"'"+","+"'"+itogo_str+"'"+")";
когда гораздо удобоваримее написать было так

Код: Выделить всё

  String sql = "INSERT INTO Data(data, title,  ves, kkal100, itogo)  VALUES ('"+date_time+"', '"+title+"', '"+ves+"', '"+kkal100+"', '"+itogo_str+"')";

beeline09
Сообщения: 33
Зарегистрирован: 23 сен 2012, 23:10

Re: SQLite на SD карте

Сообщение beeline09 » 21 мар 2013, 12:22

MoonNah писал(а):
beeline09 писал(а):А что мне даст это?
так, давай определимся, ты хочешь подискутировать на эту тему, или хочешь чтобы данные сохранились в базе , если второе, то используй транзакцию, потому что
1. при таком подходе база будет залочена для любого другого обращения (сам ты не сможешь это гарантировать на 100%)
2. в базу запишется корректно инфа, если там будут ошибки, то все откатится и не положит базу
3. Самое главное, что информация физически запишется в файл базы данных

Короче проверяй и убедишься (просто я так работаю в своей программе переводчике и программе контроля за автомобилем со спутниковой сигналкой и неоднократно сталкивался с подобным вопросом, а на начальном этапе и сам ловил такую плюху, да и мануал SQLite, как и у других БД, рекомендует пользовать транзакции).

И еще нафига ты такое в запросе нагородил (куча ненужных плюсов и кавычек)

Код: Выделить всё

  String sql = "INSERT INTO Data(data, title,  ves, kkal100, itogo)  VALUES ("+"'"+date_time+"'"+","+"'"+title+"'"+","+"'"+ves+"'"+","+"'"+kkal100+"'"+","+"'"+itogo_str+"'"+")";
когда гораздо удобоваримее написать было так

Код: Выделить всё

  String sql = "INSERT INTO Data(data, title,  ves, kkal100, itogo)  VALUES ('"+date_time+"', '"+title+"', '"+ves+"', '"+kkal100+"', '"+itogo_str+"')";
Хорошо, не могу не согласиться - сделаю в следующей версии. Сейчас уже обновленная в маркете лежит.
А на счет запроса да, торопился вот и нагородил. Я это уже исправил ))
Спасибо за помощь!

AnDron
Сообщения: 1
Зарегистрирован: 13 фев 2014, 17:48

Re: SQLite на SD карте

Сообщение AnDron » 13 фев 2014, 17:55

tyapavel писал(а):Понятно. Изменил суперкласс. Теперь Actyvity.onDestroy() можно не переопределять.

Код: Выделить всё

import java.io.File;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;

public abstract class MyDataBaseOpenHelper {

        private SQLiteDatabase db;
        private File dbFile;
        private String dbName;
        private boolean isExistDB;

        public MyDataBaseOpenHelper(Context context, String dbName) {
            super();
            this.isExistDB = false;
            this.dbName = dbName;
            this.prepareBD();
            if(!this.isExistDB){
                    onCreate(db);
            }
            db.close();
        }
        
        private void prepareBD() {
            try{
                File sdCard = Environment.getExternalStorageDirectory();
                File directory = new File(sdCard.getAbsolutePath () + "/" + dbName);
                directory.mkdirs();
                dbFile = new File(directory, dbName);
                if(dbFile.exists())this.isExistDB=true;
                this.db = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
            }catch (Exception e) {}
        }

        abstract public void onCreate(SQLiteDatabase db);
        
        abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);

        public void close(){
                db.close();
        }
        
        public SQLiteDatabase getReadableDatabase(){
        	SQLiteDatabase resdDb = SQLiteDatabase.openDatabase(dbFile.getAbsolutePath(), null, SQLiteDatabase.OPEN_READWRITE);
            return resdDb;
        }

        public SQLiteDatabase getWritableDatabase(){
        	SQLiteDatabase resdDb = SQLiteDatabase.openDatabase(dbFile.getAbsolutePath(), null, SQLiteDatabase.OPEN_READWRITE);
        	return resdDb;
        }
}
Не могу понять как поменять в данном случае версию базы? Создаваемая база всегда имеет версию "0".

Аватара пользователя
altwin
Сообщения: 1951
Зарегистрирован: 13 ноя 2013, 14:46

Re: SQLite на SD карте

Сообщение altwin » 13 фев 2014, 19:07

AnDron писал(а):
tyapavel писал(а):Понятно. Изменил суперкласс. Теперь Actyvity.onDestroy() можно не переопределять.

Код: Выделить всё

import java.io.File;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;

public abstract class MyDataBaseOpenHelper {

        private SQLiteDatabase db;
        private File dbFile;
        private String dbName;
        private boolean isExistDB;

        public MyDataBaseOpenHelper(Context context, String dbName) {
            super();
            this.isExistDB = false;
            this.dbName = dbName;
            this.prepareBD();
            if(!this.isExistDB){
                    onCreate(db);
            }
            db.close();
        }
        
        private void prepareBD() {
            try{
                File sdCard = Environment.getExternalStorageDirectory();
                File directory = new File(sdCard.getAbsolutePath () + "/" + dbName);
                directory.mkdirs();
                dbFile = new File(directory, dbName);
                if(dbFile.exists())this.isExistDB=true;
                this.db = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
            }catch (Exception e) {}
        }

        abstract public void onCreate(SQLiteDatabase db);
        
        abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);

        public void close(){
                db.close();
        }
        
        public SQLiteDatabase getReadableDatabase(){
        	SQLiteDatabase resdDb = SQLiteDatabase.openDatabase(dbFile.getAbsolutePath(), null, SQLiteDatabase.OPEN_READWRITE);
            return resdDb;
        }

        public SQLiteDatabase getWritableDatabase(){
        	SQLiteDatabase resdDb = SQLiteDatabase.openDatabase(dbFile.getAbsolutePath(), null, SQLiteDatabase.OPEN_READWRITE);
        	return resdDb;
        }
}
Не могу понять как поменять в данном случае версию базы? Создаваемая база всегда имеет версию "0".
ответ в этой строчке: abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
т.е. базу нужно не создавать а обновлять. Но это именно в этом случае.
Изображение

Ответить