Обновление sqlite базы данных в андроид-приложении из assets
Интересная задача: как обновить базу данных приложения, если мы ее используем в качестве источника данных и не храним никаких пользовательских данных? Сходу можно предложить вариант использовать текстовый дамп таблицы или при поставке обновления приложения полностью перезаписывать данные в приложении на новые. Рассмотрим второй вариант в применении к SQLite базе данных.
UPD. Подход прикольный, но в использовать не рекомендую, при каждом обращении к базе данных перекопируется файл из ассетса. Причина в том, что при моем подходе почему-то после отработки функции onCreate движок не считал базу данных созданной. Сейчас в поиске оптимального решения.
Для удобства работы с базой данных в adnroid используется наследование от класса SQLiteOpenHelper. Как говорит нам документация на класс он предназначен для управления созданием базы данных и управление версиями этой базы. При создании дочернего класса, необходимо реализовать методы onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) и по желанию onOpen(SQLiteDatabase). Так же этот класс будет заботиться об открытии базы данных если она существует, создании, если не существует и обновлении, по необходимости. В том числе класс использует транзакции для того, чтобы всегда держать базу данных в доступном состоянии.
Таким образом стандартный путь предлагает нам использовать sql скрипты для изменения базы данных, которые мы зашиваем в тело методов. Нас же интересует способ, при котором мы можем использовать файл базы данных SQLite, поставляемый с приложением.
В поставке приложения сам файл базы данных удобнее всего поместить в папку assets приложения. Для примера предположим, что наш файл имеет название «mydb.sqlite3», то его мы можем получить следующим образом:
1 |
context.getAssets().open(DATABASE_NAME); |
В итоге мы получим InputStream нашего файла.
Затем мы просто в нужное место записываем данные из файла:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
try { InputStream myInput = context.getAssets().open(DATABASE_NAME); // Path to the just created empty db String outFileName = db.getPath(); // Open the empty db as the output stream OutputStream myOutput = new FileOutputStream(outFileName); // transfer bytes from the inputfile to the outputfile byte[] buffer = new byte[1024]; int length; while ((length = myInput.read(buffer)) > 0) { myOutput.write(buffer, 0, length); } // Close the streams myOutput.flush(); myOutput.close(); myInput.close(); } catch (IOException e) { e.printStackTrace(); } |
В принципе, мы могли бы этот код поместить в методы onCreate и onUpdate, если бы не транзакционность операции в классе SQLiteOpenHelper. Если мы попробуем перезаписывать файл внутри этих методов, то мы в итоге получим «битый» файл базы данных. Значит мы должны самостоятельно отслеживать событие на создание/изменение базы данных (а это случится в случае если мы производим установку/обновление приложения). В итоге код, который отвечает за управление базой данных:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
package me.gorbach.example.app.datahelpers; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class DataStoreSQLiteHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "example.db"; private static final int DATABASE_VERSION = 8; private static String DATABASE_PATH; private Context context; private SQLiteDatabase myDataBase; private static boolean databaseMustBeUpgraded = false; public DataStoreSQLiteHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); this.context = context; DATABASE_PATH = context.getFilesDir().getPath()+"/../databases/"; } @Override public void onCreate(SQLiteDatabase db) { } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (newVersion > oldVersion) { Log.v("Database Upgrade", "Database version higher than old."); databaseMustBeUpgraded = true; } } public void deleteDatabase() { File file = new File(DATABASE_PATH + DATABASE_NAME); if(file.exists()) { file.delete(); } } private boolean checkDataBase() { boolean checkDB = false; if (databaseMustBeUpgraded) { deleteDatabase(); databaseMustBeUpgraded = false; } try { File dbFile = new File(DATABASE_PATH + DATABASE_NAME); checkDB = dbFile.exists(); } catch(SQLiteException e) { e.printStackTrace(); } return checkDB; } public void createDatabase() { this.getReadableDatabase(); boolean dbExist = checkDataBase(); if(dbExist) { Log.v("DB Exists", "db exists"); } boolean dbExist1 = checkDataBase(); if(!dbExist1) { this.getReadableDatabase(); try { this.close(); copyDataBase(); } catch (IOException e) { throw new Error("Error copying database "); } } } private void copyDataBase() throws IOException { InputStream mInput = context.getAssets().open(DATABASE_NAME); String outFileName = DATABASE_PATH + DATABASE_NAME; OutputStream mOutput = new FileOutputStream(outFileName); byte[] mBuffer = new byte[2024]; int mLength; while ((mLength = mInput.read(mBuffer)) > 0) { mOutput.write(mBuffer, 0, mLength); } mOutput.flush(); mOutput.close(); mInput.close(); } public SQLiteDatabase openDatabase() throws SQLException { String myPath = DATABASE_PATH + DATABASE_NAME; myDataBase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE); return myDataBase; } public synchronized void closeDataBase()throws SQLException { if(myDataBase != null) myDataBase.close(); super.close(); } } |
И в основном коде мы используем таким образом:
1 2 3 |
DataStoreSQLiteHelper dbHelper = new DataStoreSQLiteHelper(context); dbHelper.createDatabase(); SQLiteDatabase database = dbHelper.openDatabase(); |