Я уже писал об этом тут Несколько особенностей и вопросы по ним, в комментариях предложили несколько вариантов, которые можно использовать, чтобы переносить изменённые данные из старой базы в новую. В моём сообщении речь шла об обновлении файла базы данных без внесения изменений, нормального решения на тот момент я так и не нашёл. Оставалось только удалять файл базы и перезапускать приложение, чтобы подтянулся новый файл. Но нет таких пользователей, которым нравилось бы каждый раз перезапускать приложение, поэтому необходимо нормальное решение.
Upd (21.04.14). Проверил код на Delphi XE6 и добавил информацию о необходимых изменениях.
Upd (21.04.14). Проверил код на Delphi XE6 и добавил информацию о необходимых изменениях.
Итак, представим себе ситуацию:
Мы написали приложение, содержащее в себе базу с кулинарными рецептами (можете подставить сюда что-то своё :). В первой версии приложения содержится 100 рецептов. Через месяц мы добавляем в базу ещё 50 рецептов и раздаём (не через Маркет) пользователям новую версию приложения.
Вроде всё просто, но есть одна особенность, при установке нового приложения поверх старого, база с рецептами из старого приложения сохранится, и пользователь будет видеть только её. Т.е. все данные сохранятся, а нам ведь надо показать пользователю новые рецепты. Вот тут я ни как не мог найти решения и, похоже, не я один…
Сегодня я с гордостью могу заявить, что нашёл/написал решение данной проблемы. Пришлось ещё немного углубиться в Android, но это принесло плоды в виде решения некоторых проблем/задач (Например: Получаем список доступных устройств хранения информации).
Идём далее.
Основы.
В Android все установленные приложения, т.е. «.apk» файлы хранятся в директории «/data/app/». Эта директория не доступна пользователям (без root-прав) также как и любая директория из «/data/».
Задеплоинные файлы, после установки приложения хранятся тут «/data/data/app_name/files». При обновлении/запуске приложения осуществляется проверка файлов и если какой-то файл не найден в папке «/data/data/app_name/files», то он достаётся из «.apk» файла. Значит, чтобы получить новую версию базы данных, нужно удалить старую и вытащить новую из «.apk» файла. Не всё так просто :).
Теперь кодим.
«.apk» файл – это ZIP архив. Посмотрите на скриншот.
Важное замечание: В директории «/data/app/» все «.apk» файлы хранятся с полным именем приложения (пример: «com.embarcadero.Project1») и в конец добавляется тире и цифра (обычно единица или двойка).
Пошагово, на примере обновления базы данных при помощи кнопки (сделано для удобства понимания, можно перенести например на событие TForm1.FormCreate):
- При запуске новой версии приложения, пользователь нажимает кнопку «Обновить»
- В коде мы отключаемся от базы (если были подключены)
- Получаем имя «.apk» файла в директории «/data/app/» (это я сделал при помощи JNI - API Android - getPackageResourcePath())
- Теперь удаляем (TFile.Delete) старый файл, который лежит где-то тут «/data/data/app_name/files/test.db»
- Открываем «.apk» файл (Delphi умеет работать с Zip - System.Zip)
- Извлекаем файл «assets/internal/test.db» в папку «/data/data/app_name/files/»
- Закрываем архив и подчищаем память
- Подключаемся к базе и вуаля, видим новые рецепты
Код для примера (Delphi XE5 Update 2):
unit Unit1; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, Data.DbxSqlite, Data.DB, Data.SqlExpr, Data.FMTBcd, FMX.StdCtrls; type TForm1 = class(TForm) test_connect: TSQLConnection; SQLQuery1: TSQLQuery; Label1: TLabel; Button1: TButton; Button2: TButton; Label2: TLabel; procedure test_connectBeforeConnect(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.fmx} uses System.IOUtils, System.Zip, Androidapi.JNI.JavaTypes, FMX.Helpers.Android; procedure TForm1.Button1Click(Sender: TObject); begin SQLQuery1.Active := True; SQLQuery1.Open; Label1.Text := SQLQuery1.FieldByName('allcount').AsString; SQLQuery1.Close; end; procedure TForm1.Button2Click(Sender: TObject); var zip: TZipFile; PackageName: JString; begin // Отключаемся от базы test_connect.Connected := False; // Получаем имя apk файла PackageName := SharedActivityContext.getPackageResourcePath; if TFile.Exists(JStringToString(PackageName)) then begin // Удаляем старый файл базы TFile.Delete(TPath.GetHomePath + PathDelim + 'test.db'); // Извлекаем новый файл базы zip := TZipFile.Create; zip.Open(JStringToString(PackageName), TZipMode.zmRead); zip.Extract('assets/internal/test.db', TPath.GetDocumentsPath, False); zip.Close; zip.free; end else showmessage('False'); // Подключаемся к базе test_connect.Connected := True; // Выполняем запрос на количество записей в базе SQLQuery1.Active := True; SQLQuery1.Open; Label2.Text := SQLQuery1.FieldByName('allcount').AsString; SQLQuery1.Close; end; procedure TForm1.FormCreate(Sender: TObject); begin test_connect.Connected := True; end; procedure TForm1.test_connectBeforeConnect(Sender: TObject); begin test_connect.Params.Values['Database'] := TPath.GetDocumentsPath + PathDelim + 'test.db'; end; end.
UPDATE (21.04.14):
Чтобы код заработал в Delphi XE6, необходимо:
в "uses" добавить модуль "Androidapi.Helpers".
Архив обновлён (Добавил комментарий для Delphi XE6)!
Видео:
На видео я показываю, как сначала устанавливаю приложение с двумя записями в базе, а потом обновляю приложение до 3 записей в базе. И всё это без всяких перезагрузок и т.п.
Исходный код: Скачать с Google Drive
На этом всё.
p.s. Есть что добавить, пишите в комменты.
Для тех, кто читает через строчку:
Внимание! Этот способ предназначен для случаев когда в базу не вносятся изменения, а так же обновление приложения происходит вручную, т.е. НЕ через Маркет. Видимо это не ясно из статьи.