Александр Горбач


программирование, админство

Обновление sqlite базы данных в андроид-приложении из assets

без комментариев

Интересная задача: как обновить базу данных приложения, если мы ее используем в качестве источника данных и не храним никаких пользовательских данных? Сходу можно предложить вариант использовать текстовый дамп таблицы или при поставке обновления приложения полностью перезаписывать данные в приложении на новые. Рассмотрим второй вариант в применении к SQLite базе данных.

UPD. Подход прикольный, но в использовать не рекомендую, при каждом обращении к базе данных перекопируется файл из ассетса. Причина в том, что при моем подходе почему-то после отработки функции onCreate движок не считал базу данных созданной. Сейчас в поиске оптимального решения.

Для удобства работы с базой данных в adnroid используется наследование от класса SQLiteOpenHelper. Как говорит нам документация на класс он предназначен для управления созданием базы данных и управление версиями этой базы. При создании дочернего класса, необходимо реализовать методы onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) и по желанию onOpen(SQLiteDatabase). Так же этот класс будет заботиться об открытии базы данных если она существует, создании, если не существует и обновлении, по необходимости. В том числе класс использует транзакции для того, чтобы всегда держать базу данных в доступном состоянии.
Таким образом стандартный путь предлагает нам использовать sql скрипты для изменения базы данных, которые мы зашиваем в тело методов. Нас же интересует способ, при котором мы можем использовать файл базы данных SQLite, поставляемый с приложением.
В поставке приложения сам файл базы данных удобнее всего поместить в папку assets приложения. Для примера предположим, что наш файл имеет название «mydb.sqlite3», то его мы можем получить следующим образом:

context.getAssets().open(DATABASE_NAME);

В итоге мы получим InputStream нашего файла.

Затем мы просто в нужное место записываем данные из файла:

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. Если мы попробуем перезаписывать файл внутри этих методов, то мы в итоге получим «битый» файл базы данных. Значит мы должны самостоятельно отслеживать событие на создание/изменение базы данных (а это случится в случае если мы производим установку/обновление приложения). В итоге код, который отвечает за управление базой данных:

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();
    }

}

И в основном коде мы используем таким образом:

  DataStoreSQLiteHelper dbHelper = new DataStoreSQLiteHelper(context);
  dbHelper.createDatabase();
  SQLiteDatabase database = dbHelper.openDatabase();

Автор Sash(k)a

Апрель 7th, 2014 at 9:51 пп

Яндекс.Метрика