Можно ли использовать один экземпляр SQLiteOpenHelper для всех операций в приложении Android?

37

Можно ли иметь один экземпляр SQLiteOpenHelper в качестве члена подкласса приложения и иметь все действия, которым нужен экземпляр SQLiteDatabase, получить его от одного помощника?

Теги:
android-cursor
android-loadermanager

5 ответов

39
Лучший ответ

Наличие одного экземпляра SQLiteOpenHelper может помочь в делах с потоками. Поскольку все потоки разделят общий SQLiteDatabase, обеспечивается синхронизация операций.

Однако я бы не стал подклассом Application. Просто введите статический член данных, который является вашим SQLiteOpenHelper. Оба подхода дают вам что-то доступное из любого места. Однако существует только один подкласс Application, что затрудняет использование других подклассов Application (например, для GreenDroid требуется один IIRC). Использование статического элемента данных позволяет избежать этого. Однако используйте Application Context при создании этого статического параметра SQLiteOpenHelper (конструктор), поэтому вы не пропустите некоторые другие Context.

И в случаях, когда вы не имеете дело с несколькими потоками, вы можете избежать любых возможных проблем с утечкой памяти, просто используя один экземпляр SQLiteOpenHelper для каждого компонента. Однако на практике вам нужно иметь дело с несколькими потоками (например, a Loader), поэтому эта рекомендация применима только к тривиальным приложениям, например, найденным в некоторых книгах...: -)

  • 0
    :) Я на самом деле использовал ваши классы LoaderEx и расширенную книгу по Android, чтобы научиться перемещать доступ к базе данных из потока пользовательского интерфейса. Большое вам спасибо за оба. Они были огромной помощью.
  • 1
    Так когда же вам следует вызывать «close», если все классы и потоки достигнут одного и того же экземпляра?
Показать ещё 26 комментариев
41

Нажмите здесь, чтобы перейти к моему сообщению в блоге на эту тему.


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

Подход №1: подклассификация `Application`

Если вы знаете, что ваше приложение не будет очень сложным (т.е. если вы знаете, что у вас будет только один подкласс Application), тогда вы можете создать подкласс Application и расширить свою основную деятельность Это. Это гарантирует, что один экземпляр базы данных работает на протяжении всего жизненного цикла приложения.

public class MainApplication extends Application {

    /**
     * see NotePad tutorial for an example implementation of DataDbAdapter
     */
    private static DataDbAdapter mDbHelper;

    /**
     * Called when the application is starting, before any other 
     * application objects have been created. Implementations 
     * should be as quick as possible...
     */
    @Override
    public void onCreate() {
        super.onCreate();
        mDbHelper = new DataDbAdapter(this);
        mDbHelper.open();
    }

    public static DataDbAdapter getDatabaseHelper() {
        return mDbHelper;
    }
}

Подход # 2: есть `SQLiteOpenHelper` - статический член данных

Это не полная реализация, но она должна дать вам хорошую идею о том, как правильно планировать класс DatabaseHelper. Статический метод factory гарантирует, что в любой момент существует только один экземпляр DatabaseHelper.

/**
 * create custom DatabaseHelper class that extends SQLiteOpenHelper
 */
public class DatabaseHelper extends SQLiteOpenHelper { 
    private static DatabaseHelper mInstance = null;

    private static final String DATABASE_NAME = "databaseName";
    private static final String DATABASE_TABLE = "tableName";
    private static final int DATABASE_VERSION = 1;

    private Context mCxt;

    public static DatabaseHelper getInstance(Context ctx) {
        /** 
         * use the application context as suggested by CommonsWare.
         * this will ensure that you dont accidentally leak an Activitys
         * context (see this article for more information: 
         * http://developer.android.com/resources/articles/avoiding-memory-leaks.html)
         */
        if (mInstance == null) {
            mInstance = new DatabaseHelper(ctx.getApplicationContext());
        }
        return mInstance;
    }

    /**
     * constructor should be private to prevent direct instantiation.
     * make call to static factory method "getInstance()" instead.
     */
    private DatabaseHelper(Context ctx) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.mCtx = ctx;
    }
}

Подход № 3: аннотация базы данных SQLite с помощью ContentProvider

Это подход, который я бы предложил. Во-первых, новый класс LoaderManager в значительной степени зависит от ContentProviders, поэтому, если вы хотите реализовать Activity или Fragment LoaderManager.LoaderCallbacks<Cursor> (что я предлагаю вам использовать, это волшебство!), Вам нужно будет реализовать ContentProvider для вашего приложения. Кроме того, вам не нужно беспокоиться о создании помощника базы данных Singleton с ContentProviders. Просто вызовите getContentResolver() из Activity, и система позаботится обо всем для вас (другими словами, нет необходимости в разработке шаблона Singleton, чтобы предотвратить создание нескольких экземпляров).

Надеюсь, что это поможет!

  • 2
    Просто подумал, что хотел бы указать, что библиотека LoaderEx CommonsWare показывает, как использовать интерфейс LoaderManager.LoaderCallbacks и Loader при работе непосредственно с базой данных SQLite вместо ContentProvider. github.com/commonsguy/cwac-loaderex
  • 0
    Вам нужно вызвать super.onCreate () из вашего переопределения. Возможно, то же самое для onTerminate () - не уверен.
Показать ещё 10 комментариев
7

Я написал MultiThreadSQLiteOpenHelper, который является расширенным приложением SQLiteOpenHelper для Android, где несколько потоков могут открывать и закрывать одну и ту же базу данных sqlite.

Вместо вызова метода close потоки запрашивают закрытие базы данных, не позволяя потоку выполнять запрос в закрытой базе данных.

Если каждый поток попросил закрыть, то фактически выполняется закрытие. Каждое действие или поток (u-нить и пользовательские потоки) выполняет открытый вызов в базе данных при возобновлении и запрашивает закрытие базы данных при приостановке или завершении.

Исходный код и образцы доступны здесь: https://github.com/d4rxh4wx/MultiThreadSQLiteOpenHelper

  • 0
    Почему вы не реализовали getReadableDatabase ()?
4

Я провел много исследований по этой теме, и я согласен со всеми пунктами, упомянутыми commonware. Но я думаю, что есть важный момент, который здесь не хватает, ответ на этот вопрос полностью зависит от вашего примера использования, поэтому, если ваше приложение читает базы данных через несколько потоков, и только чтение с использованием Singleton имеет огромную производительность как все функции синхронизируются и выполняются последовательно, поскольку есть одно соединение с базой данных С открытым исходным кодом это здорово, кстати. Вы можете копаться прямо в коде и посмотреть, что происходит. Из этого и некоторых тестов я понял, что верно следующее:

Sqlite takes care of the file level locking.  Many threads can read, one can write.  The locks prevent more than one writing.
Android implements some java locking in SQLiteDatabase to help keep things straight.
If you go crazy and hammer the database from many threads, your database will (or should) not be corrupted.

Если вы попытаетесь одновременно записать в базу данных из реальных отдельных соединений, произойдет сбой. Он не будет ждать, пока первое не будет сделано, а затем напишет. Он просто не напишет ваши изменения. Хуже того, если вы не вызываете правильную версию вставки/обновления в SQLiteDatabase, вы не получите исключение. Вы просто получите сообщение в своем LogCat, и это будет оно.

Первая проблема, реальные, разные связи. Самое замечательное в том, что с открытым исходным кодом вы можете вскочить и посмотреть, что происходит. Класс SQLiteOpenHelper делает некоторые забавные вещи. Хотя есть способ получить соединение с базой данных только для чтения, а также соединение для чтения и записи под капотом, это всегда одно и то же соединение. Предполагая, что нет ошибок записи файла, даже соединение, доступное только для чтения, действительно является единственным соединением для чтения и записи. Очень забавно. Таким образом, если вы используете один вспомогательный экземпляр в своем приложении, даже из нескольких потоков, вы никогда не используете несколько подключений.

Кроме того, класс SQLiteDatabase, из которого каждый помощник имеет только один экземпляр, реализует блокировку уровня Java на себе. Итак, когда вы выполняете операции с базой данных, все остальные операции db будут заблокированы. Таким образом, даже если у вас есть несколько потоков, которые делают вещи, если вы делаете это для максимизации производительности базы данных, у меня для вас есть плохие новости. Нет выгоды.

Интересные наблюдения

Если вы отключите один поток писем, так что только один поток записывает в db, но еще одно чтение, и оба имеют свои собственные соединения, производительность чтения повышается, и Я не вижу проблем с блокировкой. То, что нужно преследовать. Я еще не пробовал это с записью доработки.

Если вы собираетесь выполнять более одного обновления любого типа, оберните его в транзакцию. Кажется, что 50 обновлений, которые я делаю в транзакции, занимают столько же времени, сколько и 1 обновление за пределами транзакции. Я предполагаю, что за пределами вызовов транзакций каждое обновление пытается записать изменения db на диск. Внутри транзакции записи выполняются в одном блоке, и накладные расходы на запись затмевают сама логика обновления.

  • 0
    Отличное исследование. Спасибо, что поделился.
  • 0
    Спасибо @Julian, поэтому вывод заключается в том, что если ваша база данных только для чтения, не используйте SINGLETON, но создайте другие экземпляры, поскольку это позволило бы одновременный доступ, так как один экземпляр блокировал бы все другие операции при чтении базы данных и если вам нужно несколько считывателей и один писатель использует функцию WAL с отдельными экземплярами, поэтому SINGLETON не должен использоваться по моему мнению
1

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню