Подтвердить что ты не робот

Что относительно многопоточности в Android-SQLite?

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

Итак, вот моя история: Когда я начал разрабатывать это приложение, я ничего не знал о sqlite, поэтому я просто не использовал нишу синхронизации на Java. Результат: у меня появилось много исключений: "SQLiteException: база данных заблокирована: BEGIN EXCLUSIVE;"

Затем я синхронизировал все свои транзакции с обычным блоком синхронизации Java() {}. Все стало намного лучше. Но я использовал Cursors для реализации CursorAdapter для моих списков. Поэтому иногда я получал то же самое "SQLiteException: база данных заблокирована: BEGIN EXCLUSIVE;"

Я закончил создание надежной утилиты sqlite с небольшим потоком, которая обрабатывает все эти потокобезопасные вещи. Также мне нужно использовать что-то вроде ArrayAdapter (читать все данные из Cursor и закрывать его после прочтения, а также синхронизировать этот блок) для моего интерфейса. Итак, он работает нормально

Но мне не нравится такой способ обработки пользовательского интерфейса, потому что пользовательский интерфейс стал медленнее с этим решением - чтение некоторого количества данных с помощью курсора довольно быстро, но медленнее, чем использование CursorAdapter

Итак, кто получил решение по этому вопросу? Спасибо вам

4b9b3361

Ответ 1

Итак, наконец, вышло решение. Вот оно.

Я прочитал несколько форумов, групп google и узнал, что база данных sqlite должна открываться только один раз. Итак, я реализовал это с помощью singleton.

Кроме того, я реализовал некоторый код db для синхронизации всех операций записи (чтобы предотвратить одновременное выполнение операций записи несколькими потоками). И я не забочусь об открытии курсоров, читая их.

После нескольких дней тестирования у меня нет сообщений об ошибках от моих пользователей, поэтому я думаю, что это работает

В моей предыдущей работе я многократно открывал базу данных sqlite в приложении, что и было проблемой.

Ответ 2

SQLite реализует исключительную блокировку записи, общую схему блокировки чтения. Это означает, что вы можете одновременно работать с одновременными читателями в базе данных или с одним автором, вы не можете иметь оба. Если вы используете функцию ведения журнала WAL, вы можете одновременно использовать один записывающий и несколько считывателей в базе данных, но у вас все еще не может быть более одного автора. Существует отличная документация по SQLite concurrency здесь и здесь,

Возможно, вам стоит подумать о том, чтобы взглянуть на DB Berkeley. Berkeley DB предлагает SQL API, который полностью совместим с SQLite, Если факт, то, что мы сделали, - добавить SQL-парсер, планировщик и исполнитель поверх уровня хранения Berkeley DB. Это дает разработчику приложений SQLite библиотеку, совместимую с SQLite, которая обладает дополнительной масштабируемостью, concurrency и надежностью (HA), а также другими функциями. Berkeley DB поддерживает несколько считывателей и пишет одновременно доступ к базе данных. Есть два превосходных белых документа, написанных Майком Оуэнсом, автором "The Definitive Guide to SQLite", которые сравнивают DB Berkeley и SQLite (Сравнение производительности, Поведенческие различия).

Отказ от ответственности: я являюсь одним из менеджеров продуктов для Berkeley DB, поэтому я немного предвзятый. Но запросы, подобные вашим (требуется больше concurrency, масштабируемость, надежность SQLite), именно поэтому мы решили предоставить объединенную библиотеку, которая даст вам лучшее из обоих миров.

Ответ 3

Если вы используете только один вспомогательный класс oneton для доступа к db, вам не нужно синхронизировать себя, и вы можете использовать помощника из нескольких читателей/писателей, потому что вспомогательный класс управляет самой синхронизацией.

Посмотрите в этом сообщении для подробного объяснения mor

Ответ 4

Используйте однотонный помощник для открытия соединений.

1) Откройте столько читаемых соединений, сколько захотите, и закройте их после того, как вы закончите с ним.

2) Для перезаписываемых подключений вы должны открыть только одно доступное для записи соединение.

При попытке открыть другое записываемое соединение верните уже открытое соединение. Если нет доступного для записи соединения, откройте новое доступное для записи соединение. Храните счетчик writeable_connection и закрывайте доступное для записи соединение, когда все потоки выполняются с ним.

Я собираюсь реализовать это и дам вам знать, работает ли это, когда я закончил.

Ответ 5

Хорошо, прочитав документацию, мой предыдущий ответ кажется неправильным. У меня есть один (тон) SQLiteOpenHelper, поэтому кажется, что все соединения одинаковы. Даже читаемые являются такими же, как и перезаписываемые соединения.

Итак, я собираюсь пропустить вызов getReadableDatabase и использовать только getWritableDatabase. Тогда я собираюсь держать счетчик, чтобы убедиться, что база данных закрывается один раз и только один раз.

Так как SQLiteOpenHelper сериализует записи, все должно быть в порядке. Я просто должен следить за соединением, чтобы я не оставил его открытым в конце.

Итак, в моем классе DbHelper у меня теперь есть:

private int activeDatabaseCount = 0;
public synchronized SQLiteDatabase openDatabase() {
    SQLiteDatabase connection = getWritableDatabase(); // always returns the same connection instance
    activeDatabaseCount++;
    return connection; 
}
public synchronized void closeDatabase(SQLiteDatabase connection) {
    activeDatabaseCount--;
    if (activeDatabaseCount == 0) {
        if (connection != null) {
            if (connection.isOpen()) {
                connection.close();
            }
        }
    }
}

Ответ 6

Ciaoo: Вот решение:

Очевидно, я не пробую весь код, но думаю, что он работает нормально (Wera Radio работает над этой логикой, посмотрите на Google Play).

Я пытался объяснить все в коде.

import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ScrollView;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

class HelpUI {

   final static class GetDataFromDBInAsyncMode extends AsyncTask<Object, Object, Object> {

      private Context context;
      private String SQL;
      private final int xMin = -X_App.ScreenHeight; // put here your screen height (get it from code)
      private final int xMax = X_App.ScreenHeight * 2; // put here your screen height (get it from code)

      // this is the main view witch go to have all views
      // Use this in onProgressUpdate to add other vies you get in runtime when you get data from database
      private ViewGroup view_container;

      // if you have a scrollview hide views witch isnot visible to user
      private ScrollView scroll_view_container;

      // this workaround make free processors in measureaments, your UI is more fluent
      final ViewTreeObserver.OnScrollChangedListener onScrollChangedListener = () -> {

         if (view_container == null || view_container.getChildCount() == 0) { return; }

         int scrollY = scroll_view_container.getScrollY();

         for (int i = 0; i < view_container.getChildCount(); i++) {

            final View current = view_container.getChildAt(i);
            final int topView = current.getTop(); //container_views.getChildAt(i).getTop();
            final int diffTop = topView - scrollY;

            if ((diffTop > xMin) && (diffTop < xMax)) {
               current.setVisibility(View.VISIBLE);
            } else {

               current.setVisibility(View.INVISIBLE);
            }

         }

      };

      // constructor
      GetDataFromDBInAsyncMode(Context ctx, String mSQL) {
         this.context = ctx;
         this.SQL = mSQL;

         // put here the id of scrollViewContainer
         scroll_view_container = X_App.getRootV().findViewById(R.id.scroll_view_container);
         if (scroll_view_container != null) {
            // add listener on scroll
            scroll_view_container.getViewTreeObserver().addOnScrollChangedListener(onScrollChangedListener);
         }

      }


      @Override
      protected Object doInBackground(Object... objects) {


         // All dirty things go to being in background
         // Your cursor
         final Cursor cursor = X_SqLite.get(X_App.getContext()).GeDataAsCursor(SQL);

         if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {

            // The magic part
            ScheduledExecutorService giveMeAsync = Executors.newSingleThreadScheduledExecutor();

            // Give time to processor to do her tasks and do you dirty task here
            // 50 millisec is enough per a single task
            giveMeAsync.scheduleAtFixedRate(() -> {
               ViewGroup viewInflated = ((Activity) this.context).findViewById(R.id.icon_view);

               // Give your data from Database
               // Do here all your things but take care some of these need to be done on UI thread (like adding view etc, you can do that on onProgressUpdate)
               final String data1 = cursor.getString(cursor.getColumnIndex("data1"));
               viewInflated.setTag(data1);

               // Send this to UI thread
               publishProgress(viewInflated, false);

               // Here test to cursor if is finish or not
               if (!cursor.moveToNext()) {
                  giveMeAsync.shutdownNow();
                  cursor.close();
                  publishProgress(null, true);
               }

            }, 1, 50, TimeUnit.MILLISECONDS);


         }

         // otherwise if cursor is emty close them
         if (cursor != null && cursor.getCount() == 0) {
            cursor.close();
            publishProgress(null, true);
         }


         return null;

      }


      @Override
      protected void onProgressUpdate(Object... values) {
         final View viewInflated = (View) values[0];
         final boolean isCursorEnded = (Boolean) values[0];

         // Here you is in main thread
         // You can do all what you do with the viewInflated


         if (isCursorEnded) {
            //raise your event to tell to your app reading data is finished
         }

      }


   }


}

Использование:

import android.app.Activity;
import android.os.Bundle;

class MyActivity extends Activity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      new HelpUI.GetDataFromDBInAsyncMode(this, "Select * FROM YourTable").execute();
   }
} 

Вы должны быть уверены в одном: ваш пользовательский интерфейс бесплатен