Использование SQLite в Android-разработке. Tips and tricks

от автора


Привет, Хабр!
Некоторое время занимаюсь разработкой для Android и сегодня хотел бы рассказать об опыте, полученном в процессе решения одной задачи.

Предупреждение:

Для опытных раработчиков в статье, скорей всего, не будет ничего нового.

Для меня этот проект стал первым, где надо было вплотную использовать SQLite (раньше он был нужен не более, чем для select <что-нибудь> <откуда-то>).

Задача такова: сканировать штрих-коды товаров, распознавать их, сверять со справочниками и выводить результат пользователю.

В ходе решения сделал несколько интересных для себя выводов.

1) Первичный ключ таблиц не обязательно должен называться "_id".

Это нужно, только если вы хотите отображать таблицу, используя стандартный механизм
ListView — CursorAdapter — LoaderManager — ContentProvider (см. примечание здесь)
В принципе, тривиальное утверждение, описанное в документации, однако как-то (лично у меня, во всяком случае) сложилось представление, что поле первичного ключа в таблицах обязательно должно называться _id. Раньше всегда так делал, не вдаваясь в подробности, во избежание.

Другое название ключа может быть необходимым, если надо импортировать в SQLite уже разработанную ранее структуру таблиц.
В моём случае — у таблиц справочников есть уже свои поля [Something_ID], по которым выполняется присоединение этих таблиц. И логично эти поля сделать первичными ключами, поскольку они будут автоматически проиндексированы.

2) Решение задачи автоматического создания структуры БД и заполнения её начальными данными.

Сначала, при первом запуске приложения думал просто получать данные от удалённого сервера и делать insert таблиц справочников. Это плохой вариант, поскольку данных много (чуть больше 2Mb).

Чуть лучше — делать bulkInsert, т.е. вставлять данные в рамках одной транзакции. Работает быстрей, но принципиально не отличается от первоначального варианта. На хабре на эту тему уже есть хорошая статья.

Вариант реализации bulkInsert в провайдере:

    @Override     public int bulkInsert(Uri uri, ContentValues[] values) {         int numInserted = 0;         final String table = selectTable(uri);         database = databaseHandler.getWritableDatabase();         database.beginTransaction();         try {             for (ContentValues cv : values) {                 if (database.insert(table, null, cv) <= 0) {                     throw new SQLException("Failed to insert row into " + uri);                 }             }             database.setTransactionSuccessful();             numInserted = values.length;         } finally {             database.endTransaction();             getContext().getContentResolver().notifyChange(uri, null);         }         return numInserted;     } 

А хотелось бы, чтобы предзаполненные таблицы с данными уже были готовы к началу работы пользователя с приложением. И нашёлся альтернативный вариант — библиотека android-SQLite-asset-helper

Суть такова: БД создаётся не на устройстве в момент работы, а в процессе разработки приложения, сжимается, зипуется и кладётся в assets. Далее в проекте хелпер работы с БД наследуется не от стандартного SQLiteOpenHelper, а от SQLiteAssetHelper. И всё, при первом обращении пользователя база копируется в приложение, подробности реализации инкапсулированы в хелпере (и мне даже лень было в них вдаваться).

Подход очень понравился своими преимуществами:

  • Скорость. У меня на копирование 2Мб заполненной базы с десятком таблиц уходит меньше секунды, что происходит ровно 1 раз за всё время жизни приложения.
    Также отсутсуют дополнительные конвертации данных из одного формата в другой (раньше подобную задачу я стал бы решать, положив в assets, скажем, JSON-файл, и начитывая его в базу при первом запуске).
  • Упрощение разработки структуры БД. Отпадает утомительная необходимость писать скрипты создания таблиц в OnCreate хелпера + можно использовать дополнительные приложения для управления SQLite. Для убунту мне понравилась SQLitestudio, простая и понятная. (Хотя и небезглючная, честно сказать — в текущей версии 2.1.4 не может создать триггер для представления, но где не справилась она, доделал через стандартный консольный sqlite3).

3) Особенности взаимодействия представлений (view) SQLite с андроидным LoaderManager.
На вопросах что такое LoaderManager и как им пользоваться, подробно останавливаться не стану, лично мне помог прекрасный цикл статей. Скажу только, что хотел использовать именно LoaderManager, чтобы возложить на него задачу автоматического обновления изменившихся данных в списке.

Однако, вставлять данные надо в таблицу, а отображать — из связанной с ней вьюхи, где вместо id-полей подставлены значения:

 CREATE TABLE [table_scan] (    [_id] INTEGER PRIMARY KEY AUTOINCREMENT,    [NR_ID] INTEGER NOT NULL,    [T_ID] INTEGER NOT NULL,    [Color_ID] INTEGER NOT NULL,    [R_ID] INTEGER NOT NULL,    [Barcode] TEXT NOT NULL,    [NumberSeat] INTEGER,    [Date] DATETIME NOT NULL DEFAULT(DATETIME('now', 'localtime')),    [Deleted] INTEGER NOT NULL DEFAULT '0',    [Status] INTEGER NOT NULL DEFAULT '0',    [Export] INTEGER NOT NULL DEFAULT '0');     CREATE VIEW [view_scan] AS SELECT _id, Barcode, Status, Deleted, NumberSeat,  goods_catalog.T_Articul, colors_catalog.Color_Name, sizes_catalog.R_Name  FROM table_scan    INNER JOIN goods_catalog ON goods_catalog.T_ID = table_scan.T_ID    INNER JOIN colors_catalog ON colors_catalog.Color_ID = table_scan.Color_ID    INNER JOIN sizes_catalog ON sizes_catalog.R_ID = table_scan.R_ID  WHERE Deleted = 0; 

В лоб такой вариант, как оказалось, не работает. Для лоадера uri на таблицу и uri на вьюху — два разных uri 🙂
Т.е. если проинициализировать в нём view_scan, вместо table_scan, то при вставке в таблицу обновления списка не будет.
С таблицей же всё отлично обновляется, но на выходе вместо красивых значений — их ID-ключи, непонятные людям.

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

Ок, дополняю вьюху недостающими id-полями

CREATE VIEW [view_scan] AS SELECT   table_scan._id, table_scan.NR_ID,    table_scan.T_ID,table_scan.Color_ID,   table_scan.R_ID, table_scan.Barcode,   table_scan.NumberSeat, table_scan.Deleted,    table_scan.Status,   goods_catalog.T_Articul,   colors_catalog.Color_Name,   sizes_catalog.R_Name FROM table_scan  INNER JOIN goods_catalog ON goods_catalog.T_ID = table_scan.T_ID  INNER JOIN colors_catalog ON colors_catalog.Color_ID = table_scan.Color_ID  INNER JOIN sizes_catalog ON sizes_catalog.R_ID = table_scan.R_ID WHERE Deleted = 0; 

и пишу триггер вставки:

CREATE TRIGGER insert_view_scan   instead of insert on view_scan     begin     insert into table_scan(NR_ID,T_ID,Color_ID,R_ID,Barcode,NumberSeat,Status)     values(new.NR_ID, new.T_ID, new.Color_ID, new.R_ID, new.Barcode, new.NumberSeat, new.Status);    end; 

Теперь всё работает. В LoaderManager при инициализации отдаётся uri вьюхи, запрос на вставку тоже идёт к вьюхе, а всю остальную работу делает SQLite. Лоадер при этом делает то, что должен, т.е. мониторит курсор и автоматически передаёт адаптеру списка изменившиеся данные.

На этом всё. Будет интересно почитать ещё что-нибудь про продвинутые техники работы со SQLite на Android.
Ну и объективная критика тоже интересна 🙂

ссылка на оригинал статьи http://habrahabr.ru/post/205620/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *