«

»

Авг
05

Распространяем SQLite базу в Assets ресурсах Андроид приложения.

android sqliteВ данной статье я расскажу как создать на ПК SQLite базу данных, наполнить ее данными, включить в состав APK пакета приложения и начать использовать ее в вашем приложении для чтения или записи. Делается это довольно просто, но когда делаешь это в первый раз сталкиваешься с не совсем очевидными вещами, о которых и будет рассказано в этой статье.

Введение

И так, вы пишите приложение, в котором нужна база данных изначально заполненная приличным количеством данных. Как распространять эту базу с приложением? Простого способа, который по щелчку вашего пальца, решит эту задачу SDK не предоставляет. Но существует несколько вариантов как это можно сделать. Основные из которых, мне видятся следующими:

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

Раз способов несколько, нужно выбрать какой-то из них.
Первый способ самый простой, на первый взгляд. Однако, если ваша база имеет сложную структуру и содержит несколько мегабайт данных процесс инициализации может оказаться довольно трудоемким и занять приличные время (нужно создать таблицы, заполнить их данными, создать индексы, загрузить триггеры и др.). Поэтому данный способ рекомендуется в случае, если ваша база содержит немного данных.
Второй способ следует использовать в случает, когда база занимает приличное место (несколько Мб и более), никому из маркета не хочется качать APK размером в 50-100Мб, а учитывая, что у пользователей очень много устройств с малым объемом внутренней памяти, вариант со скачиванием базы из сети и сохранением ее на SD-карту видится самым подходящим. Так же данный вариант подходит, когда неизвестно заранее какая база данных понадобится пользователю. Например, вы написали словарь, а словарные базы скачиваются потом, в зависимости от выбранных пользователем языков.
Третий вариант  хорош тогда, когда у вас есть готовая SQLite база данных. Мы просто включаем ее в пакет приложения и после небольших манипуляций можем начать использовать ее.

А что полезного в SDK?

SDK предлагает нам начать работать с базой с класса SQLiteOpenHelper, который управляет созданием и версионностью баз данных. Это абстрактный класс и нам самим нужно определить, каким образом создать базу, если она не существует (onCreate), и что делать в случае, если у базы поменялась версия (onUpgrade). Но в нашем случае, когда нужно использовать уже готовую базу данных, не все оказывается так просто, как кажется.

Почему мы не можем использовать onCreate и onUpgrade?

Файл с нашей базой мы положим в директорию проекта asssets/db. Директория assets специально создана для того, чтобы включать в приложение специфичные ресурсы, управлять которыми Андроид не умеет или не должен. Чтобы начать использовать нашу базу данных, приложение должно скопировать файл с базой в специальную директорию на устройстве и уже оттуда осуществлять к ней доступ, так как файлы из assetsтолько для чтения. Казалось бы все просто: реализуем свои методы onCreate и onUpgrade, в которых скопируем нашу базу в нужное место. Но не все так просто. Когда мы попадаем в метод onCreate SQLiteOpenHelper уже создает пустую базу данных, открывает ее для записи, начинает транзакцию и только потом передает управление в наш метод, где мы должны включить в транзакцию определенные SQL скрипты, которые создадут нужные таблицы, добавят начальные данные и тд. После выхода из onCreate транзакция выполняется и данные попадают в базу. Похожая история и с методом onUpgrade. То есть механизм создания базы, предоставляемый SDK для нас не подходит. Придется идти своим путем, приступаем к действию.

Приступаем к действию. Создаем базу.

Итак запускаем любимый редактор SQLite баз данных (SQLite Manager) и создаем базу. Сейчас идет олимпиада, поэтому создадим базу данных со странами-участницами и количеством завоеванных ими медалями. У нас будет 2 таблицы: названия стран и количество медалей. И добавим один вид осуществляющий выборку из этих таблиц, чтобы в приложении не писать запрос, join’ящий таблицы. Вот что получилось:

база данных

Не забываем про некоторые особенности:

  • колонку ИД в таблицах следует называть _id и делать их INTEGER PRIMARY KEY AUTOINCREMENT. В Андроиде существует соглашение, чтобы поле ИД называлось именно _id, тогда вы сможете с ходу использовать стандартные адаптеры для обработки выбранных из базы данных.
  • не забываем указать версию базы в настройках. Важно чтобы версия была больше 0. Так же, если вы произвели какие-то структурные изменения в базе, необходимо увеличить номер версии, чтобы ваша новая версия приложения могла что-то сделать в данном случае.

Готовую базу копируем в директорию assets/db проекта.

Начинаем кодить.

Итак, у нас есть файл с базой данных, мы прочитали хелп по SDK, наступило время что-нибудь приготовить. Наследуемся от SQLiteOpenHelper и впедед. Во-первых добавим статичных данных, которые понадобятся нам для работы. В основном это пути к файлам баз.

Так как нам не подходит стандартный способ создания базы данных (метод onCreate), мы добавляем новый статический метод, который будет проверять существование базы данных и ее версию, на соответствие базе данных из ресурсов проекта. Если база данных не существует или ее версия устарела, то копируем базу из ресурсов. Функцию назовем  Initialize.

Проверить существование базы данных, можно с помощью метода  openDatabase  класса SQLiteDatabase.

Теперь, если база не проинициализирована корректно, необходимо скопировать базу из ресурсов проекта в специальную директорию, где приложению стоит хранить свои базы. Пишем функцию copyInialDBfromAssets.

Код функции “стандартный” для копирования файлов. Единственное, что стоит упомянуть здесь – это класс  ChainedSQLiteException, который был создан для удобства обработки ошибок и прокидывания исключения на более высокий уровень. А так же функции closeSilent, которые пытаются закрыть поток и при этом не генерируют исключений в случае ошибок.

В принципе все готово, осталось написать защиту от “дурака”. Так как мы самостоятельно управляем создание базы и ее версий, то необходимо написать реализацию методов onCreate и onUpdate класса SQLiteOpenHelper, которая будет выдавать ошибку, в случае, если мы забыли вызвать наш код инициализации перед началом использования базы. В таком случае будем генерировать исключения.

Класс для работы с базой готов, пора посмотреть как он работает.

А работает ли?

Чтобы проверить работу нашего класса, создадим простую активити, которая будет инициализировать базу данных. Если инициализация не прошла – покажем диалог с ошибкой, если все хорошо – запустим новую активити, отображающую данные из базы. Код будет приблизительно таким:

Строчка, которая нас интересует больше всего: OlimpicRaceSQLhelper.Initialize(). Она запускает проверки базы и копирования новой базы из ресурсов, если требуется. В дальнейшем алгоритм работы с классом OlimpicRaceSQLhelper ничем не отличается от  SQLiteOpenHelper.

Заключение.

В данном уроке, мы познакомились с классом SQLiteOpenHelper и его ключевыми методами. Разработали код, для подготовки к использованию баз данных, распространяемых с приложением в ресурсах. Данный код бы проверен на работоспособность в различных версиях Андроид. Полный код приложения, а также код активити для отображения данных из базы, будет представлен в следующем уроке.

Удачных разработок.

17 комментариев

  1. 2000problem Сообщает:

    отлично, как раз нужне был такой код

    1. 2000problem Сообщает:

      Хорошо бы еще про курсоты примерчик бы…

      1. PandaСoder Сообщает:

        Надо будет заняться )

  2. Вячеслав Сообщает:

    Наткнулся на статью при поиске ответа на вопрос “Как прочитать ресурсы из статического метода?” Здесь есть это вроде как, только вот остается понять, что такое в коде App? Как только будет получен объект Application, вопрос будет решен.

    1. PandaСoder Сообщает:

      App это вот такой класс:
      public class App extends android.app.Application {
      private static App instance;
      public App() {instance = this;}
      public static App getInstance() {return instance;}
      }

      В манифесте, нужно для application задать правильное имя: android:name=”.App”
      Теперь, через App.getInstance() можно получать доступ к инстансу вашего приложения, и получить контекст, когда нет активити/сервиса.

      подробности тут: http://developer.android.com/reference/android/app/Application.html

      1. Вячеслав Сообщает:

        Оперативно! Спасибо. Да, изучив стандартный Application, я понял, что App – это не он, т.к. в стандартном нет getInstance()! Обалдеть, неужели нельзя было сделать? И, продолжая рыскать по интернету, сам пришел к выводу, что это наследник. Тем не менее, спасибо.

        1. PandaСoder Сообщает:

          это редко когда бывает нужно и так не совсем хорошо делать с идеологической точки зрения…
          если кому-то нужен контекст, то лучше явно его туда передать.

  3. Степан Сообщает:

    А как просто прочитать базу из assets не копируя ее?

    1. PandaСoder Сообщает:

      не получится, для работы sqlite нужен файл, напрямую из assets никак не получится…

  4. Dan Сообщает:

    Пытался найти где можно задать версию БД в SQLiteManager, SQLite Database Browser и в Navicat – не нашел. Подскажите, плиз, в каком направлении рыть.

    1. PandaСoder Сообщает:

      все там есть ) в Sqlite Manager на вкладке “Настройки БД”

  5. Сергей Светлов Сообщает:

    Добрый день. А подскажите, пожалуйста, можно ли с помощью этого менеджера создать базу, а пользователю обращаться к ней из приложения с помощью интернета? То есть база будет у меня, я её буду пополнять, а при запуске приложения это будет отображаться. Спасибо, за ответ

    1. PandaСoder Сообщает:

      нет конечно, кодить ручками )

  6. Kashka Сообщает:

    Ребята, может у кого исходники есть? .. что-то совсем не получается ='(( Буду очень благодарна!!*)

  7. Михаил Сообщает:

    Возникает ошибка в самом классе “Implicit super constructor SQLiteOpenHelper() is undefined for default constructor. Must define an explicit constructor”

    И здесь: ” throw new ChainedSQLiteException(
    “Fail to copy initial db from assets”, ex);
    } finally {
    IOUtils.closeSilent(outStream);
    IOUtils.closeSilent(inStream);”
    – ChainedSQLiteException cannot be resolved to a type
    – IOUtils cannot be resolved

    Подскажите в чем проблема?

    1. Дмитрий Сообщает:

      тоже не работает код. И здесь: ” throw new ChainedSQLiteException(
      “Fail to copy initial db from assets”, ex);
      } finally {
      IOUtils.closeSilent(outStream);
      IOUtils.closeSilent(inStream);”
      – ChainedSQLiteException cannot be resolved to a type
      – IOUtils cannot be resolved

  8. Роман Сообщает:

    Здравствуйте. А как выполнять обновление базы. Через onUpgrade или можно по другому, тоже используя assets?

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

Мы сохраним Ваш e-mail в тайне.