Лучшие практики для Android предварительно заполненного SQLite из ресурсов - PullRequest
0 голосов
/ 03 мая 2020

Мне нужно разработать приложение Android, которое использует предварительно заполненную базу данных SQLite. С каждой версией приложения база данных будет иметь больше данных, поэтому ее придется обновлять с каждой версией. И, поскольку мне приходится делать сложные запросы, я предпочитаю использовать запросы SQLite напрямую, а не использовать Room Persistence.

Я должен сказать, что много читал об этой топи c, я прочитал много о SQLiteHelper, и я много читал о том, как скопировать базу данных из моей папки ресурсов, но я не могу найти хороший учебник, объединяющий обе вещи.

Предполагая, что у меня есть типичный SQLiteHelper

public class MyDatabaseHelper extends SQLiteOpenHelper {

    public static final String DATABASE_NAME = "mydatabase.sqlite";
    public static final int DATABASE_VERSION = 1;

    public MyDatabaseHelper(Context context) {

        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    @Override
    public SQLiteDatabase getWritableDatabase() {
        //...
    }

    @Override
    public SQLiteDatabase getReadableDatabase() {
        //...
    }

    public void copyDatabaseFromAssets() {
        // Typical method to copy my database from assets to my writable directory
    }

}

Мои сомнения:

  • Где я должен вызвать copyDatabaseFromAssetsIfNeeded? Внутри метода getWritableDatabase? Внутри метода getReadableDatabase? или внутри onCreateMethod? Я предполагаю, что это должно быть то же самое ??

  • В случае, если есть новая версия базы данных, DATABASE_VERSION + 1, после того, как я вызову getWritableDatabase, будет вызван onUpgradeMethod, и мне придется управлять здесь моим обновление базы данных. Что я должен делать внутри этого метода? Перезаписать мою текущую базу данных новой из папки активов? Может ли это вызвать некоторые проблемы параллелизма, если база данных используется? Или лучше скопировать новую базу данных в другой записываемый файл и изменить текущую с помощью ATTACH, DROP и INSERT?

  • Наконец, так как я собираюсь получить доступ к базе данных в большое количество действий, будет ли хорошей практикой иметь MyDatabaseHelper в качестве одиночного, вызывать getWritableDatabase при открытии приложения и закрывать его при закрытии приложения?

РЕДАКТИРОВАТЬ:

Я продолжаю исследовать эти 3 вопроса.

Я почти уверен, что должен использовать синглтон, потому что мой SQLiteOpenHelper будет использоваться в большом количестве действий и фрагментов, а мое приложение является многопоточным.

И кажется, что методы onCreate и onUpgrade не очень хорошо работают, когда база данных поступает из ресурсов.

Итак, после прочтения нескольких постов я думаю, что мой SQLiteHelper может быть таким:

public class MyDatabaseHelper extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "mydatabase.sqlite";
    private static final int DATABASE_VERSION = 1;

    private static MyDatabaseHelper sInstance;
    private static SQLiteDatabase myDB = null;
    private String writableDatabasePath = "";
    // This attribute gives me a warning for being a context class inside a static, but I do not know how to avoid it.
    private Context ctxt = null;

    public static synchronized MyDatabaseHelper getInstance(Context context) {

        if (sInstance == null) {
            sInstance = new MyDatabaseHelper(context.getApplicationContext());
        }
        return sInstance;
    }

    private MyDatabaseHelper(Context context) {

        super(context, DATABASE_NAME, null, DATABASE_VERSION);

        ctxt = context;
        writableDatabasePath = getWritableDbPath(context);
        if (!existsDatabase()) {
            try {
                copyDatabaseFromAssets();
            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        }
    }

    // It seems that to get the writable path of the database I have
    // to create an auxiliary SQLiteOpenHelper
    private static String getWritableDbPath(Context context) {

        String path = "";
        MySQLiteOpenHelper helper = new MySQLiteOpenHelper(context, "temporary.sqlite");
        SQLiteDatabase database = helper.getWritableDatabase();
        String filePath = database.getPath();
        path = filePath.substring(0, filePath.lastIndexOf('/') + 1);
        database.close();
        return path;
    }

    private boolean existsDatabase() {

        SQLiteDatabase checkDB = null;
        boolean exist = false;
        try {
            String dbPath = writableDatabasePath + DATABASE_NAME;
            checkDB = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
        } catch (SQLiteException e) {
            Log.v("SAMPLE", "database does't exist");
        }

        if (checkDB != null) {
            exist = true;
            checkDB.close();
        }
        return exist;
    }

    public void copyDatabaseFromAssets() throws IOException {

        AssetManager asset = ctxt.getAssets();
        InputStream myInput = asset.open(DATABASE_NAME);
        String outFileName = writableDatabasePath + DATABASE_NAME;
        OutputStream myOutput = new FileOutputStream(outFileName);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = myInput.read(buffer)) > 0) {
            myOutput.write(buffer, 0, length);
        }
        Log.e("SAMPLE", "database copied to "+outFileName);
        myOutput.flush();
        myOutput.close();
        myInput.close();
    }

    // I have read this is to avoid some problems with Android P
    @Override
    public void onConfigure(SQLiteDatabase db) {

        super.onConfigure(db);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            db.disableWriteAheadLogging();
        }
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

        // Not used working with assets database
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        // Not used working with assets database
    }

    public void openDatabase() {

        if((myDB == null) || !myDB.isOpen()) {
            myDB = SQLiteDatabase.openDatabase(writableDatabasePath + DATABASE_NAME, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
            int currentDBVersion = getDatabaseVersion();
            int newDBVersion = DATABASE_VERSION;
            if(newDBVersion > currentDBVersion) {
                setDatabaseVersion(newDBVersion);
                try {
                    copyDatabaseFromAssets();
                } catch (IOException e) {
                    throw new Error("Error copying database");
                }
            }
        }
    }

    public String executeQuery() {

        Cursor _cursor = myDB.rawQuery("SELECT name FROM sqlite_master WHERE type='table';", null);
        String result = "";
        if (_cursor.getCount() > 0) {
            _cursor.moveToFirst();
            do {
                String name = _cursor.getString(0);
                result = result + name + ", ";
            } while (_cursor.moveToNext());
        }
        _cursor.close();
        return result;
    }

    private int getDatabaseVersion()
    {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctxt);
        return sharedPreferences.getInt("DatabaseVersion", 0);
    }

    private void setDatabaseVersion(int version)
    {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctxt);
        sharedPreferences.edit().putInt("DatabaseVersion", version).commit();
    }

}

Я протестировал его, и кажется, что он работает как при установке fre sh, так и с обновлением. Но кто-нибудь видит что-то не так с этим? Я что-то упустил или это нормально для производственного приложения?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...