Перейти к содержанию
Fire Monkey от А до Я
  • 0

Одновременное обращение к БД Sqlite из программы и из сервиса


Rusland

Вопрос

Рекомендуемые сообщения

  • 1
В 01.04.2016 в 15:01, Rusland сказал:

Совсем забыл, из сервиса никак к БД не обратиться - иначе валится с ошибкой Segment fault и валит вместе с собой основное приложение.

А как ты connection к БД прописываешь с апликухи и из сервиса?

Ссылка на комментарий
  • 0

 

2 часа назад, Rusland сказал:

Через TFDConnection, указав путь к файлу

Rusland, может конечно бредовая версия, т.к. слишком просто :-)  где файл БД лежит? есть ли у сервиса доступ к нему?

Ссылка на комментарий
  • 1
3 часа назад, Belov.V. сказал:

 

Rusland, может конечно бредовая версия, т.к. слишком просто :-)  где файл БД лежит? есть ли у сервиса доступ к нему?

БД кладу в TPath.GetDocumentsPath.

Ссылка на комментарий
  • 0

Я так же мучался с ини файлом. В конце концов прописал путь напрямую. Думаю что с БД так же прокатит.

Пример:

В апликации фаил создавался по такому пути:

System.IOUtils.TPath.Combine(System.IOUtils.TPath.GetDocumentsPath, 'KDGConfig.ini');

А в сервисе задавался такой путь:

'/data/data/com.embarcadero.KDGPhoneCallApplication/files/KDGConfig.ini'

Изменено пользователем umkes
Ссылка на комментарий
  • 0

Rusland, удалось решить эту проблему?

Еще есть эта ветка про такое.

На Stackoverflow создал вопрос, ждем ответа.

Изменено пользователем Pax Beach
Ссылка на комментарий
  • 0

в PlatformSDK есть helper SQLiteOpenHelper.java, в нем метод getWritableDatabase(). Осталось научиться в сервисе работать с SQLite таким образом.

Может есть из вас, кто хорошо разобрался, как импортировать JAVA код в Delphi?

    /**
     * Create and/or open a database that will be used for reading and writing.
     * The first time this is called, the database will be opened and
     * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
     * called.
     *
     * <p>Once opened successfully, the database is cached, so you can
     * call this method every time you need to write to the database.
     * (Make sure to call {@link #close} when you no longer need the database.)
     * Errors such as bad permissions or a full disk may cause this method
     * to fail, but future attempts may succeed if the problem is fixed.</p>
     *
     * <p class="caution">Database upgrade may take a long time, you
     * should not call this method from the application main thread, including
     * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
     *
     * @throws SQLiteException if the database cannot be opened for writing
     * @return a read/write database object valid until {@link #close} is called
     */
    public synchronized SQLiteDatabase getWritableDatabase() {
        if (mDatabase != null) {
            if (!mDatabase.isOpen()) {
                // darn! the user closed the database by calling mDatabase.close()
                mDatabase = null;
            } else if (!mDatabase.isReadOnly()) {
                return mDatabase;  // The database is already open for business
            }
        }

        if (mIsInitializing) {
            throw new IllegalStateException("getWritableDatabase called recursively");
        }

        // If we have a read-only database open, someone could be using it
        // (though they shouldn't), which would cause a lock to be held on
        // the file, and our attempts to open the database read-write would
        // fail waiting for the file lock.  To prevent that, we acquire the
        // lock on the read-only database, which shuts out other users.

        boolean success = false;
        SQLiteDatabase db = null;
        if (mDatabase != null) mDatabase.lock();
        try {
            mIsInitializing = true;
            if (mName == null) {
                db = SQLiteDatabase.create(null);
            } else {
                db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler);
            }

            int version = db.getVersion();
            if (version != mNewVersion) {
                db.beginTransaction();
                try {
                    if (version == 0) {
                        onCreate(db);
                    } else {
                        if (version > mNewVersion) {
                            onDowngrade(db, version, mNewVersion);
                        } else {
                            onUpgrade(db, version, mNewVersion);
                        }
                    }
                    db.setVersion(mNewVersion);
                    db.setTransactionSuccessful();
                } finally {
                    db.endTransaction();
                }
            }

            onOpen(db);
            success = true;
            return db;
        } finally {
            mIsInitializing = false;
            if (success) {
                if (mDatabase != null) {
                    try { mDatabase.close(); } catch (Exception e) { }
                    mDatabase.unlock();
                }
                mDatabase = db;
            } else {
                if (mDatabase != null) mDatabase.unlock();
                if (db != null) db.close();
            }
        }
    }

 

Ссылка на комментарий
  • -1

Возможный выход из ситуации не держать постоянный коннект к базе? Открыл, прочитал или записал, закрыл. Можно перед открытием проверку на доступ сделать с ожиданием.

 

Ссылка на комментарий
  • 0

Я нашел решение, к сожалению, пока только для работы с UniDAC:

Обновил UniDAC компоненты для Berlin до последней версии (6.3.12).

Компоненты TUniConnection и TUniQuery отлично работают с SQLite в Android Service. FireDAC в Android Service пока запустить не удалось, но у меня такой задачи нет.

В Deployment host приложения добавляю файл базы данных, Remote Path задаю ".\assets\internal\". И спокойно из сервиса получаю к нему доступ. Мой сервис локальный в одном потоке с приложением. Если делать Intent Service или Remote — наверное, придется помещать файл в другой, доступный каталог, или общаться через намерения (Intents).

Надеюсь мой код будет полезен для вас.

procedure TDM.conSQLiteBeforeConnect(Sender: TObject);
begin
{$IF DEFINED(iOS) or DEFINED(ANDROID)}
  conSQLite.Database := TPath.Combine(TPath.GetDocumentsPath, 'mybase.sqlite');
{$ENDIF}
end;

procedure TDM.conSQLiteError(Sender: TObject; E: EDAError; var Fail: Boolean);
begin
  Log('--- DB error: %s:', [E.Message]);
  Fail := False;
end;

function TDM.AndroidServiceStartCommand(const Sender: TObject; const Intent: JIntent; Flags, StartId: Integer): Integer;
begin
  Log('+ START with Intent: ' + JStringToString(Intent.getAction.toString), []);
  if Intent.getAction.equalsIgnoreCase(StringToJString('StopIntent')) then
  begin
    try
      conSQLite.Disconnect;
      Log('- DB disconnected', []);
    except
      on E: Exception do
        Log('- can not to disconnect DB', [E.Message]);
    end;

    Log('... service to be stoped', []);
    JavaService.stopSelf;

    Result := TJService.JavaClass.START_NOT_STICKY; // don't reload service
  end
  else
  begin
    Log('... service started', []);

    try
      conSQLite.Connect;
      Log('+ DB connected', []);

      UniQuery.SQL.Text := 'select count(*) as ALLREC from orders';
      UniQuery.Open;
      if UniQuery.RecordCount > 0 then
      begin
        UniQuery.First;
        Log('... record count: %s', [UniQuery.FieldByName('ALLREC').AsString]);
      end;
      UniQuery.Close;
    except
      on E: Exception do
        Log('- can not to connect DB: %s', [E.Message]);
    end;

    Result := TJService.JavaClass.START_STICKY; // rerun service if it stops
  end;
end;
Изменено пользователем Pax Beach
Ссылка на комментарий
  • 0

... Продолжаем исследование по теме:

Оказывается в модуле Androidapi.JNI.GraphicsContentViewText есть класс TJSQLiteDatabase, который реализует возможность работы с SQLite на Android.

Пример использования этого класса на JAVA я писал выше, осталось просто перенести пример реализацию работы с классом на Delphi.

 

Ссылка на комментарий
  • 0

Как же я намучился с этим одно время, нигде не нашел решение. Поделюсь, раз уж наткнулся на этот вопрос.
Первое правило использования сервисов в под android - никаких упоминаний FMX и всего что связано с графикой, пусть вы и не используете её.
Например: если вы упомянули в сервисе, в разделе uses FMX.Types - ваш сервис будет падать с ошибкой Segment fault.
Также, допустим у вас есть юнит uMyUnit, и в этом юните в разделе uses есть FMX.Types - тоже будет падать с ошибкой Segment fault.
Повторюсь, сервис никаким образом не должен знать об FMX. 

Причина падения FireDAC это - Wait курсор. 

Решение 1: Если вы используете designTime компонент, добавьте рядом компонент FDGUIxWaitCursor
настройте его так:

Property: Provide,  Value: Console
Property: ScreenCursor, Value: gcrNone

Решение 2: Если вы используете динамическое создание подключения:

uses 
..., FireDAC.ConsoleUI.Wait, ... 

var
 FDGUIxWaitCursor: TFDGUIxWaitCursor;
begin
  FDGUIxWaitCursor := TFDGUIxWaitCursor.Create(nil);
  FDGUIxWaitCursor.Provider := 'Console';
  FDGUIxWaitCursor.ScreenCursor := TFDGUIxScreenCursor.gcrNone;
end;

 

Ссылка на комментарий
  • 0
В 12.02.2019 в 18:07, Паршенко Виктор сказал:

Причина падения FireDAC это - Wait курсор. 
Решение 1: Если вы используете designTime компонент, добавьте рядом компонент FDGUIxWaitCursor
 

Спасибо за этот совет, мне он помог наконец-то успешно запустить мой android сервис, который сохряняет в БД sqlite некоторые данные из инета. 

Но есть другая проблема. Я запускаю свое мобильное приложение, нажимаю кнопочку и запускается фоновый сервис. Пока я нахожусь в главном приложении, сервис работает, я вижу как обновляются данные в таблицах. Все хорошо. Если закрыть приложение, то сервис работает в фоне - тоже все ок. Но если я попытають снова запустить приложение, пока сервис в фоне делает выборку или записывает данные в таблицу, то мое приложение  не запускается - я вижу черный экран. Как только сервис закончил работать с таблицами и сделал небольшую паузу на 5 минут, в этот промежуток времени я могу окрыть свое приложение. Вывод такой: сервис блокирует мою базу данных пока с ней работает. Как сделать паралельный доступ к БД и из приложения и из сервиса? 

Сервис использует простую схему работы:

fdquery1.SQL.Text := 'select * from table1'

fdquery1.Open

for i:= 0 to  fdquery1.RecordCount -1 do

begin

// здесь я считываю поля таблицы(например, ссылки на сайты), затем лезу по каждому урлу на сайт и, если на нем появились новые данные, то сохраняю их в Table2.

end;

Я так понимаю, пока у меня fdquery1.Open, мое приложение не запустится, поскольку база заблокирована сервисом. Как это лечится?

FDСonnection LockingMode = imNormal

FDQuery cmdExecMode = amNonBlocking

Ссылка на комментарий
  • 0
9 часов назад, apach сказал:

Как сделать паралельный доступ к БД и из приложения и из сервиса? 

Я так понимаю, пока у меня fdquery1.Open, мое приложение не запустится, поскольку база заблокирована сервисом. Как это лечится?

В общем я сам разобрался. Для того, чтобы приложение и сервис без проблем работали на чтение и запись с одной базой данных, я установил для FDConnection.Params.JournalMode = jmWAL. Как это работает нашел тут. Также установил Synchronous = snNormal и FetchOptions.Mode = fmAll. Сейчас приложение запускается без проблем при работающем в фоне сервисе. 

Изменено пользователем apach
Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...