Правильна робота з БД в Android
Існує три способи роботи з даними в БД, які відразу кидаються на думку: 1) Ви створюєте порожню структуру бази даних. Користувач працює з додатком (створює нотатки, видаляє їх) та база даних наповнюється. Прикладом може бути програма NotePad в демо-прикладах developer.android.com або на вашому дроїд-девайсі. 2) Ви вже маєте готову БД, наповнену даними, яку потрібно розповсюджувати з додатком, або парси дані з файлу в assets. 3) Отримувати дані з мережі в міру необхідності. Якщо є якийсь ще один або два способи, то з радістю доповню цей список з вашою допомогою. Усі основні туторіали розраховані якраз на перший випадок. Ви пишіть запит на створення структури БД і виконуєте цей запит у методі onCreate() класу SQLiteOpenHelper, наприклад:
Приблизно так. Більш повний варіант класу та інших складових можна переглянути за посиланням унизу статті. Додатково можна перевизначити методи onOpen(), getReadableDatabase()/getWritableDatаbase(), але зазвичай вистачає того, що вище і методів вибірки даних. Далі, екземпляр цього класу створюємо в нашому додатку при його запуску і виконуємо запити, тобто проблемну частину пройдено. Чому вона проблемна? Тому що, коли користувач качає програми з маркету, то не замислюється про вашу базу даних і може статися будь-що. Скажімо мережа зникла або інший процес запустився, або ви написали вразливий до помилок код.
До речі, є ще один момент, який варто звернути увагу. Змінну екземпляра нашого класу можна створити і зберігати в об'єкті Application і звертатися в міру необхідності, але потрібно не забувати викликати метод close(), оскільки постійний коннект до бази - це важкий ресурс. Крім того, можуть бути колізії при роботі з базоюз кількох потоків. Але є й інший спосіб, наприклад, створювати наш об'єкт при необхідності звернення до БД. Думаю це питання переваги, але яке також необхідно обговорити.
А тепер найголовніше. Що, якщо нам знадобилося використовувати БД, що вже існує, з даними в додатку? Трохи погугливий, Ви відразу натрапите на таку «чудову статтю» - www.reigndesign.com/blog/using-your-own-sqlite-database-in-android-applications в якій, як здасться, є потрібна панацея. Але не тут було. У ній ще й кілька помилок.
Можливо виходом із цього буде наступне рішення (розглядається варіант №2). Використовуючи перший варіант роботи з базою, наповнити її даними після створення, наприклад:
В цілому, цей пост показує (щодо способу № 2) як робити не треба, але й містить пару цікавих думок. Метод getReadableDatabase() можна перевизначити наприклад так:
До речі: дотримуючись практики самої платформи, поле первинного ключа варто називати "_id".
UPDЩойно перевірив свій підхід. Все працює в емуляторі, але будьте обережні.
Файлик data.txt лежить у assets такий: Zametka #1 Zametka #2 Zametka #3 Zametka #4
І клас додатку:
Зазначу, що цей клас використовується тільки для демонстрації та перевірки того, що станеться під час виклику методів getReadableDatabase()/getWritableDatabase() та створення бази. У реальних проектах код потрібно адаптувати. Крім того, в базі з'явилася табличка android_metadata(без моєї участі), тому вказана вище помилка вирішена. Сподіваюся комусь знадобиться.
Цікаві доповнення №1(від хабраюзера Kalobok)
Я поки що зовсім відмовився від SQLiteOpenHelper — виявилося, що в ньому неможливо створити базу на карті SD.Теоретично те, що він повертає, має використовуватися як шлях до бази. На практиці SQLiteOpenHelper іноді використовує його, а іноді обходить стороною - залежить від того, чи відкриваємо ми базу на читання або запис, чи існує вона і т.д. SQLiteOpenHelper.getWritableDatabase викликає Context.openOrCreateDatabase, який, у свою чергу, використовує Context.validateFilePath, щоб отримати повний шлях до файлу. Там використовується приватний метод Context.getDatabasesDir, перевизначити який не можна приїхали. База буде створена у стандартній директорії.
А ось якщо ми викликали SQLiteOpenHelper.getReadableDatabase, спочатку він спробує викликати той самий getWritableDatabase. Але якщо це не вийде, то він піде в обхід Context.openOrCreateDatabase - сам викличе Context.getDatabasePath (ось його ми можемо підправити) і сам відкриє потрібну базу. Цей спосіб нас би влаштував, якби він використовувався завжди. Але нажаль. :( Втім, задум з цим хелпером була хороша, а реалізація - лівою ногою з бодуна.
Хардкорна конфа за С++. Ми запрошуємо лише профі.